]> git.ipfire.org Git - thirdparty/tor.git/commitdiff
Update interface for happy families
authorNick Mathewson <nickm@torproject.org>
Tue, 18 Feb 2025 14:53:59 +0000 (09:53 -0500)
committerNick Mathewson <nickm@torproject.org>
Thu, 6 Mar 2025 14:41:54 +0000 (09:41 -0500)
I'm hoping that this design will be a bit more ergonomic
than my first idea; the improvement here is that you have to list the family
IDs you expect in your torrc.  This way, there's a cross-check between the
actual keys we use and your configuration for them.

doc/man/tor.1.txt
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/routerkeys.c
src/feature/relay/routerkeys.h
src/test/test_routerkeys.c

index ea56c83879a63e07a806ab0dce3e6b97a50557a0..39e270ee4b16af3d0e381ef52142b70961387336 100644 (file)
@@ -168,12 +168,11 @@ The following options in this section are only recognized on the
     make sure that they are owned by the user actually running the Tor
     daemon on your system.
 
-[[opt-keygen-family]] **`--keygen-family`** __filename__::
-    Generate a new family ID key in `filename`.
+[[opt-keygen-family]] **`--keygen-family`** __basename__::
+    Generate a new family ID key in __basename__`.secret_family_key`.
     To use this key, install it on every relay in your family.
-    (Put it in the relay's `KeyDirectory`, with a filename like
-    `secret_family_key`, `secret_family_key.1`, `secret_family_key.2`.)
-    Then enable the UseFamilyKeys option on your relays.
+    (Put it in the relay's `KeyDirectory`.)
+    Then enable the corresponding FamilyID option on your relays.
     See (XXXX INSERT URL HERE) for more information.
 
 **`--passphrase-fd`** __FILEDES__::
@@ -2480,9 +2479,14 @@ is non-zero):
     Note: do not use MyFamily when configuring your Tor instance as a
     bridge.
 
-[[UseFamilyKeys]] **UseFamilyKeys** **0**|**1**::
-    If 1, configure this relay to be part of a family identified by a shared
-    secret family key.  Family keys are generated with `--keygen-family`.
+[[FamilyId]] **FamilyId** __ident__::
+    Configure this relay to be part of a family
+    identified by a shared secret family key with the given key identity.
+    A corresponding family key must be stored in the relay's key directory.
+    This option can appear multiple times.
+    Family keys are generated with "--keygen-family";
+    this also generates the value you should use in the __ident__ field
+    in a file ending with ".public\_family\_id".
     For information on generating and installing a family
     key, see (XXXX INSERT URL HERE).
       +
@@ -4060,7 +4064,7 @@ __KeyDirectory__/**`secret_onion_key_ntor`** and **`secret_onion_key_ntor.old`**
     generated key, which the relay uses to handle any requests that were made
     by clients that didn't have the new one.
 
-__KeyDirectory__/**`secret_family_key`**, **`secret_family_key.`**.__N__::
+__KeyDirectory__/__keyname__**`.secret_family_key`**::
     A relay family's family identity key.
     Used to prove membership in a relay family.
     See (XXXX INSERT URL HERE) for more information.
index cdb31e4039b77c215d100fe59a27cabb66aa581e..4a09cef3fb5c082951fdcb7f1fd8a03dfc288fcc 100644 (file)
@@ -470,6 +470,7 @@ static const config_var_t option_vars_[] = {
   V(UseDefaultFallbackDirs,      BOOL,     "1"),
 
   OBSOLETE("FallbackNetworkstatusFile"),
+  VAR("FamilyId",                LINELIST, FamilyId_lines,   NULL),
   V(FascistFirewall,             BOOL,     "0"),
   V(FirewallPorts,               CSV,      ""),
   OBSOLETE("FastFirstHopPK"),
@@ -572,7 +573,6 @@ 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"),
@@ -1050,6 +1050,11 @@ options_clear_cb(const config_mgr_t *mgr, void *opts)
   tor_free(options->command_arg);
   tor_free(options->master_key_fname);
   config_free_lines(options->MyFamily);
+  if (options->FamilyIds) {
+    SMARTLIST_FOREACH(options->FamilyIds,
+                      ed25519_public_key_t *, k, tor_free(k));
+    smartlist_free(options->FamilyIds);
+  }
 }
 
 /** Release all memory allocated in options
index 2c0b9ca58a42716917aebae47d5c97796a0e6b65..698b954ef0b51ae85d2e8ba7187b4440afa9441a 100644 (file)
@@ -493,8 +493,10 @@ 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
+  struct config_line_t *FamilyId_lines; /**< If set, IDs for family keys to use
                       * to certify this OR's membership. */
+  struct smartlist_t *FamilyIds; /**< FamilyIds, parsed and converted
+                                  * to a list of ed25519_public_key_t */
   struct config_line_t *NodeFamilies; /**< List of config lines for
                                 * node families */
   /** List of parsed NodeFamilies values. */
index 3dc34abce7ace109cc709b56c06334ef4f18935e..ec1571f0ba46bef39d2d266b750a74dd16684a14 100644 (file)
@@ -830,6 +830,32 @@ do_dump_config(void)
   return 0;
 }
 
+/** Implement --keygen-family; create a family ID key and write it to a file.
+ */
+static int
+do_keygen_family(const char *fname_base)
+{
+  ed25519_public_key_t pk;
+  char *fname = NULL;
+  int r = -1;
+
+  if (BUG(!fname_base))
+    goto done;
+
+  tor_asprintf(&fname, "%s.secret_family_key", fname_base);
+
+  if (create_family_id_key(fname, &pk) < 0)
+    goto done;
+
+  printf("# Generated %s\n", fname);
+  printf("FamilyId %s\n", ed25519_fmt(&pk));
+  r = 0;
+
+ done:
+  tor_free(fname);
+  return r;
+}
+
 static void
 init_addrinfo(void)
 {
@@ -1388,7 +1414,7 @@ tor_run_main(const tor_main_configuration_t *tor_cfg)
     result = load_ed_keys(get_options(), time(NULL)) < 0;
     break;
   case CMD_KEYGEN_FAMILY:
-    result = create_family_id_key(get_options()->command_arg);
+    result = do_keygen_family(get_options()->command_arg);
     break;
   case CMD_KEY_EXPIRATION:
     init_keys();
index fd965a293b5d24d2ac5882e2888ad09438d17f8e..bc5fab5f0321a2ea3e6280212d5dc16ce52ca9ad 100644 (file)
@@ -21,6 +21,7 @@
 #include "lib/meminfo/meminfo.h"
 #include "lib/osinfo/uname.h"
 #include "lib/process/setuid.h"
+#include "lib/crypt_ops/crypto_format.h"
 
 /* Required for dirinfo_type_t in or_options_t */
 #include "core/or/or.h"
@@ -1180,6 +1181,19 @@ options_validate_relay_mode(const or_options_t *old_options,
                               options->MyFamily_lines, "MyFamily", msg))
     return -1;
 
+  if (options->FamilyId_lines) {
+    options->FamilyIds = smartlist_new();
+    config_line_t *line;
+    for (line = options->FamilyId_lines; line; line = line->next) {
+      ed25519_public_key_t pk;
+      if (ed25519_public_from_base64(&pk, line->value) < 0) {
+        tor_asprintf(msg, "Invalid FamilyId %s", line->value);
+        return -1;
+      }
+      smartlist_add(options->FamilyIds, tor_memdup(&pk, sizeof(pk)));
+    }
+  }
+
   if (options->ConstrainedSockets) {
     if (options->DirPort_set) {
       /* Providing cached directory entries while system TCP buffers are scarce
@@ -1274,7 +1288,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_LINELIST(FamilyId_lines);
   YES_IF_CHANGED_STRING(AccountingStart);
   YES_IF_CHANGED_INT(AccountingMax);
   YES_IF_CHANGED_INT(AccountingRule);
index cab2b319fe20115e072fcd435e23895c511c56b1..755c19c603c53adc43f8bb942ef1bd6f47bc1997 100644 (file)
@@ -27,6 +27,7 @@
 #include "feature/dirauth/dirvote.h"
 
 #include "lib/crypt_ops/crypto_util.h"
+#include "lib/crypt_ops/crypto_format.h"
 #include "lib/tls/tortls.h"
 #include "lib/tls/x509.h"
 
@@ -682,9 +683,9 @@ get_current_auth_key_cert(void)
 }
 
 /**
- * Prefix for the filename in which we expect to find a family ID key.
+ * Suffix for the filenames in which we expect to find a family ID key.
  */
-#define FAMILY_KEY_FNAME "secret_family_key"
+#define FAMILY_KEY_SUFFIX ".secret_family_key"
 
 /**
  * Return true if `fname` is a possible filename of a family ID key.
@@ -695,14 +696,32 @@ get_current_auth_key_cert(void)
 STATIC bool
 is_family_key_fname(const char *fname)
 {
-  if (0 == strcmp(fname, FAMILY_KEY_FNAME))
-    return true;
+  return 0 == strcmpend(fname, FAMILY_KEY_SUFFIX);
+}
 
-  unsigned num;
-  char ch;
-  if (tor_sscanf(fname, FAMILY_KEY_FNAME".%u%c", &num, &ch) == 1)
-    return true;
+/** Return true if `id` is configured in `options`. */
+static bool
+family_key_id_is_expected(const or_options_t *options,
+                          const ed25519_public_key_t *id)
+{
+  SMARTLIST_FOREACH(options->FamilyIds, const ed25519_public_key_t *, k, {
+      if (ed25519_pubkey_eq(k, id))
+        return true;
+    });
+  return false;
+}
 
+/** Return true if the key for `id` has been loaded. */
+static bool
+family_key_is_present(const ed25519_public_key_t *id)
+{
+  if (!family_id_keys)
+    return false;
+
+  SMARTLIST_FOREACH(family_id_keys, const ed25519_keypair_t *, kp, {
+      if (ed25519_pubkey_eq(&kp->pubkey, id))
+        return true;
+    });
   return false;
 }
 
@@ -716,9 +735,10 @@ is_family_key_fname(const char *fname)
  * family_id_keys.
  */
 STATIC int
-load_family_id_keys_impl(const char *keydir)
+load_family_id_keys_impl(const or_options_t *options,
+                         const char *keydir)
 {
-  if (BUG(!keydir))
+  if (BUG(!options) || BUG(!keydir))
     return -1;
 
   smartlist_t *files = tor_listdir(keydir);
@@ -756,8 +776,14 @@ load_family_id_keys_impl(const char *keydir)
       goto end;
     }
 
-    smartlist_add(new_keys, kp_tmp);
-    kp_tmp = NULL; // prevent double-free
+    if (family_key_id_is_expected(options, &kp_tmp->pubkey)) {
+      smartlist_add(new_keys, kp_tmp);
+      kp_tmp = NULL; // prevent double-free
+    } else {
+      log_warn(LD_OR, "Found secret family key in %s "
+               "with unexpected FamilyID %s",
+               fn_tmp, ed25519_fmt(&kp_tmp->pubkey));
+    }
 
     tor_free(fn_tmp);
     tor_free(tag_tmp);
@@ -784,9 +810,11 @@ load_family_id_keys_impl(const char *keydir)
 
 /**
  * Create a new family ID key, and store it in `fname`.
+ *
+ * If pk_out is provided, set it to the generated public key.
  **/
 int
-create_family_id_key(const char *fname)
+create_family_id_key(const char *fname, ed25519_public_key_t *pk_out)
 {
   int r = -1;
   ed25519_keypair_t *kp = tor_malloc_zero(sizeof(ed25519_keypair_t));
@@ -801,6 +829,10 @@ create_family_id_key(const char *fname)
     goto done;
   }
 
+  if (pk_out) {
+    ed25519_pubkey_copy(pk_out, &kp->pubkey);
+  }
+
   r = 0;
 
  done:
@@ -821,24 +853,24 @@ 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)
+  if (options->FamilyIds) {
+    if (load_family_id_keys_impl(options, 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()));
-    }
+    bool any_missing = false;
+    SMARTLIST_FOREACH_BEGIN(options->FamilyIds,
+                            const ed25519_public_key_t *, id) {
+        if (!family_key_is_present(id)) {
+          log_err(LD_OR, "No key was found for listed FamilyID %s",
+                  ed25519_fmt(id));
+          any_missing = true;
+        }
+    } SMARTLIST_FOREACH_END(id);
+    if (any_missing)
+      return -1;
+
+    log_info(LD_OR, "Found %d family ID keys",
+             smartlist_len(get_current_family_id_keys()));
   } else {
     set_family_id_keys(NULL);
   }
@@ -857,12 +889,12 @@ warn_about_family_id_config(const or_options_t *options,
   static int have_warned_absent_myfamily = 0;
   static int have_warned_absent_familykeys = 0;
 
-  if (options->UseFamilyKeys) {
+  if (options->FamilyIds) {
     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 "
+               "FamilyId was configured, but MyFamily was not. "
+               "FamilyId is good, but the Tor network still requires "
                "MyFamily while clients are migrating to use family "
                "keys instead.");
       have_warned_absent_myfamily = 1;
@@ -872,7 +904,7 @@ warn_about_family_id_config(const or_options_t *options,
         options->MyFamily &&
         ns && ns->consensus_method >= MIN_METHOD_FOR_FAMILY_IDS) {
       log_notice(LD_OR,
-                 "MyFamily was configured, but UseFamilyKeys was not. "
+                 "MyFamily was configured, but FamilyId was not. "
                  "It's a good time to start migrating your relays "
                  "to use family keys. "
                  "See (XXXX INSERT URL HERE) for instructions.");
index 70fc2ec225cf28f4ed46124456d43a3b7d43bd01..5095419a4798cd1cd21caec3218f596ce9e0a3e6 100644 (file)
@@ -43,7 +43,7 @@ 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);
+int create_family_id_key(const char *fname, ed25519_public_key_t *pk_out);
 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);
@@ -133,7 +133,7 @@ make_tap_onion_key_crosscert(const crypto_pk_t *onion_key,
   (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)
-#define create_family_id_key(x)                                         \
+#define create_family_id_key(x,y)                                      \
   (puts("Not available: Tor has been compiled without relay support"), -1)
 
 #endif /* defined(HAVE_MODULE_RELAY) */
@@ -146,7 +146,8 @@ void init_mock_ed_keys(const crypto_pk_t *rsa_identity_key);
 #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);
+STATIC int load_family_id_keys_impl(const or_options_t *options,
+                                    const char *keydir);
 #endif
 
 #endif /* !defined(TOR_ROUTERKEYS_H) */
index 15f0d56cd2c0b3aa898ccea16f7e6c15fcd23c35..7e4b4edde1d7394c5f9939ca5d2132cf80d42579 100644 (file)
@@ -741,15 +741,11 @@ 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"));
+  tt_assert(is_family_key_fname("hello.secret_family_key"));
+  tt_assert(is_family_key_fname("xyzzy.secret_family_key"));
+  tt_assert(is_family_key_fname("909.secret_family_key"));
+  tt_assert(! is_family_key_fname("zzz.secret_family_key~"));
+  tt_assert(! is_family_key_fname("secret_family_key"));
 
  done:
   ;
@@ -761,6 +757,8 @@ test_routerkeys_load_family_keys(void *arg)
   (void) arg;
   char *dname = tor_strdup(get_fname_rnd("fkeys"));
   char *fname = NULL;
+  or_options_t *options = get_options_mutable();
+  ed25519_public_key_t pubkey;
 
 #ifdef _WIN32
   tt_assert(0==mkdir(dname));
@@ -768,36 +766,49 @@ test_routerkeys_load_family_keys(void *arg)
   tt_assert(0==mkdir(dname,0700));
 #endif
 
+  options->FamilyIds = smartlist_new();
+
   // 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, load_family_id_keys_impl(options, 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_asprintf(&fname, "%s"PATH_SEPARATOR"cg.secret_family_key", dname);
+  tt_int_op(0, OP_EQ, create_family_id_key(fname, &pubkey));
   tor_free(fname);
+  smartlist_add(options->FamilyIds, tor_memdup(&pubkey, sizeof(pubkey)));
 
-  tt_int_op(0, OP_EQ, load_family_id_keys_impl(dname));
+  tt_int_op(0, OP_EQ, load_family_id_keys_impl(options, 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_asprintf(&fname, "%s"PATH_SEPARATOR"eb.secret_family_key", dname);
+  tt_int_op(0, OP_EQ, create_family_id_key(fname, &pubkey));
+  smartlist_add(options->FamilyIds, tor_memdup(&pubkey, sizeof(pubkey)));
+  tor_free(fname);
+
+  tt_int_op(0, OP_EQ, load_family_id_keys_impl(options, dname));
+  tt_int_op(2, OP_EQ, smartlist_len(get_current_family_id_keys()));
+
+  // Try an unlisted key, make sure it isn't loaded.
+  tor_asprintf(&fname, "%s"PATH_SEPARATOR"gt.secret_family_key", dname);
+  tt_int_op(0, OP_EQ, create_family_id_key(fname, &pubkey));
+  // Do not add to FamilyIDs here; we're leaving it unlisted.
   tor_free(fname);
 
-  tt_int_op(0, OP_EQ, load_family_id_keys_impl(dname));
+  tt_int_op(0, OP_EQ, load_family_id_keys_impl(options, 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);
+  tor_asprintf(&fname, "%s"PATH_SEPARATOR"xyz.secret_family_key", dname);
   write_str_to_file(fname, "hello world", 0);
   tor_free(fname);
 
-  tt_int_op(-1, OP_EQ, load_family_id_keys_impl(dname));
+  tt_int_op(-1, OP_EQ, load_family_id_keys_impl(options, dname));
   // keys unchanged
   tt_int_op(2, OP_EQ, smartlist_len(get_current_family_id_keys()));