From befa56c72e612ac2cb234e7d924511645d6a0a93 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 12 May 2008 02:14:15 +0000 Subject: [PATCH] r19691@catbus: nickm | 2008-05-11 22:13:41 -0400 Backport: Implement a proposal to let a directory authority migrate its identity key without ceasing to sign consensuses. svn:r14585 --- ChangeLog | 6 ++ configure.in | 2 +- contrib/tor-mingw.nsi.in | 2 +- src/or/config.c | 1 + src/or/dirserv.c | 7 +++ src/or/dirvote.c | 126 +++++++++++++++++++++++++++++++-------- src/or/or.h | 7 +++ src/or/router.c | 71 ++++++++++++++++------ src/or/routerparse.c | 19 ++++++ src/win32/orconfig.h | 2 +- 10 files changed, 196 insertions(+), 47 deletions(-) diff --git a/ChangeLog b/ChangeLog index a098b3e0a..feab0f093 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +Changes in version 0.2.0.26-rc - 2008-05-?? + o Minor features: + - Add a new V3AuthUseLegacyKey option to make it easier for authorities + to change their identity keys if they have to. + + Changes in version 0.2.0.25-rc - 2008-04-23 Tor 0.2.0.25-rc makes Tor work again on OS X and certain BSDs. diff --git a/configure.in b/configure.in index 4967b1f4d..b423e4cec 100644 --- a/configure.in +++ b/configure.in @@ -5,7 +5,7 @@ dnl Copyright (c) 2007-2008, The Tor Project, Inc. dnl See LICENSE for licensing information AC_INIT -AM_INIT_AUTOMAKE(tor, 0.2.0.25-rc) +AM_INIT_AUTOMAKE(tor, 0.2.0.25-rc-dev) AM_CONFIG_HEADER(orconfig.h) AC_CANONICAL_HOST diff --git a/contrib/tor-mingw.nsi.in b/contrib/tor-mingw.nsi.in index 17032d524..4d0548c78 100644 --- a/contrib/tor-mingw.nsi.in +++ b/contrib/tor-mingw.nsi.in @@ -9,7 +9,7 @@ !include "FileFunc.nsh" !insertmacro GetParameters -!define VERSION "0.2.0.25-rc" +!define VERSION "0.2.0.25-rc-dev" !define INSTALLER "tor-${VERSION}-win32.exe" !define WEBSITE "https://www.torproject.org/" !define LICENSE "LICENSE" diff --git a/src/or/config.c b/src/or/config.c index 5d69cadf1..a413f9195 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -301,6 +301,7 @@ static config_var_t _option_vars[] = { V(V3AuthVoteDelay, INTERVAL, "5 minutes"), V(V3AuthDistDelay, INTERVAL, "5 minutes"), V(V3AuthNIntervalsValid, UINT, "3"), + V(V3AuthUseLegacyKey, BOOL, "0"), VAR("VersioningAuthoritativeDirectory",BOOL,VersioningAuthoritativeDir, "0"), V(VirtualAddrNetwork, STRING, "127.192.0.0/10"), V(WarnPlaintextPorts, CSV, "23,109,110,143"), diff --git a/src/or/dirserv.c b/src/or/dirserv.c index 81181f774..b6a47411b 100644 --- a/src/or/dirserv.c +++ b/src/or/dirserv.c @@ -2285,6 +2285,13 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_env_t *private_key, voter->or_port = options->ORPort; voter->contact = tor_strdup(contact); memcpy(voter->signing_key_digest, signing_key_digest, DIGEST_LEN); + if (options->V3AuthUseLegacyKey) { + authority_cert_t *c = get_my_v3_legacy_cert(); + if (c) { + crypto_pk_get_digest(c->identity_key, voter->legacy_id_digest); + } + } + v3_out->voters = smartlist_create(); smartlist_add(v3_out->voters, voter); v3_out->cert = authority_cert_dup(cert); diff --git a/src/or/dirvote.c b/src/or/dirvote.c index e4afacc61..683389341 100644 --- a/src/or/dirvote.c +++ b/src/or/dirvote.c @@ -105,7 +105,7 @@ format_networkstatus_vote(crypto_pk_env_t *private_signing_key, tor_snprintf(status, len, "network-status-version 3\n" "vote-status vote\n" - "consensus-methods 1 2\n" + "consensus-methods 1 2 3\n" "published %s\n" "valid-after %s\n" "fresh-until %s\n" @@ -125,6 +125,14 @@ format_networkstatus_vote(crypto_pk_env_t *private_signing_key, tor_free(flags); outp = status + strlen(status); endp = status + len; + + if (!tor_digest_is_zero(voter->legacy_id_digest)) { + char fpbuf[HEX_DIGEST_LEN+1]; + base16_encode(fpbuf, sizeof(fpbuf), voter->legacy_id_digest, DIGEST_LEN); + tor_snprintf(outp, endp-outp, "legacy-dir-key %s\n", fpbuf); + outp += strlen(outp); + } + tor_assert(outp + cert->cache_info.signed_descriptor_len < endp); memcpy(outp, cert->cache_info.signed_descriptor_body, cert->cache_info.signed_descriptor_len); @@ -207,6 +215,12 @@ get_voter(const networkstatus_t *vote) return smartlist_get(vote->voters, 0); } +typedef struct { + networkstatus_t *v; + const char *digest; + int is_legacy; +} dir_src_ent_t; + /** Helper for sorting networkstatus_t votes (not consensuses) by the * hash of their voters' identity digests. */ static int @@ -217,6 +231,19 @@ _compare_votes_by_authority_id(const void **_a, const void **_b) get_voter(b)->identity_digest, DIGEST_LEN); } +static int +_compare_dir_src_ents_by_authority_id(const void **_a, const void **_b) +{ + const dir_src_ent_t *a = *_a, *b = *_b; + const networkstatus_voter_info_t *a_v = get_voter(a->v), + *b_v = get_voter(b->v); + const char *a_id, *b_id; + a_id = a->is_legacy ? a_v->legacy_id_digest : a_v->identity_digest; + b_id = b->is_legacy ? b_v->legacy_id_digest : b_v->identity_digest; + + return memcmp(a_id, b_id, DIGEST_LEN); +} + /** Given a sorted list of strings in, add every member to out * that occurs more than min times. */ static void @@ -416,7 +443,7 @@ compute_consensus_method(smartlist_t *votes) static int consensus_method_is_supported(int method) { - return (method >= 1) && (method <= 2); + return (method >= 1) && (method <= 3); } /** Given a list of vote networkstatus_t in votes, our public @@ -581,33 +608,64 @@ networkstatus_compute_consensus(smartlist_t *votes, /* Sort the votes. */ smartlist_sort(votes, _compare_votes_by_authority_id); /* Add the authority sections. */ - SMARTLIST_FOREACH(votes, networkstatus_t *, v, { - char buf[1024]; - struct in_addr in; - char ip[INET_NTOA_BUF_LEN]; - char fingerprint[HEX_DIGEST_LEN+1]; - char votedigest[HEX_DIGEST_LEN+1]; - networkstatus_voter_info_t *voter = get_voter(v); + smartlist_t *dir_sources = smartlist_create(); + SMARTLIST_FOREACH(votes, networkstatus_t *, v, + { + dir_src_ent_t *e = tor_malloc_zero(sizeof(dir_src_ent_t)); + e->v = v; + e->digest = get_voter(v)->identity_digest; + e->is_legacy = 0; + smartlist_add(dir_sources, e); + if (consensus_method >= 3 && + !tor_digest_is_zero(get_voter(v)->legacy_id_digest)) { + dir_src_ent_t *e_legacy = tor_malloc_zero(sizeof(dir_src_ent_t)); + e_legacy->v = v; + e_legacy->digest = get_voter(v)->legacy_id_digest; + e_legacy->is_legacy = 1; + smartlist_add(dir_sources, e); + } + }); + smartlist_sort(dir_sources, _compare_dir_src_ents_by_authority_id); - in.s_addr = htonl(voter->addr); - tor_inet_ntoa(&in, ip, sizeof(ip)); - base16_encode(fingerprint, sizeof(fingerprint), voter->identity_digest, - DIGEST_LEN); - base16_encode(votedigest, sizeof(votedigest), voter->vote_digest, - DIGEST_LEN); + SMARTLIST_FOREACH(dir_sources, const dir_src_ent_t *, e, + { + char buf[1024]; + struct in_addr in; + char ip[INET_NTOA_BUF_LEN]; + char fingerprint[HEX_DIGEST_LEN+1]; + char votedigest[HEX_DIGEST_LEN+1]; + networkstatus_t *v = e->v; + networkstatus_voter_info_t *voter = get_voter(v); - tor_snprintf(buf, sizeof(buf), - "dir-source %s %s %s %s %d %d\n" - "contact %s\n" - "vote-digest %s\n", - voter->nickname, fingerprint, voter->address, ip, - voter->dir_port, - voter->or_port, - voter->contact, - votedigest); - smartlist_add(chunks, tor_strdup(buf)); - }); + if (e->is_legacy) + tor_assert(consensus_method >= 2); + + in.s_addr = htonl(voter->addr); + tor_inet_ntoa(&in, ip, sizeof(ip)); + base16_encode(fingerprint, sizeof(fingerprint), e->digest, DIGEST_LEN); + base16_encode(votedigest, sizeof(votedigest), voter->vote_digest, + DIGEST_LEN); + + tor_snprintf(buf, sizeof(buf), + "dir-source %s%s %s %s %s %d %d\n", + voter->nickname, e->is_legacy ? "-legacy" : "", + fingerprint, voter->address, ip, + voter->dir_port, + voter->or_port); + smartlist_add(chunks, tor_strdup(buf)); + if (! e->is_legacy) { + tor_snprintf(buf, sizeof(buf), + "contact %s\n" + "vote-digest %s\n", + voter->contact, + votedigest); + smartlist_add(chunks, tor_strdup(buf)); + } + }); + SMARTLIST_FOREACH(dir_sources, dir_src_ent_t *, e, tor_free(e)); + smartlist_free(dir_sources); + } /* Add the actual router entries. */ { @@ -904,6 +962,22 @@ networkstatus_compute_consensus(smartlist_t *votes, return NULL; /* This leaks, but it should never happen. */ } smartlist_add(chunks, tor_strdup(buf)); + + if (get_options()->V3AuthUseLegacyKey && consensus_method >= 3) { + crypto_pk_env_t *legacy_key = get_my_v3_legacy_signing_key(); + authority_cert_t *legacy_cert = get_my_v3_legacy_cert(); + smartlist_add(chunks, tor_strdup("directory-signature ")); + crypto_pk_get_fingerprint(legacy_cert->identity_key, fingerprint, 0); + crypto_pk_get_fingerprint(legacy_key, signing_key_fingerprint, 0); + tor_snprintf(buf, sizeof(buf), "%s %s\n", fingerprint, + signing_key_fingerprint); + if (router_append_dirobj_signature(buf, sizeof(buf), digest, + signing_key)) { + log_warn(LD_BUG, "Couldn't sign consensus networkstatus."); + return NULL; /* This leaks, but it should never happen. */ + } + smartlist_add(chunks, tor_strdup(buf)); + } } result = smartlist_join_strings(chunks, "", 0, NULL); diff --git a/src/or/or.h b/src/or/or.h index 9c91a55fe..bb19a144c 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -1452,6 +1452,7 @@ typedef struct networkstatus_voter_info_t { uint16_t or_port; /**< OR port of this voter */ char *contact; /**< Contact information for this voter. */ char vote_digest[DIGEST_LEN]; /**< Digest of this voter's vote, as signed. */ + char legacy_id_digest[DIGEST_LEN]; /**< From vote only. DOCDOC */ /* Nothing from here on is signed. */ char signing_key_digest[DIGEST_LEN]; /**< Declared digest of signing key @@ -2330,6 +2331,10 @@ typedef struct { /** The number of intervals we think a consensus should be valid. */ int V3AuthNIntervalsValid; + /** Should advertise and sign consensuses with a legacy key, for key + * migration purposes? */ + int V3AuthUseLegacyKey; + /** File to check for a consensus networkstatus, if we don't have one * cached. */ char *FallbackNetworkstatusFile; @@ -3751,6 +3756,8 @@ crypto_pk_env_t *get_identity_key(void); int identity_key_is_set(void); authority_cert_t *get_my_v3_authority_cert(void); crypto_pk_env_t *get_my_v3_authority_signing_key(void); +authority_cert_t *get_my_v3_legacy_cert(void); +crypto_pk_env_t *get_my_v3_legacy_signing_key(void); void dup_onion_keys(crypto_pk_env_t **key, crypto_pk_env_t **last); void rotate_onion_key(void); crypto_pk_env_t *init_key_from_file(const char *fname, int generate, diff --git a/src/or/router.c b/src/or/router.c index 91518141c..6695989b1 100644 --- a/src/or/router.c +++ b/src/or/router.c @@ -45,6 +45,9 @@ static crypto_pk_env_t *authority_signing_key = NULL; * authorities. */ static authority_cert_t *authority_key_certificate = NULL; +static crypto_pk_env_t *legacy_signing_key = NULL; +static authority_cert_t *legacy_key_certificate = NULL; + /* (Note that v3 authorities also have a separate "authority identity key", * but this key is never actually loaded by the Tor process. Instead, it's * used by tor-gencert to sign new signing keys and make new key @@ -144,6 +147,18 @@ get_my_v3_authority_signing_key(void) return authority_signing_key; } +authority_cert_t * +get_my_v3_legacy_cert(void) +{ + return legacy_key_certificate; +} + +crypto_pk_env_t * +get_my_v3_legacy_signing_key(void) +{ + return legacy_signing_key; +} + /** Replace the previous onion key with the current onion key, and generate * a new previous onion key. Immediately after calling this function, * the OR should: @@ -258,26 +273,26 @@ init_key_from_file(const char *fname, int generate, int severity) return NULL; } -/** Load the v3 (voting) authority signing key and certificate, if they are - * present. Return -1 if anything is missing, mismatched, or unloadable; - * return 0 on success. */ static int -init_v3_authority_keys(void) +load_authority_keyset(int legacy, crypto_pk_env_t **key_out, + authority_cert_t **cert_out) { + int r = -1; char *fname = NULL, *cert = NULL; const char *eos = NULL; crypto_pk_env_t *signing_key = NULL; authority_cert_t *parsed = NULL; - int r = -1; - fname = get_datadir_fname2("keys", "authority_signing_key"); + fname = get_datadir_fname2("keys", + legacy ? "legacy_signing_key" : "authority_signing_key"); signing_key = init_key_from_file(fname, 0, LOG_INFO); if (!signing_key) { log_warn(LD_DIR, "No version 3 directory key found in %s", fname); goto done; } tor_free(fname); - fname = get_datadir_fname2("keys", "authority_certificate"); + fname = get_datadir_fname2("keys", + legacy ? "legacy_certificate" : "authority_certificate"); cert = read_file_to_str(fname, 0, NULL); if (!cert) { log_warn(LD_DIR, "Signing key found, but no certificate found in %s", @@ -298,18 +313,16 @@ init_v3_authority_keys(void) parsed->cache_info.signed_descriptor_len = eos-cert; cert = NULL; - /* Free old values... */ - if (authority_key_certificate) - authority_cert_free(authority_key_certificate); - if (authority_signing_key) - crypto_free_pk_env(authority_signing_key); - /* ...and replace them. */ - authority_key_certificate = parsed; - authority_signing_key = signing_key; - parsed = NULL; - signing_key = NULL; - + if (*key_out) + crypto_free_pk_env(*key_out); + if (*cert_out) + authority_cert_free(*cert_out); + *key_out = signing_key; + *cert_out = parsed; r = 0; + signing_key = NULL; + parsed = NULL; + done: tor_free(fname); tor_free(cert); @@ -320,6 +333,24 @@ init_v3_authority_keys(void) return r; } +/** Load the v3 (voting) authority signing key and certificate, if they are + * present. Return -1 if anything is missing, mismatched, or unloadable; + * return 0 on success. */ +static int +init_v3_authority_keys(void) +{ + if (load_authority_keyset(0, &authority_signing_key, + &authority_key_certificate)<0) + return -1; + + if (get_options()->V3AuthUseLegacyKey && + load_authority_keyset(0, &legacy_signing_key, + &legacy_key_certificate)<0) + return -1; + + return 0; +} + /** If we're a v3 authority, check whether we have a certificate that's * likely to expire soon. Warn if we do, but not too often. */ void @@ -1956,6 +1987,10 @@ router_free_all(void) crypto_free_pk_env(authority_signing_key); if (authority_key_certificate) authority_cert_free(authority_key_certificate); + if (legacy_signing_key) + crypto_free_pk_env(legacy_signing_key); + if (legacy_key_certificate) + authority_cert_free(legacy_key_certificate); if (warned_nonexistent_family) { SMARTLIST_FOREACH(warned_nonexistent_family, char *, cp, tor_free(cp)); diff --git a/src/or/routerparse.c b/src/or/routerparse.c index 31b10baf3..1520dc32f 100644 --- a/src/or/routerparse.c +++ b/src/or/routerparse.c @@ -78,6 +78,7 @@ typedef enum { K_CONSENSUS_DIGEST, K_CONSENSUS_METHODS, K_CONSENSUS_METHOD, + K_LEGACY_DIR_KEY, A_PURPOSE, _A_UNKNOWN, @@ -358,6 +359,7 @@ static token_rule_t networkstatus_token_table[] = { T0N("opt", K_OPT, CONCAT_ARGS, OBJ_OK ), T1( "contact", K_CONTACT, CONCAT_ARGS, NO_OBJ ), T1( "dir-source", K_DIR_SOURCE, GE(6), NO_OBJ ), + T01("legacy-dir-key", K_LEGACY_DIR_KEY, GE(1), NO_OBJ ), T1( "known-flags", K_KNOWN_FLAGS, CONCAT_ARGS, NO_OBJ ), T01("client-versions", K_CLIENT_VERSIONS, CONCAT_ARGS, NO_OBJ ), T01("server-versions", K_SERVER_VERSIONS, CONCAT_ARGS, NO_OBJ ), @@ -2213,6 +2215,23 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out, goto err; } + if (is_vote && + (tok = find_first_by_keyword(tokens, K_LEGACY_DIR_KEY))) { + int bad = 1; + if (strlen(tok->args[0]) == HEX_DIGEST_LEN) { + networkstatus_voter_info_t *voter = smartlist_get(ns->voters, 0); + if (base16_decode(voter->legacy_id_digest, DIGEST_LEN, + tok->args[0], HEX_DIGEST_LEN)<0) + bad = 1; + else + bad = 0; + } + if (bad) { + log_warn(LD_DIR, "Invalid legacy key digest %s on vote.", + escaped(tok->args[0])); + } + } + /* Parse routerstatus lines. */ rs_tokens = smartlist_create(); s = end_of_header; diff --git a/src/win32/orconfig.h b/src/win32/orconfig.h index bf6a31f40..938c47f12 100644 --- a/src/win32/orconfig.h +++ b/src/win32/orconfig.h @@ -227,6 +227,6 @@ #define USING_TWOS_COMPLEMENT /* Version number of package */ -#define VERSION "0.2.0.25-rc" +#define VERSION "0.2.0.25-rc-dev"