r19690@catbus: nickm | 2008-05-11 22:13:31 -0400

Implement a proposal to let a directory authority migrate its identity key without ceasing to sign consensuses.


svn:r14584
This commit is contained in:
Nick Mathewson 2008-05-12 02:14:01 +00:00
parent b7a80920e2
commit f3f6ecef48
7 changed files with 190 additions and 45 deletions

View File

@ -78,10 +78,12 @@ Changes in version 0.2.1.1-alpha - 2008-??-??
- Make dumpstats() log the fullness and size of openssl-internal
buffers.
- Servers support a new URL scheme for consensus downloads that
allos the client to specify which authorities are trusted.
allows the client to specify which authorities are trusted.
The server then only sends the consensus if the client will
trust it. Otherwise a 404 error is sent back. Clients use
this new scheme when the server supports it.
- Add a new V3AuthUseLegacyKey option to make it easier for authorities
to change their identity keys if they have to.
o Minor features (security):
- Reject requests for reverse-dns lookup of names in a private

View File

@ -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"),

View File

@ -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);

View File

@ -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 <b>in</b>, add every member to <b>out</b>
* that occurs more than <b>min</b> 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 <b>votes</b>, 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);

View File

@ -1461,6 +1461,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
@ -2339,6 +2340,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;
@ -3772,6 +3777,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,

View File

@ -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
@ -1957,6 +1988,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));

View File

@ -79,6 +79,7 @@ typedef enum {
K_CONSENSUS_DIGEST,
K_CONSENSUS_METHODS,
K_CONSENSUS_METHOD,
K_LEGACY_DIR_KEY,
A_PURPOSE,
_A_UNKNOWN,
@ -365,6 +366,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 ),
@ -2282,6 +2284,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();
rs_area = memarea_new(512);