From 4ec602d1ed5e2371c5cff0ae5bd0618d38c26275 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Thu, 13 Feb 2025 10:43:45 -0500 Subject: [PATCH] Happy families: finish implementing relay side. --- src/app/config/config.c | 1 + src/app/config/or_options_st.h | 2 + src/app/main/main.c | 5 + src/feature/relay/relay_config.c | 1 + src/feature/relay/router.c | 20 +++ src/feature/relay/router.h | 2 + src/feature/relay/routerkeys.c | 218 +++++++++++++++++++++++++++++-- src/feature/relay/routerkeys.h | 14 +- src/test/test_dir.c | 3 +- src/test/test_routerkeys.c | 73 +++++++++++ 10 files changed, 327 insertions(+), 12 deletions(-) diff --git a/src/app/config/config.c b/src/app/config/config.c index 102d1bbc04..a279507150 100644 --- a/src/app/config/config.c +++ b/src/app/config/config.c @@ -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"), diff --git a/src/app/config/or_options_st.h b/src/app/config/or_options_st.h index 624dc61bc5..2c0b9ca58a 100644 --- a/src/app/config/or_options_st.h +++ b/src/app/config/or_options_st.h @@ -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. */ diff --git a/src/app/main/main.c b/src/app/main/main.c index 6d05bd1f5e..705fa3abe9 100644 --- a/src/app/main/main.c +++ b/src/app/main/main.c @@ -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. */ diff --git a/src/feature/relay/relay_config.c b/src/feature/relay/relay_config.c index cc4d8ab420..fd965a293b 100644 --- a/src/feature/relay/relay_config.c +++ b/src/feature/relay/relay_config.c @@ -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); diff --git a/src/feature/relay/router.c b/src/feature/relay/router.c index 9c199df74b..d6898ba3cf 100644 --- a/src/feature/relay/router.c +++ b/src/feature/relay/router.c @@ -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 diff --git a/src/feature/relay/router.h b/src/feature/relay/router.h index f201fdbd63..37bcb3aec8 100644 --- a/src/feature/relay/router.h +++ b/src/feature/relay/router.h @@ -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); diff --git a/src/feature/relay/routerkeys.c b/src/feature/relay/routerkeys.c index 5bfc2ebe65..cab2b319fe 100644 --- a/src/feature/relay/routerkeys.c +++ b/src/feature/relay/routerkeys.c @@ -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); diff --git a/src/feature/relay/routerkeys.h b/src/feature/relay/routerkeys.h index f51f91b141..f00c6836ad 100644 --- a/src/feature/relay/routerkeys.h +++ b/src/feature/relay/routerkeys.h @@ -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) */ diff --git a/src/test/test_dir.c b/src/test/test_dir.c index ebb3975908..a943746900 100644 --- a/src/test/test_dir.c +++ b/src/test/test_dir.c @@ -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, diff --git a/src/test/test_routerkeys.c b/src/test/test_routerkeys.c index 1ff05d57c3..15f0d56cd2 100644 --- a/src/test/test_routerkeys.c +++ b/src/test/test_routerkeys.c @@ -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 }; -- 2.47.2