[tor-commits] [tor/master] hs-v3: Validate INTRO2 cells for onion balance

nickm at torproject.org nickm at torproject.org
Mon Feb 24 12:48:35 UTC 2020


commit 02f1caa583ca0e09e4c75ff6d9399f5d53931d2b
Author: David Goulet <dgoulet at torproject.org>
Date:   Thu Jan 9 15:18:32 2020 -0500

    hs-v3: Validate INTRO2 cells for onion balance
    
    Closes #32709
    
    Signed-off-by: David Goulet <dgoulet at torproject.org>
---
 src/feature/hs/hs_cell.c | 78 ++++++++++++++++++++++++++++++++++----
 src/feature/hs/hs_ob.c   | 97 ++++++++++++++++++++++++++++++++++++++++++++++++
 src/feature/hs/hs_ob.h   |  3 ++
 3 files changed, 171 insertions(+), 7 deletions(-)

diff --git a/src/feature/hs/hs_cell.c b/src/feature/hs/hs_cell.c
index d59ea9edb..680897cf9 100644
--- a/src/feature/hs/hs_cell.c
+++ b/src/feature/hs/hs_cell.c
@@ -13,6 +13,7 @@
 #include "feature/hs_common/replaycache.h"
 
 #include "feature/hs/hs_cell.h"
+#include "feature/hs/hs_ob.h"
 #include "core/crypto/hs_ntor.h"
 
 #include "core/or/origin_circuit_st.h"
@@ -802,6 +803,47 @@ get_introduce2_keys_and_verify_mac(hs_cell_introduce2_data_t *data,
   return intro_keys;
 }
 
+/** Return the newly allocated intro keys using the given service
+ * configuration and INTRODUCE2 data for the cell encrypted section.
+ *
+ * Every onion balance configured master key will be tried. If NULL is
+ * returned, no hit was found for the onion balance keys. */
+static hs_ntor_intro_cell_keys_t *
+get_intro2_keys_as_ob(const hs_service_config_t *config,
+                      const hs_cell_introduce2_data_t *data,
+                      const uint8_t *encrypted_section,
+                      size_t encrypted_section_len)
+{
+  uint8_t *ob_subcreds = NULL;
+  size_t ob_num_subcreds;
+  hs_ntor_intro_cell_keys_t *intro_keys = NULL;
+
+  ob_num_subcreds = hs_ob_get_subcredentials(config, &ob_subcreds);
+  if (!ob_num_subcreds) {
+    /* We are _not_ an OB instance since no configured master onion key(s)
+     * were found and thus no subcredentials were built. */
+    goto end;
+  }
+
+  for (size_t idx = 0; idx < ob_num_subcreds; idx++) {
+    /* Copy current data into a new INTRO2 cell data. We will then change the
+     * subcredential in order to validate. */
+    hs_cell_introduce2_data_t new_data = *data;
+    new_data.subcredential = &(ob_subcreds[idx * DIGEST256_LEN]);
+    intro_keys = get_introduce2_keys_and_verify_mac(&new_data,
+                                                    encrypted_section,
+                                                    encrypted_section_len);
+    if (intro_keys) {
+      /* It validates. We have a hit as an onion balance instance. */
+      goto end;
+    }
+  }
+
+ end:
+  tor_free(ob_subcreds);
+  return intro_keys;
+}
+
 /** Parse the INTRODUCE2 cell using data which contains everything we need to
  * do so and contains the destination buffers of information we extract and
  * compute from the cell. Return 0 on success else a negative value. The
@@ -856,14 +898,36 @@ hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data,
     goto done;
   }
 
-  /* Get the right INTRODUCE2 ntor keys and verify the cell MAC */
-  intro_keys = get_introduce2_keys_and_verify_mac(data, encrypted_section,
-                                                  encrypted_section_len);
+  /* First bytes of the ENCRYPTED section are the client public key (they are
+   * guaranteed to exist because of the length check above). We are gonna use
+   * the client public key to compute the ntor keys and decrypt the payload:
+   */
+  memcpy(&data->client_pk.public_key, encrypted_section,
+         CURVE25519_PUBKEY_LEN);
+
+  /* If we are configured as an Onion Balance instance, we need to try
+   * validation with the configured master public keys given in the config
+   * file. This is because the master identity key and the blinded key is put
+   * in the INTRODUCE2 cell by the client thus it will never validate with
+   * this instance default public key. */
+  if (service->config.ob_master_pubkeys) {
+    intro_keys = get_intro2_keys_as_ob(&service->config, data,
+                                       encrypted_section,
+                                       encrypted_section_len);
+  }
   if (!intro_keys) {
-    log_info(LD_REND, "Could not get valid INTRO2 keys on circuit %u "
-             "for service %s", TO_CIRCUIT(circ)->n_circ_id,
-             safe_str_client(service->onion_address));
-    goto done;
+    /* We are not an onion balance instance or no keys matched, fallback to
+     * our default values. */
+
+    /* Get the right INTRODUCE2 ntor keys and verify the cell MAC */
+    intro_keys = get_introduce2_keys_and_verify_mac(data, encrypted_section,
+                                                    encrypted_section_len);
+    if (!intro_keys) {
+      log_info(LD_REND, "Could not get valid INTRO2 keys on circuit %u "
+                        "for service %s", TO_CIRCUIT(circ)->n_circ_id,
+               safe_str_client(service->onion_address));
+      goto done;
+    }
   }
 
   {
diff --git a/src/feature/hs/hs_ob.c b/src/feature/hs/hs_ob.c
index 633b157a0..7e84af3d9 100644
--- a/src/feature/hs/hs_ob.c
+++ b/src/feature/hs/hs_ob.c
@@ -158,6 +158,8 @@ ob_option_parse(hs_service_config_t *config, const ob_options_t *opts)
       goto end;
     }
     smartlist_add(config->ob_master_pubkeys, pubkey);
+    log_info(LD_REND, "OnionBalance: MasterOnionAddress %s registered",
+             line->value);
   }
   /* Success. */
   ret = 1;
@@ -171,6 +173,26 @@ ob_option_parse(hs_service_config_t *config, const ob_options_t *opts)
   return ret;
 }
 
+/** For the given master public key and time period, compute the subcredential
+ * and put them into subcredential. The subcredential parameter needs to be at
+ * least DIGEST256_LEN in size. */
+static void
+build_subcredential(const ed25519_public_key_t *pkey, uint64_t tp,
+                    uint8_t *subcredential)
+{
+  ed25519_public_key_t blinded_pubkey;
+
+  tor_assert(pkey);
+  tor_assert(subcredential);
+
+  hs_build_blinded_pubkey(pkey, NULL, 0, tp, &blinded_pubkey);
+  hs_get_subcredential(pkey, &blinded_pubkey, subcredential);
+}
+
+/*
+ * Public API.
+ */
+
 /** Read and parse the config file at fname on disk. The service config object
  * is populated with the options if any.
  *
@@ -220,3 +242,78 @@ hs_ob_parse_config_file(hs_service_config_t *config)
   tor_free(config_file_path);
   return ret;
 }
+
+/** Compute all possible subcredentials for every onion master key in the
+ * given service config object. The subcredentials is allocated and set as an
+ * continous array containing all possible values.
+ *
+ * On success, return the number of subcredential put in the array which will
+ * correspond to an arry of size: n * DIGEST256_LEN where DIGEST256_LEN is the
+ * length of a single subcredential.
+ *
+ * If the given configuration object has no OB master keys configured, 0 is
+ * returned and subcredentials is set to NULL.
+ *
+ * Otherwise, this can't fail. */
+size_t
+hs_ob_get_subcredentials(const hs_service_config_t *config,
+                         uint8_t **subcredentials)
+{
+  unsigned int num_pkeys, idx = 0;
+  uint8_t *subcreds = NULL;
+  const int steps[3] = {0, -1, 1};
+  const unsigned int num_steps = ARRAY_LENGTH(steps);
+  const size_t subcred_len = DIGEST256_LEN;
+  const uint64_t tp = hs_get_time_period_num(0);
+
+  tor_assert(config);
+  tor_assert(subcredentials);
+
+  num_pkeys = smartlist_len(config->ob_master_pubkeys);
+  if (!num_pkeys) {
+    goto end;
+  }
+
+  /* Time to build all the subcredentials for each time period: the previous
+   * one (-1), the current one (0) and the next one (1) for each configured
+   * key in order to accomodate client and service consensus skew.
+   *
+   * If the client consensus after_time is at 23:00 but the service one is at
+   * 01:00, the client will be using the previous time period where the
+   * service will think it is the client next time period. Thus why we have
+   * to try them all.
+   *
+   * The normal use case works because the service gets the descriptor object
+   * that corresponds to the intro point's request, and because each
+   * descriptor corresponds to a specific subcredential, we get the right
+   * subcredential out of it, and use that to do the decryption.
+   *
+   * As a slight optimization, statistically, the current time period (0) will
+   * be the one to work first so we'll put them first in the array to maximize
+   * our chance of success. */
+
+  /* We use a flat array, not a smartlist_t, in order to minimize memory
+   * allocation. This function is called for _each_ INTRODUCE2 cell arriving
+   * on this instance and thus the less we allocate small chunks often,
+   * usually the healthier our overall memory will be.
+   *
+   * Size of array is: length of a single subcredential multiplied by the
+   * number of time period we need to compute and finally multiplied by the
+   * total number of keys we are about to process. In other words, for each
+   * key, we allocate 3 subcredential slots. */
+  subcreds = tor_malloc_zero(subcred_len * num_steps * num_pkeys);
+
+  /* For each time period step. */
+  for (unsigned int i = 0; i < num_steps; i++) {
+    SMARTLIST_FOREACH_BEGIN(config->ob_master_pubkeys,
+                            const ed25519_public_key_t *, pkey) {
+      build_subcredential(pkey, tp + steps[i],
+                          &(subcreds[idx * subcred_len]));
+      idx++;
+    } SMARTLIST_FOREACH_END(pkey);
+  }
+
+ end:
+  *subcredentials = subcreds;
+  return idx;
+}
diff --git a/src/feature/hs/hs_ob.h b/src/feature/hs/hs_ob.h
index d16896f2e..8ad6aabc4 100644
--- a/src/feature/hs/hs_ob.h
+++ b/src/feature/hs/hs_ob.h
@@ -13,6 +13,9 @@
 
 int hs_ob_parse_config_file(hs_service_config_t *config);
 
+size_t hs_ob_get_subcredentials(const hs_service_config_t *config,
+                                uint8_t **subcredentials);
+
 #ifdef HS_OB_PRIVATE
 
 typedef struct ob_options_t {





More information about the tor-commits mailing list