Bläddra i källkod

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
Nick Mathewson 12 år sedan
förälder
incheckning
5b3dd1610c
9 ändrade filer med 270 tillägg och 6 borttagningar
  1. 2 1
      src/or/dirserv.c
  2. 9 0
      src/or/dirvote.c
  3. 5 1
      src/or/dirvote.h
  4. 1 0
      src/or/microdesc.c
  5. 5 0
      src/or/or.h
  6. 208 4
      src/or/router.c
  7. 5 0
      src/or/router.h
  8. 1 0
      src/or/routerlist.c
  9. 34 0
      src/or/routerparse.c

+ 2 - 1
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}
 };
 

+ 9 - 0
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",

+ 5 - 1
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 */

+ 1 - 0
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);
 

+ 5 - 0
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 */

+ 208 - 4
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 (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 (crypto_pk_write_private_key_to_filename(prkey, fname)) {
-    log_err(LD_FS,"Couldn't write generated onion key to \"%s\".", fname);
+  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 <b>legacy</b>, 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);

+ 5 - 0
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);
 

+ 1 - 0
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) {

+ 34 - 0
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) {