From 5b3dd1610cf2147509167332bf298fc821e6a102 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 4 Dec 2012 15:58:18 -0500 Subject: [PATCH] Wrangle curve25519 onion keys: generate, store, load, publish, republish Here we try to handle curve25519 onion keys from generating them, loading and storing them, publishing them in our descriptors, putting them in microdescriptors, and so on. This commit is untested and probably buggy like whoa --- src/or/dirserv.c | 3 +- src/or/dirvote.c | 9 ++ src/or/dirvote.h | 6 +- src/or/microdesc.c | 1 + src/or/or.h | 5 + src/or/router.c | 216 +++++++++++++++++++++++++++++++++++++++++-- src/or/router.h | 5 + src/or/routerlist.c | 1 + src/or/routerparse.c | 34 +++++++ 9 files changed, 272 insertions(+), 8 deletions(-) diff --git a/src/or/dirserv.c b/src/or/dirserv.c index c1ddf73ee4..de4d63fa11 100644 --- a/src/or/dirserv.c +++ b/src/or/dirserv.c @@ -74,7 +74,8 @@ static const struct consensus_method_range_t { } microdesc_consensus_methods[] = { {MIN_METHOD_FOR_MICRODESC, MIN_METHOD_FOR_A_LINES - 1}, {MIN_METHOD_FOR_A_LINES, MIN_METHOD_FOR_P6_LINES - 1}, - {MIN_METHOD_FOR_P6_LINES, MAX_SUPPORTED_CONSENSUS_METHOD}, + {MIN_METHOD_FOR_P6_LINES, MIN_METHOD_FOR_NTOR_KEY - 1}, + {MIN_METHOD_FOR_NTOR_KEY, MAX_SUPPORTED_CONSENSUS_METHOD}, {-1, -1} }; diff --git a/src/or/dirvote.c b/src/or/dirvote.c index 836349375c..6236d2a026 100644 --- a/src/or/dirvote.c +++ b/src/or/dirvote.c @@ -3554,6 +3554,15 @@ dirvote_create_microdescriptor(const routerinfo_t *ri, int consensus_method) smartlist_add_asprintf(chunks, "onion-key\n%s", key); + if (consensus_method >= MIN_METHOD_FOR_NTOR_KEY && + ri->onion_curve25519_pkey) { + char kbuf[128]; + base64_encode(kbuf, sizeof(kbuf), + (const char*)ri->onion_curve25519_pkey->public_key, + CURVE25519_PUBKEY_LEN); + smartlist_add_asprintf(chunks, "ntor-onion-key %s", kbuf); + } + if (consensus_method >= MIN_METHOD_FOR_A_LINES && !tor_addr_is_null(&ri->ipv6_addr) && ri->ipv6_orport) smartlist_add_asprintf(chunks, "a %s\n", diff --git a/src/or/dirvote.h b/src/or/dirvote.h index d14a375161..19444c370c 100644 --- a/src/or/dirvote.h +++ b/src/or/dirvote.h @@ -20,7 +20,7 @@ #define MIN_VOTE_INTERVAL 300 /** The highest consensus method that we currently support. */ -#define MAX_SUPPORTED_CONSENSUS_METHOD 15 +#define MAX_SUPPORTED_CONSENSUS_METHOD 16 /** Lowest consensus method that contains a 'directory-footer' marker */ #define MIN_METHOD_FOR_FOOTER 9 @@ -48,6 +48,10 @@ /** Lowest consensus method where microdescs may include a "p6" line. */ #define MIN_METHOD_FOR_P6_LINES 15 +/** Lowest consensus method where microdescs may include an onion-key-ntor + * line */ +#define MIN_METHOD_FOR_NTOR_KEY 16 + void dirvote_free_all(void); /* vote manipulation */ diff --git a/src/or/microdesc.c b/src/or/microdesc.c index 7602a93457..93cf3edbdf 100644 --- a/src/or/microdesc.c +++ b/src/or/microdesc.c @@ -575,6 +575,7 @@ microdesc_free(microdesc_t *md) if (md->onion_pkey) crypto_pk_free(md->onion_pkey); + tor_free(md->onion_curve25519_pkey); if (md->body && md->saved_location != SAVED_IN_CACHE) tor_free(md->body); diff --git a/src/or/or.h b/src/or/or.h index 2ac9f6bdeb..219336bf72 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -99,6 +99,7 @@ #include "compat_libevent.h" #include "ht.h" #include "replaycache.h" +#include "crypto_curve25519.h" /* These signals are defined to help handle_control_signal work. */ @@ -1893,6 +1894,8 @@ typedef struct { crypto_pk_t *onion_pkey; /**< Public RSA key for onions. */ crypto_pk_t *identity_pkey; /**< Public RSA key for signing. */ + /** Public curve25519 key for onions */ + curve25519_public_key_t *onion_curve25519_pkey; char *platform; /**< What software/operating system is this OR using? */ @@ -2106,6 +2109,8 @@ typedef struct microdesc_t { /** As routerinfo_t.onion_pkey */ crypto_pk_t *onion_pkey; + /** As routerinfo_t.onion_curve25519_pkey */ + curve25519_public_key_t *onion_curve25519_pkey; /** As routerinfo_t.ipv6_add */ tor_addr_t ipv6_addr; /** As routerinfo_t.ipv6_orport */ diff --git a/src/or/router.c b/src/or/router.c index d5ffb36fd2..954304dd26 100644 --- a/src/or/router.c +++ b/src/or/router.c @@ -13,6 +13,7 @@ #include "config.h" #include "connection.h" #include "control.h" +#include "crypto_curve25519.h" #include "directory.h" #include "dirserv.h" #include "dns.h" @@ -54,6 +55,11 @@ static crypto_pk_t *onionkey=NULL; /** Previous private onionskin decryption key: used to decode CREATE cells * generated by clients that have an older version of our descriptor. */ static crypto_pk_t *lastonionkey=NULL; +#ifdef CURVE25519_ENABLED +/**DOCDOC*/ +static curve25519_keypair_t curve25519_onion_key; +static curve25519_keypair_t last_curve25519_onion_key; +#endif /** Private server "identity key": used to sign directory info and TLS * certificates. Never changes. */ static crypto_pk_t *server_identitykey=NULL; @@ -99,6 +105,20 @@ set_onion_key(crypto_pk_t *k) mark_my_descriptor_dirty("set onion key"); } +#if 0 +/**DOCDOC*/ +static void +set_curve25519_onion_key(const curve25519_keypair_t *kp) +{ + if (tor_memeq(&curve25519_onion_key, kp, sizeof(curve25519_keypair_t))) + return; + + tor_mutex_acquire(key_lock); + memcpy(&curve25519_onion_key, kp, sizeof(curve25519_keypair_t)); + tor_mutex_release(key_lock); +} +#endif + /** Return the current onion key. Requires that the onion key has been * loaded or generated. */ crypto_pk_t * @@ -126,6 +146,47 @@ dup_onion_keys(crypto_pk_t **key, crypto_pk_t **last) tor_mutex_release(key_lock); } +#ifdef CURVE25519_ENABLED +/**DOCDOC only in main thread*/ +static const curve25519_keypair_t * +get_current_curve25519_keypair(void) +{ + return &curve25519_onion_key; +} +di_digest256_map_t * +construct_ntor_key_map(void) +{ + di_digest256_map_t *m = NULL; + + dimap_add_entry(&m, + curve25519_onion_key.pubkey.public_key, + tor_memdup(&curve25519_onion_key, + sizeof(curve25519_keypair_t))); + if (!tor_mem_is_zero((const char*) + last_curve25519_onion_key.pubkey.public_key, + CURVE25519_PUBKEY_LEN)) { + dimap_add_entry(&m, + last_curve25519_onion_key.pubkey.public_key, + tor_memdup(&last_curve25519_onion_key, + sizeof(curve25519_keypair_t))); + } + + return m; +} +static void +ntor_key_map_free_helper(void *arg) +{ + curve25519_keypair_t *k = arg; + memwipe(k, 0, sizeof(*k)); + tor_free(k); +} +void +ntor_key_map_free(di_digest256_map_t *map) +{ + dimap_free(map, ntor_key_map_free_helper); +} +#endif + /** Return the time when the onion key was last set. This is either the time * when the process launched, or the time of the most recent key rotation since * the process launched. @@ -253,11 +314,18 @@ void rotate_onion_key(void) { char *fname, *fname_prev; - crypto_pk_t *prkey; + crypto_pk_t *prkey = NULL; or_state_t *state = get_or_state(); +#ifdef CURVE25519_ENABLED + curve25519_keypair_t new_curve25519_keypair; +#endif time_t now; fname = get_datadir_fname2("keys", "secret_onion_key"); fname_prev = get_datadir_fname2("keys", "secret_onion_key.old"); + if (file_status(fname) == FN_FILE) { + if (replace_file(fname, fname_prev)) + goto error; + } if (!(prkey = crypto_pk_new())) { log_err(LD_GENERAL,"Error constructing rotated onion key"); goto error; @@ -266,19 +334,37 @@ rotate_onion_key(void) log_err(LD_BUG,"Error generating onion key"); goto error; } - if (file_status(fname) == FN_FILE) { - if (replace_file(fname, fname_prev)) - goto error; - } if (crypto_pk_write_private_key_to_filename(prkey, fname)) { log_err(LD_FS,"Couldn't write generated onion key to \"%s\".", fname); goto error; } +#ifdef CURVE25519_ENABLED + tor_free(fname); + tor_free(fname_prev); + fname = get_datadir_fname2("keys", "secret_onion_key_ntor"); + fname_prev = get_datadir_fname2("keys", "secret_onion_key_ntor.old"); + curve25519_keypair_generate(&new_curve25519_keypair, 1); + if (file_status(fname) == FN_FILE) { + if (replace_file(fname, fname_prev)) + goto error; + } + if (curve25519_keypair_write_to_file(&new_curve25519_keypair, fname, + "onion") < 0) { + log_err(LD_FS,"Couldn't write curve25519 onion key to \"%s\".",fname); + goto error; + } +#endif log_info(LD_GENERAL, "Rotating onion key"); tor_mutex_acquire(key_lock); crypto_pk_free(lastonionkey); lastonionkey = onionkey; onionkey = prkey; +#ifdef CURVE25519_ENABLED + memcpy(&last_curve25519_onion_key, &curve25519_onion_key, + sizeof(curve25519_keypair_t)); + memcpy(&curve25519_onion_key, &new_curve25519_keypair, + sizeof(curve25519_keypair_t)); +#endif now = time(NULL); state->LastRotatedOnionKey = onionkey_set_at = now; tor_mutex_release(key_lock); @@ -290,6 +376,9 @@ rotate_onion_key(void) if (prkey) crypto_pk_free(prkey); done: +#ifdef CURVE25519_ENABLED + memwipe(&new_curve25519_keypair, 0, sizeof(new_curve25519_keypair)); +#endif tor_free(fname); tor_free(fname_prev); } @@ -363,6 +452,72 @@ init_key_from_file(const char *fname, int generate, int severity) return NULL; } +#ifdef CURVE25519_ENABLED +/** DOCDOC */ +static int +init_curve25519_keypair_from_file(curve25519_keypair_t *keys_out, + const char *fname, + int generate, + int severity, + const char *tag) +{ + switch (file_status(fname)) { + case FN_DIR: + case FN_ERROR: + log(severity, LD_FS,"Can't read key from \"%s\"", fname); + goto error; + case FN_NOENT: + if (generate) { + if (!have_lockfile()) { + if (try_locking(get_options(), 0)<0) { + /* Make sure that --list-fingerprint only creates new keys + * if there is no possibility for a deadlock. */ + log(severity, LD_FS, "Another Tor process has locked \"%s\". Not " + "writing any new keys.", fname); + /*XXXX The 'other process' might make a key in a second or two; + * maybe we should wait for it. */ + goto error; + } + } + log_info(LD_GENERAL, "No key found in \"%s\"; generating fresh key.", + fname); + curve25519_keypair_generate(keys_out, 1); + if (curve25519_keypair_write_to_file(keys_out, fname, tag)<0) { + log(severity, LD_FS, + "Couldn't write generated key to \"%s\".", fname); + memset(keys_out, 0, sizeof(*keys_out)); + goto error; + } + } else { + log_info(LD_GENERAL, "No key found in \"%s\"", fname); + } + return 0; + case FN_FILE: + { + char *tag_in=NULL; + if (curve25519_keypair_read_from_file(keys_out, &tag_in, fname) < 0) { + log(severity, LD_GENERAL,"Error loading private key."); + tor_free(tag_in); + goto error; + } + if (!tag_in || strcmp(tag_in, tag)) { + log(severity, LD_GENERAL,"Unexpected tag %s on private key.", + escaped(tag_in)); + tor_free(tag_in); + goto error; + } + tor_free(tag_in); + return 0; + } + default: + tor_assert(0); + } + + error: + return -1; +} +#endif + /** Try to load the vote-signing private key and certificate for being a v3 * directory authority, and make sure they match. If legacy, load a * legacy key/cert set for emergency key migration; otherwise load the regular @@ -630,12 +785,35 @@ init_keys(void) keydir = get_datadir_fname2("keys", "secret_onion_key.old"); if (!lastonionkey && file_status(keydir) == FN_FILE) { - prkey = init_key_from_file(keydir, 1, LOG_ERR); + prkey = init_key_from_file(keydir, 1, LOG_ERR); /* XXXX Why 1? */ if (prkey) lastonionkey = prkey; } tor_free(keydir); +#ifdef CURVE25519_ENABLED + { + /* 2b. Load curve25519 onion keys. */ + int r; + keydir = get_datadir_fname2("keys", "secret_onion_key_ntor"); + r = init_curve25519_keypair_from_file(&curve25519_onion_key, + keydir, 1, LOG_ERR, "onion"); + tor_free(keydir); + if (r<0) + return -1; + + keydir = get_datadir_fname2("keys", "secret_onion_key_ntor.old"); + if (tor_mem_is_zero((const char *) + last_curve25519_onion_key.pubkey.public_key, + CURVE25519_PUBKEY_LEN) && + file_status(keydir) == FN_FILE) { + init_curve25519_keypair_from_file(&last_curve25519_onion_key, + keydir, 0, LOG_ERR, "onion"); + } + tor_free(keydir); + } +#endif + /* 3. Initialize link key and TLS context. */ if (router_initialize_tls_context() < 0) { log_err(LD_GENERAL,"Error initializing TLS context"); @@ -1566,6 +1744,11 @@ router_rebuild_descriptor(int force) ri->cache_info.published_on = time(NULL); ri->onion_pkey = crypto_pk_dup_key(get_onion_key()); /* must invoke from * main thread */ +#ifdef CURVE25519_ENABLED + ri->onion_curve25519_pkey = + tor_memdup(&get_current_curve25519_keypair()->pubkey, + sizeof(curve25519_public_key_t)); +#endif /* For now, at most one IPv6 or-address is being advertised. */ { @@ -2146,6 +2329,22 @@ router_dump_router_to_string(char *s, size_t maxlen, routerinfo_t *router, written += result; } +#ifdef CURVE25519_ENABLED + if (router->onion_curve25519_pkey) { + char kbuf[128]; + base64_encode(kbuf, sizeof(kbuf), + (const char *)router->onion_curve25519_pkey->public_key, + CURVE25519_PUBKEY_LEN); + result = tor_snprintf(s+written,maxlen-written, "ntor-onion-key %s", + kbuf); + if (result<0) { + log_warn(LD_BUG,"descriptor snprintf ran out of room!"); + return -1; + } + written += result; + } +#endif + /* Write the exit policy to the end of 's'. */ if (!router->exit_policy || !smartlist_len(router->exit_policy)) { strlcat(s+written, "reject *:*\n", maxlen-written); @@ -2794,6 +2993,11 @@ router_free_all(void) crypto_pk_free(legacy_signing_key); authority_cert_free(legacy_key_certificate); +#ifdef CURVE25519_ENABLED + memwipe(&curve25519_onion_key, 0, sizeof(curve25519_onion_key)); + memwipe(&last_curve25519_onion_key, 0, sizeof(last_curve25519_onion_key)); +#endif + if (warned_nonexistent_family) { SMARTLIST_FOREACH(warned_nonexistent_family, char *, cp, tor_free(cp)); smartlist_free(warned_nonexistent_family); diff --git a/src/or/router.h b/src/or/router.h index b641c1cc6a..85c7d351d1 100644 --- a/src/or/router.h +++ b/src/or/router.h @@ -30,6 +30,11 @@ crypto_pk_t *init_key_from_file(const char *fname, int generate, int severity); void v3_authority_check_key_expiry(void); +#ifdef CURVE25519_ENABLED +di_digest256_map_t *construct_ntor_key_map(void); +void ntor_key_map_free(di_digest256_map_t *map); +#endif + int router_initialize_tls_context(void); int init_keys(void); diff --git a/src/or/routerlist.c b/src/or/routerlist.c index 1735837871..0508e4174e 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -2395,6 +2395,7 @@ routerinfo_free(routerinfo_t *router) tor_free(router->contact_info); if (router->onion_pkey) crypto_pk_free(router->onion_pkey); + tor_free(router->onion_curve25519_pkey); if (router->identity_pkey) crypto_pk_free(router->identity_pkey); if (router->declared_family) { diff --git a/src/or/routerparse.c b/src/or/routerparse.c index 0ab99a09ca..17902d9d0a 100644 --- a/src/or/routerparse.c +++ b/src/or/routerparse.c @@ -43,6 +43,7 @@ typedef enum { K_SIGNED_DIRECTORY, K_SIGNING_KEY, K_ONION_KEY, + K_ONION_KEY_NTOR, K_ROUTER_SIGNATURE, K_PUBLISHED, K_RUNNING_ROUTERS, @@ -276,6 +277,7 @@ static token_rule_t routerdesc_token_table[] = { T01("ipv6-policy", K_IPV6_POLICY, CONCAT_ARGS, NO_OBJ), T1( "signing-key", K_SIGNING_KEY, NO_ARGS, NEED_KEY_1024 ), T1( "onion-key", K_ONION_KEY, NO_ARGS, NEED_KEY_1024 ), + T01("ntor-onion-key", K_ONION_KEY_NTOR, GE(1), NO_OBJ ), T1_END( "router-signature", K_ROUTER_SIGNATURE, NO_ARGS, NEED_OBJ ), T1( "published", K_PUBLISHED, CONCAT_ARGS, NO_OBJ ), T01("uptime", K_UPTIME, GE(1), NO_OBJ ), @@ -527,6 +529,7 @@ static token_rule_t networkstatus_detached_signature_token_table[] = { /** List of tokens recognized in microdescriptors */ static token_rule_t microdesc_token_table[] = { T1_START("onion-key", K_ONION_KEY, NO_ARGS, NEED_KEY_1024), + T01("ntor-onion-key", K_ONION_KEY_NTOR, GE(1), NO_OBJ ), T0N("a", K_A, GE(1), NO_OBJ ), T01("family", K_FAMILY, ARGS, NO_OBJ ), T01("p", K_P, CONCAT_ARGS, NO_OBJ ), @@ -1516,6 +1519,21 @@ router_parse_entry_from_string(const char *s, const char *end, router->onion_pkey = tok->key; tok->key = NULL; /* Prevent free */ + if ((tok = find_opt_by_keyword(tokens, K_ONION_KEY_NTOR))) { + uint8_t k[CURVE25519_PUBKEY_LEN+32]; + int r; + tor_assert(tok->n_args >= 1); + r = base64_decode((char*)k, sizeof(k), tok->args[0], strlen(tok->args[0])); + if (r != CURVE25519_PUBKEY_LEN) { + log_warn(LD_DIR, "Bogus onion-key-ntor in routerinfo"); + goto err; + } + router->onion_curve25519_pkey = + tor_malloc(sizeof(curve25519_public_key_t)); + memcpy(router->onion_curve25519_pkey->public_key, + k, CURVE25519_PUBKEY_LEN); + } + tok = find_by_keyword(tokens, K_SIGNING_KEY); router->identity_pkey = tok->key; tok->key = NULL; /* Prevent free */ @@ -4475,6 +4493,22 @@ microdescs_parse_from_string(const char *s, const char *eos, md->onion_pkey = tok->key; tok->key = NULL; + if ((tok = find_opt_by_keyword(tokens, K_ONION_KEY_NTOR))) { + uint8_t k[CURVE25519_PUBKEY_LEN+32]; + int r; + tor_assert(tok->n_args >= 1); + r = base64_decode((char*)k, sizeof(k), + tok->args[0], strlen(tok->args[0])); + if (r != CURVE25519_PUBKEY_LEN) { + log_warn(LD_DIR, "Bogus onion-key-ntor in microdesc"); + goto next; + } + md->onion_curve25519_pkey = + tor_malloc(sizeof(curve25519_public_key_t)); + memcpy(md->onion_curve25519_pkey->public_key, + k, CURVE25519_PUBKEY_LEN); + } + { smartlist_t *a_lines = find_all_by_keyword(tokens, K_A); if (a_lines) {