]> git.ipfire.org Git - thirdparty/tor.git/commitdiff
Implement proposal 228: cross-certification with onion keys
authorNick Mathewson <nickm@torproject.org>
Wed, 1 Oct 2014 15:54:07 +0000 (11:54 -0400)
committerNick Mathewson <nickm@torproject.org>
Thu, 28 May 2015 14:40:57 +0000 (10:40 -0400)
Routers now use TAP and ntor onion keys to sign their identity keys,
and put these signatures in their descriptors.  That allows other
parties to be confident that the onion keys are indeed controlled by
the router that generated the descriptor.

14 files changed:
src/common/crypto.c
src/common/crypto.h
src/or/or.h
src/or/router.c
src/or/router.h
src/or/routerkeys.c
src/or/routerkeys.h
src/or/routerlist.c
src/or/routerlist.h
src/or/routerparse.c
src/or/routerparse.h
src/or/torcert.h
src/test/test_dir.c
src/test/test_routerkeys.c

index 06631e1604951871c50027255b34cb598da031b9..05dc43213ce519e5ac342ce2d7e10ec674fa8936 100644 (file)
@@ -830,7 +830,7 @@ crypto_pk_public_exponent_ok(crypto_pk_t *env)
  * Note that this may leak information about the keys through timing.
  */
 int
-crypto_pk_cmp_keys(crypto_pk_t *a, crypto_pk_t *b)
+crypto_pk_cmp_keys(const crypto_pk_t *a, const crypto_pk_t *b)
 {
   int result;
   char a_is_non_null = (a != NULL) && (a->key != NULL);
@@ -856,19 +856,19 @@ crypto_pk_cmp_keys(crypto_pk_t *a, crypto_pk_t *b)
  *  Note that this may leak information about the keys through timing.
  */
 int
-crypto_pk_eq_keys(crypto_pk_t *a, crypto_pk_t *b)
+crypto_pk_eq_keys(const crypto_pk_t *a, const crypto_pk_t *b)
 {
   return (crypto_pk_cmp_keys(a, b) == 0);
 }
 
 /** Return the size of the public key modulus in <b>env</b>, in bytes. */
 size_t
-crypto_pk_keysize(crypto_pk_t *env)
+crypto_pk_keysize(const crypto_pk_t *env)
 {
   tor_assert(env);
   tor_assert(env->key);
 
-  return (size_t) RSA_size(env->key);
+  return (size_t) RSA_size((RSA*)env->key);
 }
 
 /** Return the size of the public key modulus of <b>env</b>, in bits. */
@@ -997,7 +997,7 @@ crypto_pk_private_decrypt(crypto_pk_t *env, char *to,
  * at least the length of the modulus of <b>env</b>.
  */
 int
-crypto_pk_public_checksig(crypto_pk_t *env, char *to,
+crypto_pk_public_checksig(const crypto_pk_t *env, char *to,
                           size_t tolen,
                           const char *from, size_t fromlen)
 {
@@ -1069,7 +1069,7 @@ crypto_pk_public_checksig_digest(crypto_pk_t *env, const char *data,
  * at least the length of the modulus of <b>env</b>.
  */
 int
-crypto_pk_private_sign(crypto_pk_t *env, char *to, size_t tolen,
+crypto_pk_private_sign(const crypto_pk_t *env, char *to, size_t tolen,
                        const char *from, size_t fromlen)
 {
   int r;
@@ -1084,7 +1084,7 @@ crypto_pk_private_sign(crypto_pk_t *env, char *to, size_t tolen,
 
   r = RSA_private_encrypt((int)fromlen,
                           (unsigned char*)from, (unsigned char*)to,
-                          env->key, RSA_PKCS1_PADDING);
+                          (RSA*)env->key, RSA_PKCS1_PADDING);
   if (r<0) {
     crypto_log_errors(LOG_WARN, "generating RSA signature");
     return -1;
@@ -1298,7 +1298,7 @@ crypto_pk_get_digest(const crypto_pk_t *pk, char *digest_out)
   unsigned char *buf = NULL;
   int len;
 
-  len = i2d_RSAPublicKey(pk->key, &buf);
+  len = i2d_RSAPublicKey((RSA*)pk->key, &buf);
   if (len < 0 || buf == NULL)
     return -1;
   if (crypto_digest(digest_out, (char*)buf, len) < 0) {
index 526619766d2b6787fb7e3eee7734fd7eaa8a2164..15d1f6e3a9a69f4c24b54405acdd492f0a8c11a4 100644 (file)
@@ -147,9 +147,9 @@ int crypto_pk_write_private_key_to_filename(crypto_pk_t *env,
                                             const char *fname);
 
 int crypto_pk_check_key(crypto_pk_t *env);
-int crypto_pk_cmp_keys(crypto_pk_t *a, crypto_pk_t *b);
-int crypto_pk_eq_keys(crypto_pk_t *a, crypto_pk_t *b);
-size_t crypto_pk_keysize(crypto_pk_t *env);
+int crypto_pk_cmp_keys(const crypto_pk_t *a, const crypto_pk_t *b);
+int crypto_pk_eq_keys(const crypto_pk_t *a, const crypto_pk_t *b);
+size_t crypto_pk_keysize(const crypto_pk_t *env);
 int crypto_pk_num_bits(crypto_pk_t *env);
 crypto_pk_t *crypto_pk_dup_key(crypto_pk_t *orig);
 crypto_pk_t *crypto_pk_copy_full(crypto_pk_t *orig);
@@ -161,11 +161,11 @@ int crypto_pk_public_encrypt(crypto_pk_t *env, char *to, size_t tolen,
 int crypto_pk_private_decrypt(crypto_pk_t *env, char *to, size_t tolen,
                               const char *from, size_t fromlen,
                               int padding, int warnOnFailure);
-int crypto_pk_public_checksig(crypto_pk_t *env, char *to, size_t tolen,
+int crypto_pk_public_checksig(const crypto_pk_t *env, char *to, size_t tolen,
                               const char *from, size_t fromlen);
 int crypto_pk_public_checksig_digest(crypto_pk_t *env, const char *data,
                                size_t datalen, const char *sig, size_t siglen);
-int crypto_pk_private_sign(crypto_pk_t *env, char *to, size_t tolen,
+int crypto_pk_private_sign(const crypto_pk_t *env, char *to, size_t tolen,
                            const char *from, size_t fromlen);
 int crypto_pk_private_sign_digest(crypto_pk_t *env, char *to, size_t tolen,
                                   const char *from, size_t fromlen);
index 437183e7274ec1599c8a85f540c18bfa3b668ef9..d030189b61f855e7d52a27a28b129e18dac2e953 100644 (file)
@@ -2023,6 +2023,9 @@ typedef struct {
   curve25519_public_key_t *onion_curve25519_pkey;
   /** Certificate for ed25519 signing key */
   struct tor_cert_st *signing_key_cert;
+  /** What's the earliest expiration time on all the certs in this
+   * routerinfo? */
+  time_t cert_expiration_time;
 
   char *platform; /**< What software/operating system is this OR using? */
 
@@ -5043,6 +5046,8 @@ typedef enum was_router_added_t {
   /* Router descriptor was rejected because it was older than
    * OLD_ROUTER_DESC_MAX_AGE. */
   ROUTER_WAS_TOO_OLD = -7, /* note contrast with 'NOT_NEW' */
+  /* DOCDOC */
+  ROUTER_CERTS_EXPIRED = -8
 } was_router_added_t;
 
 /********************************* routerparse.c ************************/
index 6b2a238140a86a37df7e261d0669e8dca5311ba3..2087a25fcaa6415be77f6cade25963215cc68688 100644 (file)
@@ -2000,6 +2000,8 @@ router_rebuild_descriptor(int force)
   }
   if (! (ri->cache_info.signed_descriptor_body =
           router_dump_router_to_string(ri, get_server_identity_key(),
+                                       get_onion_key(),
+                                       get_current_curve25519_keypair(),
                                        get_master_signing_keypair())) ) {
     log_warn(LD_BUG, "Couldn't generate router descriptor.");
     routerinfo_free(ri);
@@ -2306,7 +2308,9 @@ get_platform_str(char *platform, size_t len)
  */
 char *
 router_dump_router_to_string(routerinfo_t *router,
-                             crypto_pk_t *ident_key,
+                             const crypto_pk_t *ident_key,
+                             const crypto_pk_t *tap_key,
+                             const curve25519_keypair_t *ntor_keypair,
                              const ed25519_keypair_t *signing_keypair)
 {
   char *address = NULL;
@@ -2325,6 +2329,8 @@ router_dump_router_to_string(routerinfo_t *router,
   char *output = NULL;
   const int emit_ed_sigs = signing_keypair && router->signing_key_cert;
   char *ed_cert_line = NULL;
+  char *rsa_tap_cc_line = NULL;
+  char *ntor_cc_line = NULL;
 
   /* Make sure the identity key matches the one in the routerinfo. */
   if (!crypto_pk_eq_keys(ident_key, router->identity_pkey)) {
@@ -2377,6 +2383,67 @@ router_dump_router_to_string(routerinfo_t *router,
     goto err;
   }
 
+  /* Cross-certify with RSA key */
+  if (tap_key && router->signing_key_cert &&
+      router->signing_key_cert->signing_key_included) {
+    char buf[256];
+    int tap_cc_len = 0;
+    uint8_t *tap_cc =
+      make_tap_onion_key_crosscert(tap_key,
+                                   &router->signing_key_cert->signing_key,
+                                   router->identity_pkey,
+                                   &tap_cc_len);
+    if (!tap_cc) {
+      log_warn(LD_BUG,"make_tap_onion_key_crosscert failed!");
+      goto err;
+    }
+
+    if (base64_encode(buf, sizeof(buf), (const char*)tap_cc, tap_cc_len) < 0) {
+      log_warn(LD_BUG,"base64_encode(rsa_crosscert) failed!");
+      tor_free(tap_cc);
+      goto err;
+    }
+    tor_free(tap_cc);
+
+    tor_asprintf(&rsa_tap_cc_line,
+                 "onion-key-crosscert\n"
+                 "-----BEGIN CROSSCERT-----\n"
+                 "%s"
+                 "-----END CROSSCERT-----\n", buf);
+  }
+
+  /* Cross-certify with onion keys */
+  if (ntor_keypair && router->signing_key_cert &&
+      router->signing_key_cert->signing_key_included) {
+    int sign = 0;
+    char buf[256];
+    /* XXXX Base the expiration date on the actual onion key expiration time?*/
+    tor_cert_t *cert =
+      make_ntor_onion_key_crosscert(ntor_keypair,
+                                &router->signing_key_cert->signing_key,
+                                router->cache_info.published_on,
+                                MIN_ONION_KEY_LIFETIME, &sign);
+    if (!cert) {
+      log_warn(LD_BUG,"make_ntor_onion_key_crosscert failed!");
+      goto err;
+    }
+    tor_assert(sign == 0 || sign == 1);
+
+    if (base64_encode(buf, sizeof(buf),
+                      (const char*)cert->encoded, cert->encoded_len)<0) {
+      log_warn(LD_BUG,"base64_encode(ntor_crosscert) failed!");
+      tor_cert_free(cert);
+      goto err;
+    }
+    tor_cert_free(cert);
+
+    tor_asprintf(&ntor_cc_line,
+                 "ntor-onion-key-crosscert %d\n"
+                 "-----BEGIN ED25519 CERT-----\n"
+                 "%s"
+                 "-----END ED25519 CERT-----\n", sign, buf);
+  }
+
   /* Encode the publication time. */
   format_iso_time(published, router->cache_info.published_on);
 
@@ -2426,6 +2493,7 @@ router_dump_router_to_string(routerinfo_t *router,
                     "%s%s%s%s"
                     "onion-key\n%s"
                     "signing-key\n%s"
+                    "%s%s"
                     "%s%s%s%s",
     router->nickname,
     address,
@@ -2446,6 +2514,8 @@ router_dump_router_to_string(routerinfo_t *router,
     (options->DownloadExtraInfo || options->V3AuthoritativeDir) ?
                          "caches-extra-info\n" : "",
     onion_pkey, identity_pkey,
+    rsa_tap_cc_line ? rsa_tap_cc_line : "",
+    ntor_cc_line ? ntor_cc_line : "",
     family_line,
     we_are_hibernating() ? "hibernating 1\n" : "",
     options->HidServDirectoryV2 ? "hidden-service-dir\n" : "",
index c53a104ebc00a007dd26cb0445de9d0beb914882..cb813c68131e22acceafbe4b728dd036a7cf2d1b 100644 (file)
@@ -91,7 +91,9 @@ int router_is_me(const routerinfo_t *router);
 int router_pick_published_address(const or_options_t *options, uint32_t *addr);
 int router_rebuild_descriptor(int force);
 char *router_dump_router_to_string(routerinfo_t *router,
-                                   crypto_pk_t *ident_key,
+                                   const crypto_pk_t *ident_key,
+                                   const crypto_pk_t *tap_key,
+                                   const curve25519_keypair_t *ntor_keypair,
                                    const ed25519_keypair_t *signing_keypair);
 char *router_dump_exit_policy_to_string(const routerinfo_t *router,
                                          int include_ipv4,
index 6609d893118cdf119f4bce22a1d063b689c5f0e5..9ad5a8947964833e9fb95f5353a39e3138c99456 100644 (file)
@@ -435,6 +435,93 @@ get_current_auth_key_cert(void)
   return auth_key_cert;
 }
 
+/** Construct cross-certification for the master identity key with
+ * the ntor onion key. Store the sign of the corresponding ed25519 public key
+ * in *<b>sign_out</b>. */
+tor_cert_t *
+make_ntor_onion_key_crosscert(const curve25519_keypair_t *onion_key,
+      const ed25519_public_key_t *master_id_key, time_t now, time_t lifetime,
+      int *sign_out)
+{
+  tor_cert_t *cert = NULL;
+  ed25519_keypair_t ed_onion_key;
+
+  if (ed25519_keypair_from_curve25519_keypair(&ed_onion_key, sign_out,
+                                              onion_key) < 0)
+    goto end;
+
+  cert = tor_cert_create(&ed_onion_key, CERT_TYPE_ONION_ID, master_id_key,
+      now, lifetime, 0);
+
+ end:
+  memwipe(&ed_onion_key, 0, sizeof(ed_onion_key));
+  return cert;
+}
+
+/** Construct and return an RSA signature for the TAP onion key to
+ * cross-certify the RSA and Ed25519 identity keys. Set <b>len_out</b> to its
+ * length. */
+uint8_t *
+make_tap_onion_key_crosscert(const crypto_pk_t *onion_key,
+                             const ed25519_public_key_t *master_id_key,
+                             const crypto_pk_t *rsa_id_key,
+                             int *len_out)
+{
+  uint8_t signature[PK_BYTES];
+  uint8_t signed_data[DIGEST_LEN + ED25519_PUBKEY_LEN];
+
+  *len_out = 0;
+  crypto_pk_get_digest(rsa_id_key, (char*)signed_data);
+  memcpy(signed_data + DIGEST_LEN, master_id_key->pubkey, ED25519_PUBKEY_LEN);
+
+  int r = crypto_pk_private_sign(onion_key,
+                               (char*)signature, sizeof(signature),
+                               (const char*)signed_data, sizeof(signed_data));
+  if (r < 0)
+    return NULL;
+
+  *len_out = r;
+
+  return tor_memdup(signature, r);
+}
+
+/** Check whether an RSA-TAP cross-certification is correct. Return 0 if it
+ * is, -1 if it isn't. */
+int
+check_tap_onion_key_crosscert(const uint8_t *crosscert,
+                              int crosscert_len,
+                              const crypto_pk_t *onion_pkey,
+                              const ed25519_public_key_t *master_id_pkey,
+                              const uint8_t *rsa_id_digest)
+{
+  uint8_t *cc = tor_malloc(crypto_pk_keysize(onion_pkey));
+  int cc_len =
+    crypto_pk_public_checksig(onion_pkey,
+                              (char*)cc,
+                              crypto_pk_keysize(onion_pkey),
+                              (const char*)crosscert,
+                              crosscert_len);
+  if (cc_len < 0) {
+    goto err;
+  }
+  if (cc_len < DIGEST_LEN + ED25519_PUBKEY_LEN) {
+    log_warn(LD_DIR, "Short signature on cross-certification with TAP key");
+    goto err;
+  }
+  if (tor_memneq(cc, rsa_id_digest, DIGEST_LEN) ||
+      tor_memneq(cc + DIGEST_LEN, master_id_pkey->pubkey,
+                 ED25519_PUBKEY_LEN)) {
+    log_warn(LD_DIR, "Incorrect cross-certification with TAP key");
+    goto err;
+  }
+
+  tor_free(cc);
+  return 0;
+ err:
+  tor_free(cc);
+  return -1;
+}
+
 void
 routerkeys_free_all(void)
 {
index eb21401d56150328478057215957bcc8ba2ab2c9..5e2ef45314dcd1f2b762c0aa5afff26e907f2567 100644 (file)
@@ -37,6 +37,22 @@ const ed25519_keypair_t *get_current_auth_keypair(void);
 const struct tor_cert_st *get_current_link_key_cert(void);
 const struct tor_cert_st *get_current_auth_key_cert(void);
 
+struct tor_cert_st *make_ntor_onion_key_crosscert(
+                                  const curve25519_keypair_t *onion_key,
+                                  const ed25519_public_key_t *master_id_key,
+                                  time_t now, time_t lifetime,
+                                  int *sign_out);
+uint8_t *make_tap_onion_key_crosscert(const crypto_pk_t *onion_key,
+                                  const ed25519_public_key_t *master_id_key,
+                                  const crypto_pk_t *rsa_id_key,
+                                  int *len_out);
+
+int check_tap_onion_key_crosscert(const uint8_t *crosscert,
+                                  int crosscert_len,
+                                  const crypto_pk_t *onion_pkey,
+                                  const ed25519_public_key_t *master_id_pkey,
+                                  const uint8_t *rsa_id_digest);
+
 int load_ed_keys(const or_options_t *options, time_t now);
 void routerkeys_free_all(void);
 
index 069f70d662cd71f78f357cb6b6334035c1cb8932..dbb93e4a64c2f40f56a5f5469fb04d19f92282a8 100644 (file)
@@ -3294,6 +3294,11 @@ router_add_to_routerlist(routerinfo_t *router, const char **msg,
 
   old_router = router_get_mutable_by_digest(id_digest);
 
+  /* Make sure that it isn't expired. */
+  if (router->cert_expiration_time < approx_time()) {
+    return ROUTER_CERTS_EXPIRED;
+  }
+
   /* Make sure that we haven't already got this exact descriptor. */
   if (sdmap_get(routerlist->desc_digest_map,
                 router->cache_info.signed_descriptor_digest)) {
index 78c3fbb880874849a7b019bc697fbee3b7084635..200533fe91d294c804e364d862ad6924a0e9e87f 100644 (file)
@@ -118,13 +118,15 @@ WRA_WAS_ADDED(was_router_added_t s) {
  * - not in the consensus
  * - neither in the consensus nor in any networkstatus document
  * - it was outdated.
+ * - its certificates were expired.
  */
 static INLINE int WRA_WAS_OUTDATED(was_router_added_t s)
 {
   return (s == ROUTER_WAS_TOO_OLD ||
           s == ROUTER_IS_ALREADY_KNOWN ||
           s == ROUTER_NOT_IN_CONSENSUS ||
-          s == ROUTER_NOT_IN_CONSENSUS_OR_NETWORKSTATUS);
+          s == ROUTER_NOT_IN_CONSENSUS_OR_NETWORKSTATUS ||
+          s == ROUTER_CERTS_EXPIRED);
 }
 /** Return true iff the outcome code in <b>s</b> indicates that the descriptor
  * was flat-out rejected. */
@@ -138,7 +140,8 @@ static INLINE int WRA_NEVER_DOWNLOADABLE(was_router_added_t s)
 {
   return (s == ROUTER_AUTHDIR_REJECTS ||
           s == ROUTER_BAD_EI ||
-          s == ROUTER_WAS_TOO_OLD);
+          s == ROUTER_WAS_TOO_OLD ||
+          s == ROUTER_CERTS_EXPIRED);
 }
 was_router_added_t router_add_to_routerlist(routerinfo_t *router,
                                             const char **msg,
index dbd6fdd47202dd82c0124832e78e0814cef71966..b3bb565e584370980be63a680c995ebceda9ea97 100644 (file)
@@ -24,6 +24,7 @@
 #include "microdesc.h"
 #include "networkstatus.h"
 #include "rephist.h"
+#include "routerkeys.h"
 #include "routerparse.h"
 #include "entrynodes.h"
 #include "torcert.h"
@@ -87,6 +88,8 @@ typedef enum {
   K_IPV6_POLICY,
   K_ROUTER_SIG_ED25519,
   K_IDENTITY_ED25519,
+  K_ONION_KEY_CROSSCERT,
+  K_NTOR_ONION_KEY_CROSSCERT,
 
   K_DIRREQ_END,
   K_DIRREQ_V2_IPS,
@@ -299,6 +302,9 @@ static token_rule_t routerdesc_token_table[] = {
   T01("hidden-service-dir",  K_HIDDEN_SERVICE_DIR,  NO_ARGS, NO_OBJ ),
   T01("identity-ed25519",    K_IDENTITY_ED25519,    NO_ARGS, NEED_OBJ ),
   T01("router-sig-ed25519",  K_ROUTER_SIG_ED25519,  GE(1),   NO_OBJ ),
+  T01("onion-key-crosscert", K_ONION_KEY_CROSSCERT, NO_ARGS, NEED_OBJ ),
+  T01("ntor-onion-key-crosscert", K_NTOR_ONION_KEY_CROSSCERT,
+                                                    EQ(1),   NEED_OBJ ),
 
   T01("allow-single-hop-exits",K_ALLOW_SINGLE_HOP_EXITS,    NO_ARGS, NO_OBJ ),
 
@@ -648,7 +654,7 @@ router_get_extrainfo_hash(const char *s, size_t s_len, char *digest)
 char *
 router_get_dirobj_signature(const char *digest,
                             size_t digest_len,
-                            crypto_pk_t *private_key)
+                            const crypto_pk_t *private_key)
 {
   char *signature;
   size_t i, keysize;
@@ -868,8 +874,8 @@ check_signature_token(const char *digest,
     tor_free(signed_digest);
     return -1;
   }
-//  log_debug(LD_DIR,"Signed %s hash starts %s", doctype,
-//            hex_str(signed_digest,4));
+  //  log_debug(LD_DIR,"Signed %s hash starts %s", doctype,
+  //            hex_str(signed_digest,4));
   if (tor_memneq(digest, signed_digest, digest_len)) {
     log_warn(LD_DIR, "Error reading %s: signature does not match.", doctype);
     tor_free(signed_digest);
@@ -1116,6 +1122,7 @@ router_parse_entry_from_string(const char *s, const char *end,
   size_t prepend_len = prepend_annotations ? strlen(prepend_annotations) : 0;
   int ok = 1;
   memarea_t *area = NULL;
+  tor_cert_t *ntor_cc_cert = NULL;
   /* Do not set this to '1' until we have parsed everything that we intend to
    * parse that's covered by the hash. */
   int can_dl_again = 0;
@@ -1191,6 +1198,7 @@ router_parse_entry_from_string(const char *s, const char *end,
   tor_assert(tok->n_args >= 5);
 
   router = tor_malloc_zero(sizeof(routerinfo_t));
+  router->cert_expiration_time = TIME_MAX;
   router->cache_info.routerlist_index = -1;
   router->cache_info.annotations_len = s-start_of_annotations + prepend_len;
   router->cache_info.signed_descriptor_len = end-s;
@@ -1313,16 +1321,30 @@ router_parse_entry_from_string(const char *s, const char *end,
       tor_memdup(&k, sizeof(curve25519_public_key_t));
   }
 
+  tok = find_by_keyword(tokens, K_SIGNING_KEY);
+  router->identity_pkey = tok->key;
+  tok->key = NULL; /* Prevent free */
+  if (crypto_pk_get_digest(router->identity_pkey,
+                           router->cache_info.identity_digest)) {
+    log_warn(LD_DIR, "Couldn't calculate key digest"); goto err;
+  }
+
   {
-    directory_token_t *ed_sig_tok, *ed_cert_tok;
+    directory_token_t *ed_sig_tok, *ed_cert_tok, *cc_tap_tok, *cc_ntor_tok;
     ed_sig_tok = find_opt_by_keyword(tokens, K_ROUTER_SIG_ED25519);
     ed_cert_tok = find_opt_by_keyword(tokens, K_IDENTITY_ED25519);
-    if (!ed_sig_tok != !ed_cert_tok) {
-      log_warn(LD_DIR, "Router descriptor with only partial ed25519 support");
+    cc_tap_tok = find_opt_by_keyword(tokens, K_ONION_KEY_CROSSCERT);
+    cc_ntor_tok = find_opt_by_keyword(tokens, K_NTOR_ONION_KEY_CROSSCERT);
+    int n_ed_toks = !!ed_sig_tok + !!ed_cert_tok +
+      !!cc_tap_tok + !!cc_ntor_tok;
+    if ((n_ed_toks != 0 && n_ed_toks != 4) ||
+        (n_ed_toks == 4 && !router->onion_curve25519_pkey)) {
+      log_warn(LD_DIR, "Router descriptor with only partial ed25519/"
+               "cross-certification support");
       goto err;
     }
     if (ed_sig_tok) {
-      tor_assert(ed_cert_tok);
+      tor_assert(ed_cert_tok && cc_tap_tok && cc_ntor_tok);
       if (ed_cert_tok != smartlist_get(tokens, 0) &&
           ed_cert_tok != smartlist_get(tokens, 1)) {
         log_warn(LD_DIR, "Ed25519 certificate in wrong position");
@@ -1336,10 +1358,25 @@ router_parse_entry_from_string(const char *s, const char *end,
         log_warn(LD_DIR, "Wrong object type on identity-ed25519 in decriptor");
         goto err;
       }
+      if (strcmp(cc_ntor_tok->object_type, "ED25519 CERT")) {
+        log_warn(LD_DIR, "Wrong object type on ntor-onion-key-crosscert "
+                 "in decriptor");
+        goto err;
+      }
+      if (strcmp(cc_tap_tok->object_type, "CROSSCERT")) {
+        log_warn(LD_DIR, "Wrong object type on onion-key-crosscert "
+                 "in decriptor");
+        goto err;
+      }
+      if (strcmp(cc_ntor_tok->args[0], "0") &&
+          strcmp(cc_ntor_tok->args[0], "1")) {
+        log_warn(LD_DIR, "Bad sign bit on ntor-onion-key-crosscert");
+        goto err;
+      }
+      int ntor_cc_sign_bit = !strcmp(cc_ntor_tok->args[0], "1");
 
       uint8_t d256[DIGEST256_LEN];
       const char *signed_start, *signed_end;
-
       tor_cert_t *cert = tor_cert_parse(
                        (const uint8_t*)ed_cert_tok->object_body,
                        ed_cert_tok->object_size);
@@ -1347,13 +1384,33 @@ router_parse_entry_from_string(const char *s, const char *end,
         log_warn(LD_DIR, "Couldn't parse ed25519 cert");
         goto err;
       }
-      router->signing_key_cert = cert;
+      router->signing_key_cert = cert; /* makes sure it gets freed. */
       if (cert->cert_type != CERT_TYPE_ID_SIGNING ||
           ! cert->signing_key_included) {
         log_warn(LD_DIR, "Invalid form for ed25519 cert");
         goto err;
       }
 
+      ntor_cc_cert = tor_cert_parse((const uint8_t*)cc_ntor_tok->object_body,
+                                    cc_ntor_tok->object_size);
+      if (!cc_ntor_tok) {
+        log_warn(LD_DIR, "Couldn't parse ntor-onion-key-crosscert cert");
+        goto err;
+      }
+      if (ntor_cc_cert->cert_type != CERT_TYPE_ONION_ID ||
+          ! ed25519_pubkey_eq(&ntor_cc_cert->signed_key, &cert->signing_key)) {
+        log_warn(LD_DIR, "Invalid contents for ntor-onion-key-crosscert cert");
+        goto err;
+      }
+
+      ed25519_public_key_t ntor_cc_pk;
+      if (ed25519_public_key_from_curve25519_public_key(&ntor_cc_pk,
+                                            router->onion_curve25519_pkey,
+                                            ntor_cc_sign_bit)<0) {
+        log_warn(LD_DIR, "Error converting onion key to ed25519");
+        goto err;
+      }
+
       if (router_get_hash_impl_helper(s, end-s, "router ",
                                       "\nrouter-sig-ed25519",
                                       ' ', &signed_start, &signed_end) < 0) {
@@ -1367,38 +1424,48 @@ router_parse_entry_from_string(const char *s, const char *end,
       crypto_digest_get_digest(d, (char*)d256, sizeof(d256));
       crypto_digest_free(d);
 
-      ed25519_checkable_t check[2];
-      int check_ok[2];
+      ed25519_checkable_t check[3];
+      int check_ok[3];
       if (tor_cert_get_checkable_sig(&check[0], cert, NULL) < 0) {
         log_err(LD_BUG, "Couldn't create 'checkable' for cert.");
         goto err;
       }
-      if (ed25519_signature_from_base64(&check[1].signature,
+      if (tor_cert_get_checkable_sig(&check[1],
+                                     ntor_cc_cert, &ntor_cc_pk) < 0) {
+        log_err(LD_BUG, "Couldn't create 'checkable' for ntor_cc_cert.");
+        goto err;
+      }
+
+      if (ed25519_signature_from_base64(&check[2].signature,
                                         ed_sig_tok->args[0])<0) {
         log_warn(LD_DIR, "Couldn't decode ed25519 signature");
         goto err;
       }
-      check[1].pubkey = &cert->signed_key;
-      check[1].msg = d256;
-      check[1].len = DIGEST256_LEN;
+      check[2].pubkey = &cert->signed_key;
+      check[2].msg = d256;
+      check[2].len = DIGEST256_LEN;
 
-      if (ed25519_checksig_batch(check_ok, check, 2) < 0) {
-        log_warn(LD_DIR, "Incorrect ed25519 signatures");
+      if (ed25519_checksig_batch(check_ok, check, 3) < 0) {
+        log_warn(LD_DIR, "Incorrect ed25519 signature(s)");
         goto err;
       }
-      if (cert->valid_until < time(NULL)) {
-        log_warn(LD_DIR, "Expired ed25519 certificate in router descriptor");
+
+      if (check_tap_onion_key_crosscert(
+                      (const uint8_t*)cc_tap_tok->object_body,
+                      (int)cc_tap_tok->object_size,
+                      router->onion_pkey,
+                      &cert->signing_key,
+                      (const uint8_t*)router->cache_info.identity_digest)<0) {
+        log_warn(LD_DIR, "Incorrect TAP cross-verification");
         goto err;
       }
-    }
-  }
 
-  tok = find_by_keyword(tokens, K_SIGNING_KEY);
-  router->identity_pkey = tok->key;
-  tok->key = NULL; /* Prevent free */
-  if (crypto_pk_get_digest(router->identity_pkey,
-                           router->cache_info.identity_digest)) {
-    log_warn(LD_DIR, "Couldn't calculate key digest"); goto err;
+      /* We check this before adding it to the routerlist. */
+      if (cert->valid_until < ntor_cc_cert->valid_until)
+        router->cert_expiration_time = cert->valid_until;
+      else
+        router->cert_expiration_time = ntor_cc_cert->valid_until;
+    }
   }
 
   if ((tok = find_opt_by_keyword(tokens, K_FINGERPRINT))) {
@@ -1527,6 +1594,7 @@ router_parse_entry_from_string(const char *s, const char *end,
   routerinfo_free(router);
   router = NULL;
  done:
+  tor_cert_free(ntor_cc_cert);
   if (tokens) {
     SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
     smartlist_free(tokens);
index 52e53a833cc65ff6831fb1168d569527fcc0343b..7776063f03bc091e67a1c9f50c322474c4363599 100644 (file)
@@ -19,7 +19,7 @@ int router_get_extrainfo_hash(const char *s, size_t s_len, char *digest);
 #define DIROBJ_MAX_SIG_LEN 256
 char *router_get_dirobj_signature(const char *digest,
                                   size_t digest_len,
-                                  crypto_pk_t *private_key);
+                                  const crypto_pk_t *private_key);
 int router_append_dirobj_signature(char *buf, size_t buf_len,
                                    const char *digest,
                                    size_t digest_len,
index 644cbf812df1e241ecd294b6045f9c2af7cf7a8f..ae9361ff1625422c96212971e4c8586293da4216 100644 (file)
@@ -11,6 +11,7 @@
 #define CERT_TYPE_ID_SIGNING    0x04
 #define CERT_TYPE_SIGNING_LINK  0x05
 #define CERT_TYPE_SIGNING_AUTH  0x06
+#define CERT_TYPE_ONION_ID      0x0A
 
 #define CERT_FLAG_INCLUDE_SIGNING_KEY 0x1
 
index 85ca40f3de03bc2778c6a057b5ce64c3c3e4c9c9..d7b5e1b7c47a27edcaa85de8e3bb070015660709 100644 (file)
@@ -21,6 +21,7 @@
 #include "hibernate.h"
 #include "networkstatus.h"
 #include "router.h"
+#include "routerkeys.h"
 #include "routerlist.h"
 #include "routerparse.h"
 #include "test.h"
@@ -89,6 +90,8 @@ test_dir_formats(void *arg)
   routerinfo_t *rp1 = NULL, *rp2 = NULL;
   addr_policy_t *ex1, *ex2;
   routerlist_t *dir1 = NULL, *dir2 = NULL;
+  uint8_t *rsa_cc = NULL;
+  tor_cert_t *ntor_cc = NULL;
   or_options_t *options = get_options_mutable();
   const addr_policy_t *p;
   time_t now = time(NULL);
@@ -152,8 +155,10 @@ test_dir_formats(void *arg)
   r2->dir_port = 0;
   r2->onion_pkey = crypto_pk_dup_key(pk2);
   r2->onion_curve25519_pkey = tor_malloc_zero(sizeof(curve25519_public_key_t));
-  curve25519_public_from_base64(r2->onion_curve25519_pkey,
-                                "skyinAnvardNostarsNomoonNowindormistsorsnow");
+  curve25519_keypair_t r2_onion_keypair;
+  curve25519_keypair_generate(&r2_onion_keypair, 0);
+  r2->onion_curve25519_pkey = tor_memdup(&r2_onion_keypair.pubkey,
+                                         sizeof(curve25519_public_key_t));
   r2->identity_pkey = crypto_pk_dup_key(pk1);
   r2->bandwidthrate = r2->bandwidthburst = r2->bandwidthcapacity = 3000;
   r2->exit_policy = smartlist_new();
@@ -169,7 +174,7 @@ test_dir_formats(void *arg)
   /* XXXX025 router_dump_to_string should really take this from ri.*/
   options->ContactInfo = tor_strdup("Magri White "
                                     "<magri@elsewhere.example.com>");
-  buf = router_dump_router_to_string(r1, pk2, NULL);
+  buf = router_dump_router_to_string(r1, pk2, NULL, NULL, NULL);
   tor_free(options->ContactInfo);
   tt_assert(buf);
 
@@ -202,7 +207,7 @@ test_dir_formats(void *arg)
   tt_str_op(buf,OP_EQ, buf2);
   tor_free(buf);
 
-  buf = router_dump_router_to_string(r1, pk2, NULL);
+  buf = router_dump_router_to_string(r1, pk2, NULL, NULL, NULL);
   tt_assert(buf);
   cp = buf;
   rp1 = router_parse_entry_from_string((const char*)cp,NULL,1,0,NULL,NULL);
@@ -238,20 +243,49 @@ test_dir_formats(void *arg)
   strlcat(buf2, pk2_str, sizeof(buf2));
   strlcat(buf2, "signing-key\n", sizeof(buf2));
   strlcat(buf2, pk1_str, sizeof(buf2));
+  int rsa_cc_len;
+  rsa_cc = make_tap_onion_key_crosscert(pk2,
+                                        &kp1.pubkey,
+                                        pk1,
+                                        &rsa_cc_len);
+  tt_assert(rsa_cc);
+  base64_encode(cert_buf, sizeof(cert_buf), (char*)rsa_cc, rsa_cc_len);
+  strlcat(buf2, "onion-key-crosscert\n"
+          "-----BEGIN CROSSCERT-----\n", sizeof(buf2));
+  strlcat(buf2, cert_buf, sizeof(buf2));
+  strlcat(buf2, "-----END CROSSCERT-----\n", sizeof(buf2));
+  int ntor_cc_sign;
+  ntor_cc = make_ntor_onion_key_crosscert(&r2_onion_keypair,
+                                          &kp1.pubkey,
+                                          r2->cache_info.published_on,
+                                          MIN_ONION_KEY_LIFETIME,
+                                          &ntor_cc_sign);
+  tt_assert(ntor_cc);
+  base64_encode(cert_buf, sizeof(cert_buf),
+                (char*)ntor_cc->encoded, ntor_cc->encoded_len);
+  tor_snprintf(buf2+strlen(buf2), sizeof(buf2)-strlen(buf2),
+               "ntor-onion-key-crosscert %d\n"
+               "-----BEGIN ED25519 CERT-----\n"
+               "%s"
+               "-----END ED25519 CERT-----\n", ntor_cc_sign, cert_buf);
+
   strlcat(buf2, "hidden-service-dir\n", sizeof(buf2));
-  strlcat(buf2, "ntor-onion-key "
-          "skyinAnvardNostarsNomoonNowindormistsorsnow=\n", sizeof(buf2));
+  strlcat(buf2, "ntor-onion-key ", sizeof(buf2));
+  base64_encode(cert_buf, sizeof(cert_buf),
+                (const char*)r2_onion_keypair.pubkey.public_key, 32);
+  strlcat(buf2, cert_buf, sizeof(buf2));
   strlcat(buf2, "accept *:80\nreject 18.0.0.0/8:24\n", sizeof(buf2));
   strlcat(buf2, "router-sig-ed25519 ", sizeof(buf2));
 
-  buf = router_dump_router_to_string(r2, pk1, &kp2);
+  buf = router_dump_router_to_string(r2, pk1, pk2, &r2_onion_keypair, &kp2);
   tt_assert(buf);
   buf[strlen(buf2)] = '\0'; /* Don't compare the sig; it's never the same
                              * twice */
-  tt_str_op(buf,OP_EQ, buf2);
+
+  tt_str_op(buf, OP_EQ, buf2);
   tor_free(buf);
 
-  buf = router_dump_router_to_string(r2, pk1, NULL);
+  buf = router_dump_router_to_string(r2, pk1, NULL, NULL, NULL);
   cp = buf;
   rp2 = router_parse_entry_from_string((const char*)cp,NULL,1,0,NULL,NULL);
   tt_assert(rp2);
@@ -304,6 +338,7 @@ test_dir_formats(void *arg)
   if (rp2)
     routerinfo_free(rp2);
 
+  tor_free(rsa_cc);
   tor_free(buf);
   tor_free(pk1_str);
   tor_free(pk2_str);
@@ -1418,6 +1453,7 @@ generate_ri_from_rs(const vote_routerstatus_t *vrs)
   static time_t published = 0;
 
   r = tor_malloc_zero(sizeof(routerinfo_t));
+  r->cert_expiration_time = TIME_MAX;
   memcpy(r->cache_info.identity_digest, rs->identity_digest, DIGEST_LEN);
   memcpy(r->cache_info.signed_descriptor_digest, rs->descriptor_digest,
          DIGEST_LEN);
index 8780213a298e126bcda5e3782f4fef5b78c694d7..2434255b6348d538a8b441b709a76a13d3ddfaf4 100644 (file)
@@ -514,6 +514,74 @@ test_routerkeys_ed_keys_init_all(void *arg)
   routerkeys_free_all();
 }
 
+static void
+test_routerkeys_cross_certify_ntor(void *args)
+{
+  (void) args;
+
+  tor_cert_t *cert = NULL;
+  curve25519_keypair_t onion_keys;
+  ed25519_public_key_t master_key;
+  ed25519_public_key_t onion_check_key;
+  time_t now = time(NULL);
+  int sign;
+
+  tt_int_op(0, ==, ed25519_public_from_base64(&master_key,
+                               "IamwritingthesetestsOnARainyAfternoonin2014"));
+  tt_int_op(0, ==, curve25519_keypair_generate(&onion_keys, 0));
+  cert = make_ntor_onion_key_crosscert(&onion_keys,
+                                       &master_key,
+                                       now, 10000,
+                                       &sign);
+  tt_assert(cert);
+  tt_assert(sign == 0 || sign == 1);
+  tt_int_op(cert->cert_type, ==, CERT_TYPE_ONION_ID);
+  tt_int_op(1, ==, ed25519_pubkey_eq(&cert->signed_key, &master_key));
+  tt_int_op(0, ==, ed25519_public_key_from_curve25519_public_key(
+                               &onion_check_key, &onion_keys.pubkey, sign));
+  tt_int_op(0, ==, tor_cert_checksig(cert, &onion_check_key, now));
+
+ done:
+  tor_cert_free(cert);
+}
+
+static void
+test_routerkeys_cross_certify_tap(void *args)
+{
+  (void)args;
+  uint8_t *cc = NULL;
+  int cc_len;
+  ed25519_public_key_t master_key;
+  crypto_pk_t *onion_key = pk_generate(2), *id_key = pk_generate(1);
+  char digest[20];
+  char buf[128];
+  int n;
+
+  tt_int_op(0, ==, ed25519_public_from_base64(&master_key,
+                               "IAlreadyWroteTestsForRouterdescsUsingTheseX"));
+
+  cc = make_tap_onion_key_crosscert(onion_key,
+                                    &master_key,
+                                    id_key, &cc_len);
+  tt_assert(cc);
+  tt_assert(cc_len);
+
+  n = crypto_pk_public_checksig(onion_key, buf, sizeof(buf),
+                                (char*)cc, cc_len);
+  tt_int_op(n,>,0);
+  tt_int_op(n,==,52);
+
+  crypto_pk_get_digest(id_key, digest);
+  tt_mem_op(buf,==,digest,20);
+  tt_mem_op(buf+20,==,master_key.pubkey,32);
+
+  tt_int_op(0, ==, check_tap_onion_key_crosscert(cc, cc_len,
+                                    onion_key, &master_key, (uint8_t*)digest));
+
+ done:
+  tor_free(cc);
+}
+
 #define TEST(name, flags)                                       \
   { #name , test_routerkeys_ ## name, (flags), NULL, NULL }
 
@@ -524,6 +592,8 @@ struct testcase_t routerkeys_tests[] = {
   TEST(ed_key_init_basic, TT_FORK),
   TEST(ed_key_init_split, TT_FORK),
   TEST(ed_keys_init_all, TT_FORK),
+  TEST(cross_certify_ntor, 0),
+  TEST(cross_certify_tap, 0),
   END_OF_TESTCASES
 };