]> git.ipfire.org Git - thirdparty/tor.git/commitdiff
Happy families: finish implementing relay side.
authorNick Mathewson <nickm@torproject.org>
Thu, 13 Feb 2025 15:43:45 +0000 (10:43 -0500)
committerNick Mathewson <nickm@torproject.org>
Thu, 6 Mar 2025 14:41:54 +0000 (09:41 -0500)
src/app/config/config.c
src/app/config/or_options_st.h
src/app/main/main.c
src/feature/relay/relay_config.c
src/feature/relay/router.c
src/feature/relay/router.h
src/feature/relay/routerkeys.c
src/feature/relay/routerkeys.h
src/test/test_dir.c
src/test/test_routerkeys.c

index 102d1bbc04ea2b5a9e40d5e70914cb2fbb802581..a279507150fc6e1096d64295273afe31ab5850ab 100644 (file)
@@ -572,6 +572,7 @@ static const config_var_t option_vars_[] = {
   V(MetricsPortPolicy,           LINELIST, NULL),
   V(TestingMinTimeToReportBandwidth,    INTERVAL, "1 day"),
   VAR("MyFamily",                LINELIST, MyFamily_lines,       NULL),
+  V(UseFamilyKeys,               BOOL,     "0"),
   V(NewCircuitPeriod,            INTERVAL, "30 seconds"),
   OBSOLETE("NamingAuthoritativeDirectory"),
   OBSOLETE("NATDListenAddress"),
index 624dc61bc5c1c797af4dec44bc9d93c7c220845e..2c0b9ca58a42716917aebae47d5c97796a0e6b65 100644 (file)
@@ -493,6 +493,8 @@ struct or_options_t {
   struct config_line_t *MyFamily_lines; /**< Declared family for this OR. */
   struct config_line_t *MyFamily; /**< Declared family for this OR,
                                      normalized */
+  int UseFamilyKeys; /**< If set, we use one or more family keys
+                      * to certify this OR's membership. */
   struct config_line_t *NodeFamilies; /**< List of config lines for
                                 * node families */
   /** List of parsed NodeFamilies values. */
index 6d05bd1f5e2489473e19f793ee964e9aeef82949..705fa3abe9819296bec47099bd12c3887bdefdc5 100644 (file)
@@ -187,6 +187,11 @@ do_hup(void)
         generate_ed_link_cert(options, now, new_signing_key > 0)) {
       log_warn(LD_OR, "Problem reloading Ed25519 keys; still using old keys.");
     }
+    const networkstatus_t *ns = networkstatus_get_latest_consensus();
+    if (load_family_id_keys(options, ns)) {
+      log_warn(LD_OR, "Problem reloading family ID keys; "
+               "still using old keys.");
+    }
 
     /* Update cpuworker and dnsworker processes, so they get up-to-date
      * configuration options. */
index cc4d8ab42062c285243c1b4bcc9ec195411b7c58..fd965a293b5d24d2ac5882e2888ad09438d17f8e 100644 (file)
@@ -1274,6 +1274,7 @@ options_transition_affects_descriptor(const or_options_t *old_options,
   YES_IF_CHANGED_STRING(ContactInfo);
   YES_IF_CHANGED_STRING(BridgeDistribution);
   YES_IF_CHANGED_LINELIST(MyFamily);
+  YES_IF_CHANGED_BOOL(UseFamilyKeys);
   YES_IF_CHANGED_STRING(AccountingStart);
   YES_IF_CHANGED_INT(AccountingMax);
   YES_IF_CHANGED_INT(AccountingRule);
index 9c199df74beefaea6c47f5161f7c70f7aad54b7b..d6898ba3cf8442d77f285e1d17bea5ef489e0d9b 100644 (file)
@@ -1065,6 +1065,11 @@ init_keys(void)
   if (new_signing_key < 0)
     return -1;
 
+  if (options->command == CMD_RUN_TOR) {
+    if (load_family_id_keys(options, networkstatus_get_latest_consensus()) < 0)
+      return -1;
+  }
+
   /* 2. Read onion key.  Make it if none is found. */
   keydir = get_keydir_fname("secret_onion_key");
   log_info(LD_GENERAL,"Reading/making onion key \"%s\"...",keydir);
@@ -2528,6 +2533,21 @@ router_new_consensus_params(const networkstatus_t *ns)
 
   publish_even_when_ipv4_orport_unreachable = ar;
   publish_even_when_ipv6_orport_unreachable = ar || ar6;
+
+  warn_about_family_id_config(get_options(), ns);
+}
+
+/**
+ * Return true if the parameters in `ns` say that we should publish
+ * a legacy family list.
+ *
+ * Use the latest networkstatus (or returns the default) if `ns` is NULL.
+ */
+bool
+should_publish_family_list(const networkstatus_t *ns)
+{
+  return networkstatus_get_param(ns, "publish-family-list",
+                                 1, 0, 1); // default, min, max
 }
 
 /** Mark our descriptor out of data iff the IPv6 omit status flag is flipped
index f201fdbd63132ed96a415330118a6ae911179f79..37bcb3aec8ca5cb20467e5b63b97e1192dc07d0d 100644 (file)
@@ -81,6 +81,8 @@ void consider_publishable_server(int force);
 int should_refuse_unknown_exits(const or_options_t *options);
 
 void router_new_consensus_params(const networkstatus_t *);
+bool should_publish_family_list(const networkstatus_t *ns);
+
 void router_upload_dir_desc_to_dirservers(int force);
 void mark_my_descriptor_dirty_if_too_old(time_t now);
 void mark_my_descriptor_dirty(const char *reason);
index 5bfc2ebe65a090710367757b37da34f2e2c84cee..cab2b319fe20115e072fcd435e23895c511c56b1 100644 (file)
@@ -14,6 +14,8 @@
  * (TODO: The keys in router.c should go here too.)
  */
 
+#define ROUTERKEYS_PRIVATE
+
 #include "core/or/or.h"
 #include "app/config/config.h"
 #include "feature/relay/router.h"
@@ -21,6 +23,8 @@
 #include "feature/relay/routermode.h"
 #include "feature/keymgt/loadkey.h"
 #include "feature/nodelist/torcert.h"
+#include "feature/nodelist/networkstatus_st.h"
+#include "feature/dirauth/dirvote.h"
 
 #include "lib/crypt_ops/crypto_util.h"
 #include "lib/tls/tortls.h"
@@ -677,6 +681,206 @@ get_current_auth_key_cert(void)
   return auth_key_cert;
 }
 
+/**
+ * Prefix for the filename in which we expect to find a family ID key.
+ */
+#define FAMILY_KEY_FNAME "secret_family_key"
+
+/**
+ * Return true if `fname` is a possible filename of a family ID key.
+ *
+ * Family ID key filenames are FAMILY_KEY_FNAME, followed optionally
+ * by "." and a positive integer.
+ */
+STATIC bool
+is_family_key_fname(const char *fname)
+{
+  if (0 == strcmp(fname, FAMILY_KEY_FNAME))
+    return true;
+
+  unsigned num;
+  char ch;
+  if (tor_sscanf(fname, FAMILY_KEY_FNAME".%u%c", &num, &ch) == 1)
+    return true;
+
+  return false;
+}
+
+/**
+ * Tag to use on family key files.
+ */
+#define FAMILY_KEY_FILE_TAG "fmly-id"
+
+/**
+ * Look for all the family keys in `keydir`, load them into
+ * family_id_keys.
+ */
+STATIC int
+load_family_id_keys_impl(const char *keydir)
+{
+  if (BUG(!keydir))
+    return -1;
+
+  smartlist_t *files = tor_listdir(keydir);
+  smartlist_t *new_keys = NULL;
+  ed25519_keypair_t *kp_tmp = NULL;
+  char *fn_tmp = NULL;
+  char *tag_tmp = NULL;
+  int r = -1;
+
+  if (files == NULL) {
+    log_warn(LD_OR, "Unable to list contents of directory %s", keydir);
+    goto end;
+  }
+
+  new_keys = smartlist_new();
+  SMARTLIST_FOREACH_BEGIN(files, const char *, fn) {
+    if (!is_family_key_fname(fn))
+      continue;
+
+    tor_asprintf(&fn_tmp, "%s%s%s", keydir, PATH_SEPARATOR, fn);
+
+    kp_tmp = tor_malloc_zero(sizeof(*kp_tmp));
+    // TODO: If we ever allow cert provisioning here,
+    // use ed_key_init_from_file() instead.
+    if (ed25519_seckey_read_from_file(&kp_tmp->seckey, &tag_tmp, fn_tmp) < 0) {
+      log_warn(LD_OR, "%s was not an ed25519 secret key.", fn_tmp);
+      goto end;
+    }
+    if (0 != strcmp(tag_tmp, FAMILY_KEY_FILE_TAG)) {
+      log_warn(LD_OR, "%s was not a family ID key.", fn_tmp);
+      goto end;
+    }
+    if (ed25519_public_key_generate(&kp_tmp->pubkey, &kp_tmp->seckey) < 0) {
+      log_warn(LD_OR, "Unable to generate public key for %s", fn_tmp);
+      goto end;
+    }
+
+    smartlist_add(new_keys, kp_tmp);
+    kp_tmp = NULL; // prevent double-free
+
+    tor_free(fn_tmp);
+    tor_free(tag_tmp);
+  } SMARTLIST_FOREACH_END(fn);
+
+  set_family_id_keys(new_keys);
+  new_keys = NULL; // prevent double-free
+  r = 0;
+ end:
+  if (files) {
+    SMARTLIST_FOREACH(files, char *, cp, tor_free(cp));
+    smartlist_free(files);
+  }
+  if (new_keys) {
+    SMARTLIST_FOREACH(new_keys, ed25519_keypair_t *, kp,
+                      ed25519_keypair_free(kp));
+    smartlist_free(new_keys);
+  }
+  tor_free(fn_tmp);
+  tor_free(tag_tmp);
+  ed25519_keypair_free(kp_tmp);
+  return r;
+}
+
+/**
+ * Create a new family ID key, and store it in `fname`.
+ **/
+int
+create_family_id_key(const char *fname)
+{
+  int r = -1;
+  ed25519_keypair_t *kp = tor_malloc_zero(sizeof(ed25519_keypair_t));
+  if (ed25519_keypair_generate(kp, 1) < 0) {
+    log_warn(LD_BUG, "Can't generate ed25519 key!");
+    goto done;
+  }
+
+  if (ed25519_seckey_write_to_file(&kp->seckey,
+                                   fname, FAMILY_KEY_FILE_TAG)<0) {
+    log_warn(LD_BUG, "Can't write key to file.");
+    goto done;
+  }
+
+  r = 0;
+
+ done:
+  ed25519_keypair_free(kp);
+  return r;
+}
+
+/**
+ * If configured to do so, load our family keys from the key directory.
+ * Otherwise, clear the family keys.
+ *
+ * Additionally, warn about inconsistencies between family options.
+ * If `ns` is provided, provide additional warnings.
+ *
+ * `options` is required; `ns` may be NULL.
+ */
+int
+load_family_id_keys(const or_options_t *options,
+                    const networkstatus_t *ns)
+{
+  if (options->UseFamilyKeys) {
+    if (load_family_id_keys_impl(options->KeyDirectory) < 0)
+      return -1;
+
+    // This warning is _here_ because we want to give it (or not)
+    // every time keys are reloaded.
+    if (!smartlist_len(get_current_family_id_keys())) {
+      log_warn(LD_OR,
+               "UseFamilyKeys was configured, but no family keys were found. "
+               "Family keys need to be in %s, with names like "
+               FAMILY_KEY_FNAME", "FAMILY_KEY_FNAME".1, "
+               FAMILY_KEY_FNAME".2, etc. "
+               "See (XXXX INSERT URL HERE) for instructions.",
+               options->KeyDirectory);
+    } else {
+      log_info(LD_OR, "Found %d family ID keys",
+               smartlist_len(get_current_family_id_keys()));
+    }
+  } else {
+    set_family_id_keys(NULL);
+  }
+  warn_about_family_id_config(options, ns);
+  return 0;
+}
+
+/** Generate warnings as appropriate about our family ID configuration.
+ *
+ * `options` is required; `ns` may be NULL.
+ */
+void
+warn_about_family_id_config(const or_options_t *options,
+                            const networkstatus_t *ns)
+{
+  static int have_warned_absent_myfamily = 0;
+  static int have_warned_absent_familykeys = 0;
+
+  if (options->UseFamilyKeys) {
+    if (!have_warned_absent_myfamily &&
+        !options->MyFamily && ns && should_publish_family_list(ns)) {
+      log_warn(LD_OR,
+               "UseFamilyKeys was configured, but MyFamily was not. "
+               "UseFamilyKeys is good, but the Tor network still requires "
+               "MyFamily while clients are migrating to use family "
+               "keys instead.");
+      have_warned_absent_myfamily = 1;
+    }
+  } else {
+    if (!have_warned_absent_familykeys &&
+        options->MyFamily &&
+        ns && ns->consensus_method >= MIN_METHOD_FOR_FAMILY_IDS) {
+      log_notice(LD_OR,
+                 "MyFamily was configured, but UseFamilyKeys was not. "
+                 "It's a good time to start migrating your relays "
+                 "to use family keys. "
+                 "See (XXXX INSERT URL HERE) for instructions.");
+      have_warned_absent_familykeys = 1;
+    }
+  }
+}
+
 /**
  * Return a list of our current family id keypairs,
  * as a list of `ed25519_keypair_t`.
@@ -695,15 +899,14 @@ get_current_family_id_keys(void)
   return family_id_keys;
 }
 
-#ifdef TOR_UNIT_TESTS
 /**
- * Testing only: Replace our list of family ID keys with `family_id_keys`,
+ * Replace our list of family ID keys with `family_id_keys`,
  * which must be a list of `ed25519_keypair_t`.
  *
  * Takes ownership of its input.
  */
-void
-set_mock_family_id_keys(smartlist_t *keys)
+STATIC void
+set_family_id_keys(smartlist_t *keys)
 {
   if (family_id_keys) {
     SMARTLIST_FOREACH(family_id_keys, ed25519_keypair_t *, kp,
@@ -712,7 +915,6 @@ set_mock_family_id_keys(smartlist_t *keys)
   }
   family_id_keys = keys;
 }
-#endif
 
 void
 get_master_rsa_crosscert(const uint8_t **cert_out,
@@ -786,11 +988,7 @@ routerkeys_free_all(void)
   ed25519_keypair_free(master_identity_key);
   ed25519_keypair_free(master_signing_key);
   ed25519_keypair_free(current_auth_key);
-  if (family_id_keys) {
-    SMARTLIST_FOREACH(family_id_keys, ed25519_keypair_t *, kp,
-                      ed25519_keypair_free(kp));
-    smartlist_free(family_id_keys);
-  }
+  set_family_id_keys(NULL);
 
   tor_cert_free(signing_key_cert);
   tor_cert_free(link_cert_cert);
index f51f91b141311fa28f3646016e245ab1f6b99766..f00c6836add2828e84578f611a9e166a3c89b830 100644 (file)
@@ -41,6 +41,11 @@ uint8_t *make_tap_onion_key_crosscert(const crypto_pk_t *onion_key,
 
 int log_cert_expiration(void);
 int load_ed_keys(const or_options_t *options, time_t now);
+int load_family_id_keys(const or_options_t *options,
+                        const networkstatus_t *ns);
+int create_family_id_key(const char *fname);
+void warn_about_family_id_config(const or_options_t *options,
+                                 const networkstatus_t *ns);
 int should_make_new_ed_keys(const or_options_t *options, const time_t now);
 
 int generate_ed_link_cert(const or_options_t *options, time_t now, int force);
@@ -122,13 +127,20 @@ make_tap_onion_key_crosscert(const crypto_pk_t *onion_key,
  * CMD_KEYGEN. */
 #define load_ed_keys(x,y)                                                \
   (puts("Not available: Tor has been compiled without relay support"), 0)
+#define load_family_id_keys(x,y)                                         \
+  (puts("Not available: Tor has been compiled without relay support"), 0)
 
 #endif /* defined(HAVE_MODULE_RELAY) */
 
 #ifdef TOR_UNIT_TESTS
 const ed25519_keypair_t *get_master_identity_keypair(void);
 void init_mock_ed_keys(const crypto_pk_t *rsa_identity_key);
-void set_mock_family_id_keys(smartlist_t *keys);
+#endif
+
+#ifdef ROUTERKEYS_PRIVATE
+STATIC void set_family_id_keys(smartlist_t *keys);
+STATIC bool is_family_key_fname(const char *fname);
+STATIC int load_family_id_keys_impl(const char *keydir);
 #endif
 
 #endif /* !defined(TOR_ROUTERKEYS_H) */
index ebb39759085ab6a91adaccc386b4cfabe074405a..a9437469009300c2021015bdd1b721508de9593e 100644 (file)
@@ -21,6 +21,7 @@
 #define RELAY_PRIVATE
 #define ROUTERLIST_PRIVATE
 #define ROUTER_PRIVATE
+#define ROUTERKEYS_PRIVATE
 #define ROUTERPARSE_PRIVATE
 #define UNPARSEABLE_PRIVATE
 #define VOTEFLAGS_PRIVATE
@@ -877,7 +878,7 @@ test_dir_formats_rsa_ed25519(void *arg)
     smartlist_t *family_keys = smartlist_new();
     smartlist_add(family_keys, tor_memdup(&family_1, sizeof(family_1)));
     smartlist_add(family_keys, tor_memdup(&family_2, sizeof(family_2)));
-    set_mock_family_id_keys(family_keys); // takes ownership.
+    set_family_id_keys(family_keys); // takes ownership.
   }
 
   buf = router_dump_router_to_string(r2, r2->identity_pkey,
index 1ff05d57c3b91b4abb1829092769b8c92ca33f58..15f0d56cd2c0b3aa898ccea16f7e6c15fcd23c35 100644 (file)
@@ -5,6 +5,7 @@
 
 #include "orconfig.h"
 #define ROUTER_PRIVATE
+#define ROUTERKEYS_PRIVATE
 #include "core/or/or.h"
 #include "app/config/config.h"
 #include "feature/relay/router.h"
@@ -735,6 +736,76 @@ test_routerkeys_rsa_ed_crosscert(void *arg)
   tor_free(cc);
 }
 
+static void
+test_routerkeys_family_key_fname(void *arg)
+{
+  (void)arg;
+
+  tt_assert(is_family_key_fname("secret_family_key"));
+  tt_assert(is_family_key_fname("secret_family_key.1"));
+  tt_assert(is_family_key_fname("secret_family_key.413"));
+  tt_assert(! is_family_key_fname("secret_family_key.413x"));
+  tt_assert(! is_family_key_fname("secret_family_key1"));
+  tt_assert(! is_family_key_fname("secret_family_key.hello"));
+  tt_assert(! is_family_key_fname("secret_family_key.-1"));
+  tt_assert(! is_family_key_fname("fun_with_filenames.1"));
+  tt_assert(! is_family_key_fname("fun_with_filenames"));
+
+ done:
+  ;
+}
+
+static void
+test_routerkeys_load_family_keys(void *arg)
+{
+  (void) arg;
+  char *dname = tor_strdup(get_fname_rnd("fkeys"));
+  char *fname = NULL;
+
+#ifdef _WIN32
+  tt_assert(0==mkdir(dname));
+#else
+  tt_assert(0==mkdir(dname,0700));
+#endif
+
+  // Not a family key, will be ignored
+  tor_asprintf(&fname, "%s"PATH_SEPARATOR"junk.1", dname);
+  write_str_to_file(fname, "hello world", 0);
+  tor_free(fname);
+
+  tt_int_op(0, OP_EQ, load_family_id_keys_impl(dname));
+  tt_int_op(0, OP_EQ, smartlist_len(get_current_family_id_keys()));
+
+  // Create a family key; make sure we can load it.
+  tor_asprintf(&fname, "%s"PATH_SEPARATOR"secret_family_key.12", dname);
+  tt_int_op(0, OP_EQ, create_family_id_key(fname));
+  tor_free(fname);
+
+  tt_int_op(0, OP_EQ, load_family_id_keys_impl(dname));
+  tt_int_op(1, OP_EQ, smartlist_len(get_current_family_id_keys()));
+
+  //Try a second key.
+  tor_asprintf(&fname, "%s"PATH_SEPARATOR"secret_family_key.413", dname);
+  tt_int_op(0, OP_EQ, create_family_id_key(fname));
+  tor_free(fname);
+
+  tt_int_op(0, OP_EQ, load_family_id_keys_impl(dname));
+  tt_int_op(2, OP_EQ, smartlist_len(get_current_family_id_keys()));
+
+  // Make a junk key, make sure it causes an error.
+  tor_asprintf(&fname, "%s"PATH_SEPARATOR"secret_family_key.6", dname);
+  write_str_to_file(fname, "hello world", 0);
+  tor_free(fname);
+
+  tt_int_op(-1, OP_EQ, load_family_id_keys_impl(dname));
+  // keys unchanged
+  tt_int_op(2, OP_EQ, smartlist_len(get_current_family_id_keys()));
+
+ done:
+  tor_free(dname);
+  tor_free(fname);
+}
+
 #define TEST(name, flags)                                       \
   { #name , test_routerkeys_ ## name, (flags), NULL, NULL }
 
@@ -749,5 +820,7 @@ struct testcase_t routerkeys_tests[] = {
   TEST(cross_certify_ntor, 0),
   TEST(cross_certify_tap, 0),
   TEST(rsa_ed_crosscert, 0),
+  TEST(family_key_fname, 0),
+  TEST(load_family_keys, TT_FORK),
   END_OF_TESTCASES
 };