diff --git a/changes/bug22460_case1 b/changes/bug22460_case1 new file mode 100644 index 0000000000..cfe78ad791 --- /dev/null +++ b/changes/bug22460_case1 @@ -0,0 +1,16 @@ + o Major bugfixes (relays, key management): + - Regenerate link and authentication certificates whenever the key that + signs them changes; also, regenerate link certificates whenever the + signed key changes. Previously, these processes were only weakly + coupled, and we relays could (for minutes to hours) wind up with an + inconsistent set of keys and certificates, which other relays + would not accept. Fixes two cases of bug 22460; bugfix on + 0.3.0.1-alpha. + - When sending an Ed25519 signing->link certificate in a CERTS cell, + send the certificate that matches the x509 certificate that we used + on the TLS connection. Previously, there was a race condition if + the TLS context rotated after we began the TLS handshake but + before we sent the CERTS cell. Fixes a case of bug 22460; bugfix + on 0.3.0.1-alpha. + + diff --git a/src/or/connection_or.c b/src/or/connection_or.c index b9ac9b2c5d..1e2d731f53 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -1855,6 +1855,9 @@ connection_init_or_handshake_state(or_connection_t *conn, int started_here) s->started_here = started_here ? 1 : 0; s->digest_sent_data = 1; s->digest_received_data = 1; + if (! started_here && get_current_link_cert_cert()) { + s->own_link_cert = tor_cert_dup(get_current_link_cert_cert()); + } s->certs = or_handshake_certs_new(); s->certs->started_here = s->started_here; return 0; @@ -1869,6 +1872,7 @@ or_handshake_state_free(or_handshake_state_t *state) crypto_digest_free(state->digest_sent); crypto_digest_free(state->digest_received); or_handshake_certs_free(state->certs); + tor_cert_free(state->own_link_cert); memwipe(state, 0xBE, sizeof(or_handshake_state_t)); tor_free(state); } @@ -2234,7 +2238,8 @@ add_certs_cell_cert_helper(certs_cell_t *certs_cell, /** Add an encoded X509 cert (stored as cert_len bytes at * cert_encoded) to the trunnel certs_cell_t object that we are - * building in certs_cell. Set its type field to cert_type. */ + * building in certs_cell. Set its type field to cert_type. + * (If cert is NULL, take no action.) */ static void add_x509_cert(certs_cell_t *certs_cell, uint8_t cert_type, @@ -2252,7 +2257,7 @@ add_x509_cert(certs_cell_t *certs_cell, /** Add an Ed25519 cert from cert to the trunnel certs_cell_t object * that we are building in certs_cell. Set its type field to - * cert_type. */ + * cert_type. (If cert is NULL, take no action.) */ static void add_ed25519_cert(certs_cell_t *certs_cell, uint8_t cert_type, @@ -2315,9 +2320,10 @@ connection_or_send_certs_cell(or_connection_t *conn) CERTTYPE_ED_ID_SIGN, get_master_signing_key_cert()); if (conn_in_server_mode) { + tor_assert_nonfatal(conn->handshake_state->own_link_cert); add_ed25519_cert(certs_cell, CERTTYPE_ED_SIGN_LINK, - get_current_link_cert_cert()); + conn->handshake_state->own_link_cert); } else { add_ed25519_cert(certs_cell, CERTTYPE_ED_SIGN_AUTH, diff --git a/src/or/main.c b/src/or/main.c index bc7b3db2b9..3139381f30 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -1506,8 +1506,9 @@ check_ed_keys_callback(time_t now, const or_options_t *options) { if (server_mode(options)) { if (should_make_new_ed_keys(options, now)) { - if (load_ed_keys(options, now) < 0 || - generate_ed_link_cert(options, now)) { + int new_signing_key = load_ed_keys(options, now); + if (new_signing_key < 0 || + generate_ed_link_cert(options, now, new_signing_key > 0)) { log_err(LD_OR, "Unable to update Ed25519 keys! Exiting."); tor_cleanup(); exit(0); @@ -1559,6 +1560,11 @@ rotate_x509_certificate_callback(time_t now, const or_options_t *options) log_err(LD_BUG, "Error reinitializing TLS context"); tor_assert_unreached(); } + if (generate_ed_link_cert(options, now, 1)) { + log_err(LD_OR, "Unable to update Ed25519->TLS link certificate for " + "new TLS context."); + tor_assert_unreached(); + } /* We also make sure to rotate the TLS connections themselves if they've * been up for too long -- but that's done via is_bad_for_new_circs in @@ -2298,8 +2304,9 @@ do_hup(void) /* Maybe we've been given a new ed25519 key or certificate? */ time_t now = approx_time(); - if (load_ed_keys(options, now) < 0 || - generate_ed_link_cert(options, now)) { + int new_signing_key = load_ed_keys(options, now); + if (new_signing_key < 0 || + generate_ed_link_cert(options, now, new_signing_key > 0)) { log_warn(LD_OR, "Problem reloading Ed25519 keys; still using old keys."); } @@ -3627,7 +3634,7 @@ tor_main(int argc, char *argv[]) result = do_main_loop(); break; case CMD_KEYGEN: - result = load_ed_keys(get_options(), time(NULL)); + result = load_ed_keys(get_options(), time(NULL)) < 0; break; case CMD_LIST_FINGERPRINT: result = do_list_fingerprint(); diff --git a/src/or/or.h b/src/or/or.h index 0db9f23604..50e6e3e71b 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -1449,6 +1449,12 @@ typedef struct or_handshake_state_t { /* True iff we have sent a netinfo cell */ unsigned int sent_netinfo : 1; + /** The signing->ed25519 link certificate corresponding to the x509 + * certificate we used on the TLS connection (if this is a server-side + * connection). We make a copy of this here to prevent a race condition + * caused by TLS context rotation. */ + struct tor_cert_st *own_link_cert; + /** True iff we should feed outgoing cells into digest_sent and * digest_received respectively. * diff --git a/src/or/router.c b/src/or/router.c index e4fa72a283..f6b03cde27 100644 --- a/src/or/router.c +++ b/src/or/router.c @@ -906,7 +906,8 @@ init_keys(void) } /* 1d. Load all ed25519 keys */ - if (load_ed_keys(options,now) < 0) + const int new_signing_key = load_ed_keys(options,now); + if (new_signing_key < 0) return -1; /* 2. Read onion key. Make it if none is found. */ @@ -976,7 +977,7 @@ init_keys(void) /* 3b. Get an ed25519 link certificate. Note that we need to do this * after we set up the TLS context */ - if (generate_ed_link_cert(options, now) < 0) { + if (generate_ed_link_cert(options, now, new_signing_key > 0) < 0) { log_err(LD_GENERAL,"Couldn't make link cert"); return -1; } diff --git a/src/or/routerkeys.c b/src/or/routerkeys.c index 611ac91397..1eb44db855 100644 --- a/src/or/routerkeys.c +++ b/src/or/routerkeys.c @@ -673,6 +673,9 @@ static time_t rsa_ed_crosscert_expiration = 0; /** * Running as a server: load, reload, or refresh our ed25519 keys and * certificates, creating and saving new ones as needed. + * + * Return -1 on failure; 0 on success if the signing key was not replaced; + * and 1 on success if the signing key was replaced. */ int load_ed_keys(const or_options_t *options, time_t now) @@ -685,6 +688,7 @@ load_ed_keys(const or_options_t *options, time_t now) const tor_cert_t *check_signing_cert = NULL; tor_cert_t *sign_cert = NULL; tor_cert_t *auth_cert = NULL; + int signing_key_changed = 0; #define FAIL(msg) do { \ log_warn(LD_OR, (msg)); \ @@ -722,7 +726,23 @@ load_ed_keys(const or_options_t *options, time_t now) use_signing = sign; } + if (use_signing) { + /* We loaded a signing key with its certificate. */ + if (! master_signing_key) { + /* We didn't know one before! */ + signing_key_changed = 1; + } else if (! ed25519_pubkey_eq(&use_signing->pubkey, + &master_signing_key->pubkey) || + ! tor_memeq(use_signing->seckey.seckey, + master_signing_key->seckey.seckey, + ED25519_SECKEY_LEN)) { + /* We loaded a different signing key than the one we knew before. */ + signing_key_changed = 1; + } + } + if (!use_signing && master_signing_key) { + /* We couldn't load a signing key, but we already had one loaded */ check_signing_cert = signing_key_cert; use_signing = master_signing_key; } @@ -882,6 +902,7 @@ load_ed_keys(const or_options_t *options, time_t now) if (!sign) FAIL("Missing signing key"); use_signing = sign; + signing_key_changed = 1; tor_assert(sign_cert->signing_key_included); tor_assert(ed25519_pubkey_eq(&sign_cert->signing_key, &id->pubkey)); @@ -918,6 +939,7 @@ load_ed_keys(const or_options_t *options, time_t now) } if (!current_auth_key || + signing_key_changed || EXPIRES_SOON(auth_key_cert, options->TestingAuthKeySlop)) { auth = ed_key_new(use_signing, INIT_ED_KEY_NEEDCERT, now, @@ -945,7 +967,7 @@ load_ed_keys(const or_options_t *options, time_t now) SET_CERT(auth_key_cert, auth_cert); } - return 0; + return signing_key_changed; err: ed25519_keypair_free(id); ed25519_keypair_free(sign); @@ -959,16 +981,18 @@ load_ed_keys(const or_options_t *options, time_t now) * Retrieve our currently-in-use Ed25519 link certificate and id certificate, * and, if they would expire soon (based on the time now, generate new * certificates (without embedding the public part of the signing key inside). + * If force is true, always generate a new certificate. * - * The signed_key from the expiring certificate will be used to sign the new - * key within newly generated X509 certificate. + * The signed_key from the current id->signing certificate will be used to + * sign the new key within newly generated X509 certificate. * * Returns -1 upon error. Otherwise, returns 0 upon success (either when the * current certificate is still valid, or when a new certificate was * successfully generated). */ int -generate_ed_link_cert(const or_options_t *options, time_t now) +generate_ed_link_cert(const or_options_t *options, time_t now, + int force) { const tor_x509_cert_t *link_ = NULL, *id = NULL; tor_cert_t *link_cert = NULL; @@ -980,7 +1004,8 @@ generate_ed_link_cert(const or_options_t *options, time_t now) const common_digests_t *digests = tor_x509_cert_get_cert_digests(link_); - if (link_cert_cert && + if (force == 0 && + link_cert_cert && ! EXPIRES_SOON(link_cert_cert, options->TestingLinkKeySlop) && fast_memeq(digests->d[DIGEST_SHA256], link_cert_cert->signed_key.pubkey, DIGEST256_LEN)) { @@ -1082,7 +1107,7 @@ init_mock_ed_keys(const crypto_pk_t *rsa_identity_key) MAKECERT(auth_key_cert, master_signing_key, current_auth_key, CERT_TYPE_SIGNING_AUTH, 0); - if (generate_ed_link_cert(get_options(), time(NULL)) < 0) { + if (generate_ed_link_cert(get_options(), time(NULL), 0) < 0) { log_warn(LD_BUG, "Couldn't make link certificate"); goto err; } diff --git a/src/or/routerkeys.h b/src/or/routerkeys.h index d2027f4bbe..845abb4c70 100644 --- a/src/or/routerkeys.h +++ b/src/or/routerkeys.h @@ -66,7 +66,7 @@ MOCK_DECL(int, check_tap_onion_key_crosscert,(const uint8_t *crosscert, int load_ed_keys(const or_options_t *options, time_t now); 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 generate_ed_link_cert(const or_options_t *options, time_t now, int force); int read_encrypted_secret_key(ed25519_secret_key_t *out, const char *fname); diff --git a/src/test/test_link_handshake.c b/src/test/test_link_handshake.c index b3e854ef76..ade7cf6998 100644 --- a/src/test/test_link_handshake.c +++ b/src/test/test_link_handshake.c @@ -911,6 +911,11 @@ test_link_handshake_send_authchallenge(void *arg) or_connection_t *c1 = or_connection_new(CONN_TYPE_OR, AF_INET); var_cell_t *cell1=NULL, *cell2=NULL; + crypto_pk_t *rsa0 = pk_generate(0), *rsa1 = pk_generate(1); + tt_int_op(tor_tls_context_init(TOR_TLS_CTX_IS_PUBLIC_SERVER, + rsa0, rsa1, 86400), ==, 0); + init_mock_ed_keys(rsa0); + MOCK(connection_or_write_var_cell_to_buf, mock_write_var_cell); tt_int_op(connection_init_or_handshake_state(c1, 0), ==, 0); @@ -936,6 +941,8 @@ test_link_handshake_send_authchallenge(void *arg) connection_free_(TO_CONN(c1)); tor_free(cell1); tor_free(cell2); + crypto_pk_free(rsa0); + crypto_pk_free(rsa1); } typedef struct authchallenge_data_s { diff --git a/src/test/test_routerkeys.c b/src/test/test_routerkeys.c index 64692d28a0..13059267ac 100644 --- a/src/test/test_routerkeys.c +++ b/src/test/test_routerkeys.c @@ -450,8 +450,8 @@ test_routerkeys_ed_keys_init_all(void *arg) options->DataDirectory = dir; - tt_int_op(0, ==, load_ed_keys(options, now)); - tt_int_op(0, ==, generate_ed_link_cert(options, now)); + tt_int_op(1, ==, load_ed_keys(options, now)); + tt_int_op(0, ==, generate_ed_link_cert(options, now, 0)); tt_assert(get_master_identity_key()); tt_assert(get_master_identity_key()); tt_assert(get_master_signing_keypair()); @@ -466,7 +466,7 @@ test_routerkeys_ed_keys_init_all(void *arg) /* Call load_ed_keys again, but nothing has changed. */ tt_int_op(0, ==, load_ed_keys(options, now)); - tt_int_op(0, ==, generate_ed_link_cert(options, now)); + tt_int_op(0, ==, generate_ed_link_cert(options, now, 0)); tt_mem_op(&id, ==, get_master_identity_key(), sizeof(id)); tt_mem_op(&sign, ==, get_master_signing_keypair(), sizeof(sign)); tt_mem_op(&auth, ==, get_current_auth_keypair(), sizeof(auth)); @@ -474,8 +474,8 @@ test_routerkeys_ed_keys_init_all(void *arg) /* Force a reload: we make new link/auth keys. */ routerkeys_free_all(); - tt_int_op(0, ==, load_ed_keys(options, now)); - tt_int_op(0, ==, generate_ed_link_cert(options, now)); + tt_int_op(1, ==, load_ed_keys(options, now)); + tt_int_op(0, ==, generate_ed_link_cert(options, now, 0)); tt_mem_op(&id, ==, get_master_identity_key(), sizeof(id)); tt_mem_op(&sign, ==, get_master_signing_keypair(), sizeof(sign)); tt_assert(tor_cert_eq(link_cert, get_current_link_cert_cert())); @@ -489,7 +489,7 @@ test_routerkeys_ed_keys_init_all(void *arg) /* Force a link/auth-key regeneration by advancing time. */ tt_int_op(0, ==, load_ed_keys(options, now+3*86400)); - tt_int_op(0, ==, generate_ed_link_cert(options, now+3*86400)); + tt_int_op(0, ==, generate_ed_link_cert(options, now+3*86400, 0)); tt_mem_op(&id, ==, get_master_identity_key(), sizeof(id)); tt_mem_op(&sign, ==, get_master_signing_keypair(), sizeof(sign)); tt_assert(! tor_cert_eq(link_cert, get_current_link_cert_cert())); @@ -502,8 +502,8 @@ test_routerkeys_ed_keys_init_all(void *arg) memcpy(&auth, get_current_auth_keypair(), sizeof(auth)); /* Force a signing-key regeneration by advancing time. */ - tt_int_op(0, ==, load_ed_keys(options, now+100*86400)); - tt_int_op(0, ==, generate_ed_link_cert(options, now+100*86400)); + tt_int_op(1, ==, load_ed_keys(options, now+100*86400)); + tt_int_op(0, ==, generate_ed_link_cert(options, now+100*86400, 0)); tt_mem_op(&id, ==, get_master_identity_key(), sizeof(id)); tt_mem_op(&sign, !=, get_master_signing_keypair(), sizeof(sign)); tt_assert(! tor_cert_eq(link_cert, get_current_link_cert_cert())); @@ -520,8 +520,8 @@ test_routerkeys_ed_keys_init_all(void *arg) routerkeys_free_all(); unlink(get_fname("test_ed_keys_init_all/keys/" "ed25519_master_id_secret_key")); - tt_int_op(0, ==, load_ed_keys(options, now)); - tt_int_op(0, ==, generate_ed_link_cert(options, now)); + tt_int_op(1, ==, load_ed_keys(options, now)); + tt_int_op(0, ==, generate_ed_link_cert(options, now, 0)); tt_mem_op(&id, ==, get_master_identity_key(), sizeof(id)); tt_mem_op(&sign, ==, get_master_signing_keypair(), sizeof(sign)); tt_assert(! tor_cert_eq(link_cert, get_current_link_cert_cert())); diff --git a/src/test/test_shared_random.c b/src/test/test_shared_random.c index d511f163e3..026a0f3825 100644 --- a/src/test/test_shared_random.c +++ b/src/test/test_shared_random.c @@ -48,7 +48,7 @@ init_authority_state(void) mock_cert = authority_cert_parse_from_string(AUTHORITY_CERT_1, NULL); tt_assert(mock_cert); options->AuthoritativeDir = 1; - tt_int_op(0, ==, load_ed_keys(options, time(NULL))); + tt_int_op(load_ed_keys(options, time(NULL)), OP_GE, 0); sr_state_init(0, 0); /* It's possible a commit has been generated in our state depending on * the phase we are currently in which uses "now" as the starting @@ -286,7 +286,7 @@ test_sr_commit(void *arg) tt_assert(auth_cert); options->AuthoritativeDir = 1; - tt_int_op(0, ==, load_ed_keys(options, now)); + tt_int_op(load_ed_keys(options, time(NULL)), OP_GE, 0); } /* Generate our commit object and validate it has the appropriate field