Browse Source

Merge branch 'tls_ecdhe_rebased_v2'

Nick Mathewson 12 years ago
parent
commit
3eb3900510
10 changed files with 480 additions and 35 deletions
  1. 3 0
      changes/dh_benchmarks
  2. 26 0
      changes/tls_ecdhe
  3. 6 0
      doc/tor.1.txt
  4. 309 30
      src/common/tortls.c
  5. 7 1
      src/common/tortls.h
  6. 10 0
      src/or/config.c
  7. 6 3
      src/or/connection_or.c
  8. 2 0
      src/or/or.h
  9. 12 1
      src/or/router.c
  10. 99 0
      src/test/bench.c

+ 3 - 0
changes/dh_benchmarks

@@ -0,0 +1,3 @@
+  o Minor features (testing):
+    - Add benchmarks for DH (1024-bit multiplicative group) and ECDH
+      (P-256) diffie-hellman handshakes to src/or/bench.

+ 26 - 0
changes/tls_ecdhe

@@ -0,0 +1,26 @@
+  o Major features:
+
+    - Servers can now enable the ECDHE TLS ciphersuites when available
+      and appropriate. These ciphersuites let us negotiate forward-
+      secure TLS secret keys more safely and more efficiently than with
+      our previous use of Diffie Hellman modulo a 1024-bit prime.
+      By default, public servers prefer the (faster) P224 group, and
+      bridges prefer the (more common) P256 group; you can override this
+      with the TLSECGroup option.
+
+      Enabling these ciphers was a little tricky, since for a long
+      time, clients had been claiming to support them without
+      actually doing so, in order to foil fingerprinting. But with
+      the client-side implementation of proposal 198 in
+      0.2.3.17-beta, clients can now match the ciphers from recent
+      firefox versions *and* list the ciphers they actually mean, so
+      servers can believe such clients when they advertise ECDHE
+      support in their TLS ClientHello messages.
+
+      This feature requires clients running 0.2.3.17-beta or later,
+      and requires both sides to be running OpenSSL 1.0.0 or later
+      with ECC support. OpenSSL 1.0.1, with the compile-time option
+      "enable-ec_nistp_64_gcc_128", is highly recommended.
+      Implements the server side of proposal 198; closes ticket
+      7200.
+

+ 6 - 0
doc/tor.1.txt

@@ -1527,6 +1527,12 @@ is non-zero):
 **GeoIPv6File** __filename__::
     A filename containing IPv6 GeoIP data, for use with by-country statistics.
 
+**TLSECGroup** **P224**|**P256**::
+    What EC group should we try to use for incoming TLS connections?
+    P224 is faster, but makes us stand out more. Has no effect if
+    we're a client, or if our OpenSSL version lacks support for ECDHE.
+    (Default: P224 for public servers; P256 for bridges.)
+
 **CellStatistics** **0**|**1**::
     When this option is enabled, Tor writes statistics on the mean time that
     cells spend in circuit queues to disk every 24 hours. (Default: 0)

+ 309 - 30
src/common/tortls.c

@@ -127,6 +127,24 @@ typedef struct tor_tls_context_t {
   crypto_pk_t *auth_key;
 } tor_tls_context_t;
 
+/** Return values for tor_tls_classify_client_ciphers.
+ *
+ * @{
+ */
+/** An error occurred when examining the client ciphers */
+#define CIPHERS_ERR -1
+/** The client cipher list indicates that a v1 handshake was in use. */
+#define CIPHERS_V1 1
+/** The client cipher list indicates that the client is using the v2 or the
+ * v3 handshake, but that it is (probably!) lying about what ciphers it
+ * supports */
+#define CIPHERS_V2 2
+/** The client cipher list indicates that the client is using the v2 or the
+ * v3 handshake, and that it is telling the truth about what ciphers it
+ * supports */
+#define CIPHERS_UNRESTRICTED 3
+/** @} */
+
 #define TOR_TLS_MAGIC 0x71571571
 
 /** Holds a SSL object and its associated data.  Members are only
@@ -152,6 +170,9 @@ struct tor_tls_t {
                                   * one certificate). */
   /** True iff we should call negotiated_callback when we're done reading. */
   unsigned int got_renegotiate:1;
+  /** Return value from tor_tls_classify_client_ciphers, or 0 if we haven't
+   * called that function yet. */
+  int8_t client_cipher_list_type;
   /** Incremented every time we start the server side of a handshake. */
   uint8_t server_handshake_count;
   size_t wantwrite_n; /**< 0 normally, >0 if we returned wantwrite last
@@ -215,9 +236,11 @@ static X509* tor_tls_create_certificate(crypto_pk_t *rsa,
 static int tor_tls_context_init_one(tor_tls_context_t **ppcontext,
                                     crypto_pk_t *identity,
                                     unsigned int key_lifetime,
+                                    unsigned int flags,
                                     int is_client);
 static tor_tls_context_t *tor_tls_context_new(crypto_pk_t *identity,
                                               unsigned int key_lifetime,
+                                              unsigned int flags,
                                               int is_client);
 static int check_cert_lifetime_internal(int severity, const X509 *cert,
                                    int past_tolerance, int future_tolerance);
@@ -505,6 +528,37 @@ tor_tls_init(void)
                SSLeay_version(SSLEAY_VERSION), version);
     }
 
+#if (SIZEOF_VOID_P >= 8 &&                              \
+     !defined(OPENSSL_NO_EC) &&                         \
+     OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,0,1))
+    if (version >= OPENSSL_V_SERIES(1,0,1)) {
+      /* Warn if we could *almost* be running with much faster ECDH.
+         If we're built for a 64-bit target, using OpenSSL 1.0.1, but we
+         don't have one of the built-in __uint128-based speedups, we are
+         just one build operation away from an accelerated handshake.
+
+         (We could be looking at OPENSSL_NO_EC_NISTP_64_GCC_128 instead of
+          doing this test, but that gives compile-time options, not runtime
+          behavior.)
+      */
+      EC_KEY *key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
+      const EC_GROUP *g = key ? EC_KEY_get0_group(key) : NULL;
+      const EC_METHOD *m = g ? EC_GROUP_method_of(g) : NULL;
+      const int warn = (m == EC_GFp_simple_method() ||
+                        m == EC_GFp_mont_method() ||
+                        m == EC_GFp_nist_method());
+      EC_KEY_free(key);
+
+      if (warn)
+        log_notice(LD_GENERAL, "We were built to run on a 64-bit CPU, with "
+                   "OpenSSL 1.0.1 or later, but with a version of OpenSSL "
+                   "that apparently lacks accelerated support for the NIST "
+                   "P-224 and P-256 groups. Building openssl with such "
+                   "support (using the enable-ec_nistp_64_gcc_128 option "
+                   "when configuring it) would make ECDH much faster.");
+    }
+#endif
+
     tor_tls_allocate_tor_tls_object_ex_data_index();
 
     tls_library_is_initialized = 1;
@@ -657,11 +711,42 @@ tor_tls_create_certificate(crypto_pk_t *rsa,
 #undef SERIAL_NUMBER_SIZE
 }
 
-/** List of ciphers that servers should select from.*/
+/** List of ciphers that servers should select from when the client might be
+ * claiming extra unsupported ciphers in order to avoid fingerprinting.  */
 #define SERVER_CIPHER_LIST                         \
   (TLS1_TXT_DHE_RSA_WITH_AES_256_SHA ":"           \
    TLS1_TXT_DHE_RSA_WITH_AES_128_SHA ":"           \
    SSL3_TXT_EDH_RSA_DES_192_CBC3_SHA)
+
+/** List of ciphers that servers should select from when we actually have
+ * our choice of what cipher to use. */
+const char UNRESTRICTED_SERVER_CIPHER_LIST[] =
+#ifdef TLS1_TXT_ECDHE_RSA_WITH_AES_256_CHC_SHA
+       TLS1_TXT_ECDHE_RSA_WITH_AES_256_CBC_SHA ":"
+#endif
+#ifdef TLS1_TXT_ECDHE_RSA_WITH_AES_256_GCM_SHA384
+       TLS1_TXT_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ":"
+#endif
+#ifdef TLS1_TXT_ECDHE_RSA_WITH_AES_128_SHA256
+       TLS1_TXT_ECDHE_RSA_WITH_AES_128_SHA256 ":"
+#endif
+#ifdef TLS1_TXT_ECDHE_RSA_WITH_AES_128_CBC_SHA
+       TLS1_TXT_ECDHE_RSA_WITH_AES_128_CBC_SHA ":"
+#endif
+#ifdef TLS1_TXT_ECDHE_RSA_WITH_AES_128_GCM_SHA256
+       TLS1_TXT_ECDHE_RSA_WITH_AES_128_GCM_SHA256
+#endif
+//#if TLS1_TXT_ECDHE_RSA_WITH_RC4_128_SHA
+//    TLS1_TXT_ECDHE_RSA_WITH_RC4_128_SHA ":"
+//#endif
+  /* These next two are mandatory. */
+  TLS1_TXT_DHE_RSA_WITH_AES_256_SHA ":"
+  TLS1_TXT_DHE_RSA_WITH_AES_128_SHA ":"
+#ifdef TLS1_TXT_ECDHE_RSA_WITH_DES_192_CBC3_SHA
+       TLS1_TXT_ECDHE_RSA_WITH_DES_192_CBC3_SHA ":"
+#endif
+  SSL3_TXT_EDH_RSA_DES_192_CBC3_SHA;
+
 /* Note: to set up your own private testing network with link crypto
  * disabled, set your Tors' cipher list to
  * (SSL3_TXT_RSA_NULL_SHA).  If you do this, you won't be able to communicate
@@ -1014,17 +1099,20 @@ tor_tls_context_incref(tor_tls_context_t *ctx)
 /** Create new global client and server TLS contexts.
  *
  * If <b>server_identity</b> is NULL, this will not generate a server
- * TLS context. If <b>is_public_server</b> is non-zero, this will use
+ * TLS context. If TOR_TLS_CTX_IS_PUBLIC_SERVER is set in <b>flags</b>, use
  * the same TLS context for incoming and outgoing connections, and
- * ignore <b>client_identity</b>. */
+ * ignore <b>client_identity</b>. If one of TOR_TLS_CTX_USE_ECDHE_P{224,256}
+ * is set in <b>flags</b>, use that ECDHE group if possible; otherwise use
+ * the default ECDHE group. */
 int
-tor_tls_context_init(int is_public_server,
+tor_tls_context_init(unsigned flags,
                      crypto_pk_t *client_identity,
                      crypto_pk_t *server_identity,
                      unsigned int key_lifetime)
 {
   int rv1 = 0;
   int rv2 = 0;
+  const int is_public_server = flags & TOR_TLS_CTX_IS_PUBLIC_SERVER;
 
   if (is_public_server) {
     tor_tls_context_t *new_ctx;
@@ -1034,7 +1122,7 @@ tor_tls_context_init(int is_public_server,
 
     rv1 = tor_tls_context_init_one(&server_tls_context,
                                    server_identity,
-                                   key_lifetime, 0);
+                                   key_lifetime, flags, 0);
 
     if (rv1 >= 0) {
       new_ctx = server_tls_context;
@@ -1051,6 +1139,7 @@ tor_tls_context_init(int is_public_server,
       rv1 = tor_tls_context_init_one(&server_tls_context,
                                      server_identity,
                                      key_lifetime,
+                                     flags,
                                      0);
     } else {
       tor_tls_context_t *old_ctx = server_tls_context;
@@ -1064,6 +1153,7 @@ tor_tls_context_init(int is_public_server,
     rv2 = tor_tls_context_init_one(&client_tls_context,
                                    client_identity,
                                    key_lifetime,
+                                   flags,
                                    1);
   }
 
@@ -1080,10 +1170,12 @@ static int
 tor_tls_context_init_one(tor_tls_context_t **ppcontext,
                          crypto_pk_t *identity,
                          unsigned int key_lifetime,
+                         unsigned int flags,
                          int is_client)
 {
   tor_tls_context_t *new_ctx = tor_tls_context_new(identity,
                                                    key_lifetime,
+                                                   flags,
                                                    is_client);
   tor_tls_context_t *old_ctx = *ppcontext;
 
@@ -1107,7 +1199,7 @@ tor_tls_context_init_one(tor_tls_context_t **ppcontext,
  */
 static tor_tls_context_t *
 tor_tls_context_new(crypto_pk_t *identity, unsigned int key_lifetime,
-                    int is_client)
+                    unsigned flags, int is_client)
 {
   crypto_pk_t *rsa = NULL, *rsa_auth = NULL;
   EVP_PKEY *pkey = NULL;
@@ -1224,6 +1316,7 @@ tor_tls_context_new(crypto_pk_t *identity, unsigned int key_lifetime,
   }
 
   SSL_CTX_set_options(result->ctx, SSL_OP_SINGLE_DH_USE);
+  SSL_CTX_set_options(result->ctx, SSL_OP_SINGLE_ECDH_USE);
 
 #ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
   SSL_CTX_set_options(result->ctx,
@@ -1274,6 +1367,26 @@ tor_tls_context_new(crypto_pk_t *identity, unsigned int key_lifetime,
     SSL_CTX_set_tmp_dh(result->ctx, crypto_dh_get_dh_(dh));
     crypto_dh_free(dh);
   }
+#if (!defined(OPENSSL_NO_EC) &&                         \
+     OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,0,0))
+  if (! is_client) {
+    int nid;
+    EC_KEY *ec_key;
+    if (flags & TOR_TLS_CTX_USE_ECDHE_P224)
+      nid = NID_secp224r1;
+    else if (flags & TOR_TLS_CTX_USE_ECDHE_P256)
+      nid = NID_X9_62_prime256v1;
+    else if (flags & TOR_TLS_CTX_IS_PUBLIC_SERVER)
+      nid = NID_X9_62_prime256v1;
+    else
+      nid = NID_secp224r1;
+    /* Use P-256 for ECDHE. */
+    ec_key = EC_KEY_new_by_curve_name(nid);
+    if (ec_key != NULL) /*XXXX Handle errors? */
+      SSL_CTX_set_tmp_ecdh(result->ctx, ec_key);
+    EC_KEY_free(ec_key);
+  }
+#endif
   SSL_CTX_set_verify(result->ctx, SSL_VERIFY_PEER,
                      always_accept_verify_cb);
   /* let us realloc bufs that we're writing from */
@@ -1310,28 +1423,108 @@ tor_tls_context_new(crypto_pk_t *identity, unsigned int key_lifetime,
 }
 
 #ifdef V2_HANDSHAKE_SERVER
-/** Return true iff the cipher list suggested by the client for <b>ssl</b> is
- * a list that indicates that the client knows how to do the v2 TLS connection
- * handshake. */
+
+/* Here's the old V2 cipher list we sent from 0.2.1.1-alpha up to
+ * 0.2.3.17-beta. If a client is using this list, we can't believe the ciphers
+ * that it claims to support.  We'll prune this list to remove the ciphers
+ * *we* don't recognize. */
+static uint16_t v2_cipher_list[] = {
+  0xc00a, /* TLS1_TXT_ECDHE_ECDSA_WITH_AES_256_CBC_SHA */
+  0xc014, /* TLS1_TXT_ECDHE_RSA_WITH_AES_256_CBC_SHA */
+  0x0039, /* TLS1_TXT_DHE_RSA_WITH_AES_256_SHA */
+  0x0038, /* TLS1_TXT_DHE_DSS_WITH_AES_256_SHA */
+  0xc00f, /* TLS1_TXT_ECDH_RSA_WITH_AES_256_CBC_SHA */
+  0xc005, /* TLS1_TXT_ECDH_ECDSA_WITH_AES_256_CBC_SHA */
+  0x0035, /* TLS1_TXT_RSA_WITH_AES_256_SHA */
+  0xc007, /* TLS1_TXT_ECDHE_ECDSA_WITH_RC4_128_SHA */
+  0xc009, /* TLS1_TXT_ECDHE_ECDSA_WITH_AES_128_CBC_SHA */
+  0xc011, /* TLS1_TXT_ECDHE_RSA_WITH_RC4_128_SHA */
+  0xc013, /* TLS1_TXT_ECDHE_RSA_WITH_AES_128_CBC_SHA */
+  0x0033, /* TLS1_TXT_DHE_RSA_WITH_AES_128_SHA */
+  0x0032, /* TLS1_TXT_DHE_DSS_WITH_AES_128_SHA */
+  0xc00c, /* TLS1_TXT_ECDH_RSA_WITH_RC4_128_SHA */
+  0xc00e, /* TLS1_TXT_ECDH_RSA_WITH_AES_128_CBC_SHA */
+  0xc002, /* TLS1_TXT_ECDH_ECDSA_WITH_RC4_128_SHA */
+  0xc004, /* TLS1_TXT_ECDH_ECDSA_WITH_AES_128_CBC_SHA */
+  0x0004, /* SSL3_TXT_RSA_RC4_128_MD5 */
+  0x0005, /* SSL3_TXT_RSA_RC4_128_SHA */
+  0x002f, /* TLS1_TXT_RSA_WITH_AES_128_SHA */
+  0xc008, /* TLS1_TXT_ECDHE_ECDSA_WITH_DES_192_CBC3_SHA */
+  0xc012, /* TLS1_TXT_ECDHE_RSA_WITH_DES_192_CBC3_SHA */
+  0x0016, /* SSL3_TXT_EDH_RSA_DES_192_CBC3_SHA */
+  0x0013, /* SSL3_TXT_EDH_DSS_DES_192_CBC3_SHA */
+  0xc00d, /* TLS1_TXT_ECDH_RSA_WITH_DES_192_CBC3_SHA */
+  0xc003, /* TLS1_TXT_ECDH_ECDSA_WITH_DES_192_CBC3_SHA */
+  0xfeff, /* SSL3_TXT_RSA_FIPS_WITH_3DES_EDE_CBC_SHA */
+  0x000a, /* SSL3_TXT_RSA_DES_192_CBC3_SHA */
+  0
+};
+/** Have we removed the unrecognized ciphers from v2_cipher_list yet? */
+static int v2_cipher_list_pruned = 0;
+
+/** Remove from v2_cipher_list every cipher that we don't support, so that
+ * comparing v2_cipher_list to a client's cipher list will give a sensible
+ * result. */
+static void
+prune_v2_cipher_list(void)
+{
+  uint16_t *inp, *outp;
+  const SSL_METHOD *m = SSLv23_method();
+
+  inp = outp = v2_cipher_list;
+  while (*inp) {
+    unsigned char cipherid[2];
+    const SSL_CIPHER *cipher;
+    /* Is there no better way to do this? */
+    set_uint16(cipherid, htons(*inp));
+    cipher = m->get_cipher_by_char(cipherid);
+    if (cipher) {
+      tor_assert((cipher->id & 0xffff) == *inp);
+      *outp++ = *inp++;
+    } else {
+      inp++;
+    }
+  }
+  *outp = 0;
+
+  v2_cipher_list_pruned = 1;
+}
+
+/* Return the name of the negotiated ciphersuite in use on <b>tls</b> */
+const char *
+tor_tls_get_ciphersuite_name(tor_tls_t *tls)
+{
+  return SSL_get_cipher(tls->ssl);
+}
+
+/** Examine the client cipher list in <b>ssl</b>, and determine what kind of
+ * client it is.  Return one of CIPHERS_ERR, CIPHERS_V1, CIPHERS_V2,
+ * CIPHERS_UNRESTRICTED.
+ **/
 static int
-tor_tls_client_is_using_v2_ciphers(const SSL *ssl, const char *address)
+tor_tls_classify_client_ciphers(const SSL *ssl,
+                                STACK_OF(SSL_CIPHER) *peer_ciphers)
 {
-  int i;
-  SSL_SESSION *session;
+  int i, res;
+  tor_tls_t *tor_tls;
+  if (PREDICT_UNLIKELY(!v2_cipher_list_pruned))
+    prune_v2_cipher_list();
+
+  tor_tls = tor_tls_get_by_ssl(ssl);
+  if (tor_tls && tor_tls->client_cipher_list_type)
+    return tor_tls->client_cipher_list_type;
+
   /* If we reached this point, we just got a client hello.  See if there is
    * a cipher list. */
-  if (!(session = SSL_get_session((SSL *)ssl))) {
-    log_info(LD_NET, "No session on TLS?");
-    return 0;
-  }
-  if (!session->ciphers) {
+  if (!peer_ciphers) {
     log_info(LD_NET, "No ciphers on session");
-    return 0;
+    res = CIPHERS_ERR;
+    goto done;
   }
   /* Now we need to see if there are any ciphers whose presence means we're
    * dealing with an updated Tor. */
-  for (i = 0; i < sk_SSL_CIPHER_num(session->ciphers); ++i) {
-    SSL_CIPHER *cipher = sk_SSL_CIPHER_value(session->ciphers, i);
+  for (i = 0; i < sk_SSL_CIPHER_num(peer_ciphers); ++i) {
+    SSL_CIPHER *cipher = sk_SSL_CIPHER_value(peer_ciphers, i);
     const char *ciphername = SSL_CIPHER_get_name(cipher);
     if (strcmp(ciphername, TLS1_TXT_DHE_RSA_WITH_AES_128_SHA) &&
         strcmp(ciphername, TLS1_TXT_DHE_RSA_WITH_AES_256_SHA) &&
@@ -1339,28 +1532,111 @@ tor_tls_client_is_using_v2_ciphers(const SSL *ssl, const char *address)
         strcmp(ciphername, "(NONE)")) {
       log_debug(LD_NET, "Got a non-version-1 cipher called '%s'", ciphername);
       // return 1;
-      goto dump_list;
+      goto v2_or_higher;
     }
   }
-  return 0;
- dump_list:
+  res = CIPHERS_V1;
+  goto done;
+ v2_or_higher:
+  {
+    const uint16_t *v2_cipher = v2_cipher_list;
+    for (i = 0; i < sk_SSL_CIPHER_num(peer_ciphers); ++i) {
+      SSL_CIPHER *cipher = sk_SSL_CIPHER_value(peer_ciphers, i);
+      uint16_t id = cipher->id & 0xffff;
+      if (id == 0x00ff) /* extended renegotiation indicator. */
+        continue;
+      if (!id || id != *v2_cipher) {
+        res = CIPHERS_UNRESTRICTED;
+        goto dump_ciphers;
+      }
+      ++v2_cipher;
+    }
+    if (*v2_cipher != 0) {
+      res = CIPHERS_UNRESTRICTED;
+      goto dump_ciphers;
+    }
+    res = CIPHERS_V2;
+  }
+
+ dump_ciphers:
   {
     smartlist_t *elts = smartlist_new();
     char *s;
-    for (i = 0; i < sk_SSL_CIPHER_num(session->ciphers); ++i) {
-      SSL_CIPHER *cipher = sk_SSL_CIPHER_value(session->ciphers, i);
+    for (i = 0; i < sk_SSL_CIPHER_num(peer_ciphers); ++i) {
+      SSL_CIPHER *cipher = sk_SSL_CIPHER_value(peer_ciphers, i);
       const char *ciphername = SSL_CIPHER_get_name(cipher);
       smartlist_add(elts, (char*)ciphername);
     }
     s = smartlist_join_strings(elts, ":", 0, NULL);
-    log_debug(LD_NET, "Got a non-version-1 cipher list from %s.  It is: '%s'",
-              address, s);
+    log_debug(LD_NET, "Got a %s V2/V3 cipher list from %s.  It is: '%s'",
+              (res == CIPHERS_V2) ? "fictitious" : "real", ADDR(tor_tls), s);
     tor_free(s);
     smartlist_free(elts);
   }
-  return 1;
+ done:
+  if (tor_tls)
+    return tor_tls->client_cipher_list_type = res;
+
+  return res;
+}
+
+/** Return true iff the cipher list suggested by the client for <b>ssl</b> is
+ * a list that indicates that the client knows how to do the v2 TLS connection
+ * handshake. */
+static int
+tor_tls_client_is_using_v2_ciphers(const SSL *ssl)
+{
+  SSL_SESSION *session;
+  if (!(session = SSL_get_session((SSL *)ssl))) {
+    log_info(LD_NET, "No session on TLS?");
+    return CIPHERS_ERR;
+  }
+
+  return tor_tls_classify_client_ciphers(ssl, session->ciphers) >= CIPHERS_V2;
 }
 
+#if OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,0,0)
+/** Callback to get invoked on a server after we've read the list of ciphers
+ * the client supports, but before we pick our own ciphersuite.
+ *
+ * We can't abuse an info_cb for this, since by the time one of the
+ * client_hello info_cbs is called, we've already picked which ciphersuite to
+ * use.
+ *
+ * Technically, this function is an abuse of this callback, since the point of
+ * a session_secret_cb is to try to set up and/or verify a shared-secret for
+ * authentication on the fly.  But as long as we return 0, we won't actually be
+ * setting up a shared secret, and all will be fine.
+ */
+static int
+tor_tls_session_secret_cb(SSL *ssl, void *secret, int *secret_len,
+                          STACK_OF(SSL_CIPHER) *peer_ciphers,
+                          SSL_CIPHER **cipher, void *arg)
+{
+  (void) secret;
+  (void) secret_len;
+  (void) peer_ciphers;
+  (void) cipher;
+  (void) arg;
+
+  if (tor_tls_classify_client_ciphers(ssl, peer_ciphers) ==
+       CIPHERS_UNRESTRICTED) {
+    SSL_set_cipher_list(ssl, UNRESTRICTED_SERVER_CIPHER_LIST);
+  }
+
+  SSL_set_session_secret_cb(ssl, NULL, NULL);
+
+  return 0;
+}
+static void
+tor_tls_setup_session_secret_cb(tor_tls_t *tls)
+{
+  SSL_set_session_secret_cb(tls->ssl, tor_tls_session_secret_cb, NULL);
+}
+#else
+#define tor_tls_setup_session_secret_cb(tls) STMT_NIL
+#endif
+
 /** Invoked when a TLS state changes: log the change at severity 'debug' */
 static void
 tor_tls_debug_state_callback(const SSL *ssl, int type, int val)
@@ -1402,7 +1678,7 @@ tor_tls_server_info_callback(const SSL *ssl, int type, int val)
   }
 
   /* Now check the cipher list. */
-  if (tor_tls_client_is_using_v2_ciphers(ssl, ADDR(tls))) {
+  if (tor_tls_client_is_using_v2_ciphers(ssl)) {
     if (tls->wasV2Handshake)
       return; /* We already turned this stuff off for the first handshake;
                * This is a renegotiation. */
@@ -1627,6 +1903,9 @@ tor_tls_new(int sock, int isServer)
     SSL_set_info_callback(result->ssl, tor_tls_debug_state_callback);
   }
 
+  if (isServer)
+    tor_tls_setup_session_secret_cb(result);
+
   /* Not expected to get called. */
   tls_log_errors(NULL, LOG_WARN, LD_NET, "creating tor_tls_t object");
   return result;
@@ -1868,7 +2147,7 @@ tor_tls_finish_handshake(tor_tls_t *tls)
     /* There doesn't seem to be a clear OpenSSL API to clear mode flags. */
     tls->ssl->mode &= ~SSL_MODE_NO_AUTO_CHAIN;
 #ifdef V2_HANDSHAKE_SERVER
-    if (tor_tls_client_is_using_v2_ciphers(tls->ssl, ADDR(tls))) {
+    if (tor_tls_client_is_using_v2_ciphers(tls->ssl)) {
       /* This check is redundant, but back when we did it in the callback,
        * we might have not been able to look up the tor_tls_t if the code
        * was buggy.  Fixing that. */

+ 7 - 1
src/common/tortls.h

@@ -54,7 +54,12 @@ const char *tor_tls_err_to_string(int err);
 void tor_tls_get_state_description(tor_tls_t *tls, char *buf, size_t sz);
 
 void tor_tls_free_all(void);
-int tor_tls_context_init(int is_public_server,
+
+#define TOR_TLS_CTX_IS_PUBLIC_SERVER (1u<<0)
+#define TOR_TLS_CTX_USE_ECDHE_P256   (1u<<1)
+#define TOR_TLS_CTX_USE_ECDHE_P224   (1u<<2)
+
+int tor_tls_context_init(unsigned flags,
                          crypto_pk_t *client_identity,
                          crypto_pk_t *server_identity,
                          unsigned int key_lifetime);
@@ -129,6 +134,7 @@ int tor_tls_cert_is_valid(int severity,
                           const tor_cert_t *cert,
                           const tor_cert_t *signing_cert,
                           int check_rsa_1024);
+const char *tor_tls_get_ciphersuite_name(tor_tls_t *tls);
 
 #endif
 

+ 10 - 0
src/or/config.c

@@ -372,6 +372,7 @@ static config_var_t option_vars_[] = {
   OBSOLETE("TestVia"),
   V(TokenBucketRefillInterval,   MSEC_INTERVAL, "100 msec"),
   V(Tor2webMode,                 BOOL,     "0"),
+  V(TLSECGroup,                  STRING,   NULL),
   V(TrackHostExits,              CSV,      NULL),
   V(TrackHostExitsExpire,        INTERVAL, "30 minutes"),
   OBSOLETE("TrafficShaping"),
@@ -1193,6 +1194,9 @@ options_transition_requires_fresh_tls_context(const or_options_t *old_options,
     return 1;
   }
 
+  if (!opt_streq(old_options->TLSECGroup, new_options->TLSECGroup))
+    return 1;
+
   return 0;
 }
 
@@ -2301,6 +2305,12 @@ options_validate(or_options_t *old_options, or_options_t *options,
     }
   }
 
+  if (options->TLSECGroup && (strcasecmp(options->TLSECGroup, "P256") &&
+                              strcasecmp(options->TLSECGroup, "P224"))) {
+    COMPLAIN("Unrecognized TLSECGroup: Falling back to the default.");
+    tor_free(options->TLSECGroup);
+  }
+
   if (options->ExcludeNodes && options->StrictNodes) {
     COMPLAIN("You have asked to exclude certain relays from all positions "
              "in your circuits. Expect hidden services and other Tor "

+ 6 - 3
src/or/connection_or.c

@@ -1317,7 +1317,8 @@ connection_tls_continue_handshake(or_connection_t *conn)
           if (conn->base_.state == OR_CONN_STATE_TLS_HANDSHAKING) {
             if (tor_tls_received_v3_certificate(conn->tls)) {
               log_info(LD_OR, "Client got a v3 cert!  Moving on to v3 "
-                         "handshake.");
+                       "handshake with ciphersuite %s",
+                       tor_tls_get_ciphersuite_name(conn->tls));
               return connection_or_launch_v3_or_handshake(conn);
             } else {
               log_debug(LD_OR, "Done with initial SSL handshake (client-side)."
@@ -1641,10 +1642,12 @@ connection_tls_finish_handshake(or_connection_t *conn)
   char digest_rcvd[DIGEST_LEN];
   int started_here = connection_or_nonopen_was_started_here(conn);
 
-  log_debug(LD_HANDSHAKE,"%s tls handshake on %p with %s done. verifying.",
+  log_debug(LD_HANDSHAKE,"%s tls handshake on %p with %s done, using "
+            "ciphersuite %s. verifying.",
             started_here?"outgoing":"incoming",
             conn,
-            safe_str_client(conn->base_.address));
+            safe_str_client(conn->base_.address),
+            tor_tls_get_ciphersuite_name(conn->tls));
 
   directory_set_dirty();
 

+ 2 - 0
src/or/or.h

@@ -3854,6 +3854,8 @@ typedef struct {
 
   int IPv6Exit; /**< Do we support exiting to IPv6 addresses? */
 
+  char *TLSECGroup; /**< One of "P256", "P224", or nil for auto */
+
 } or_options_t;
 
 /** Persistent state for an onion router, as saved to disk. */

+ 12 - 1
src/or/router.c

@@ -491,7 +491,18 @@ v3_authority_check_key_expiry(void)
 int
 router_initialize_tls_context(void)
 {
-  return tor_tls_context_init(public_server_mode(get_options()),
+  unsigned int flags = 0;
+  const or_options_t *options = get_options();
+  if (public_server_mode(options))
+    flags |= TOR_TLS_CTX_IS_PUBLIC_SERVER;
+  if (options->TLSECGroup) {
+    if (!strcasecmp(options->TLSECGroup, "P256"))
+      flags |= TOR_TLS_CTX_USE_ECDHE_P256;
+    else if (!strcasecmp(options->TLSECGroup, "P224"))
+      flags |= TOR_TLS_CTX_USE_ECDHE_P224;
+  }
+
+  return tor_tls_context_init(flags,
                               get_tlsclient_identity_key(),
                               server_mode(get_options()) ?
                               get_server_identity_key() : NULL,

+ 99 - 0
src/test/bench.c

@@ -18,6 +18,15 @@ const char tor_git_revision[] = "";
 
 #include "or.h"
 #include "relay.h"
+#include <openssl/opensslv.h>
+#include <openssl/evp.h>
+#if OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,0,0)
+#ifndef OPENSSL_NO_EC
+#include <openssl/ec.h>
+#include <openssl/ecdh.h>
+#include <openssl/obj_mac.h>
+#endif
+#endif
 
 #if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_PROCESS_CPUTIME_ID)
 static uint64_t nanostart;
@@ -248,6 +257,91 @@ bench_cell_ops(void)
   tor_free(cell);
 }
 
+static void
+bench_dh(void)
+{
+  const int iters = 1<<10;
+  int i;
+  uint64_t start, end;
+
+  reset_perftime();
+  start = perftime();
+  for (i = 0; i < iters; ++i) {
+    char dh_pubkey_a[DH_BYTES], dh_pubkey_b[DH_BYTES];
+    char secret_a[DH_BYTES], secret_b[DH_BYTES];
+    ssize_t slen_a, slen_b;
+    crypto_dh_t *dh_a = crypto_dh_new(DH_TYPE_TLS);
+    crypto_dh_t *dh_b = crypto_dh_new(DH_TYPE_TLS);
+    crypto_dh_generate_public(dh_a);
+    crypto_dh_generate_public(dh_b);
+    crypto_dh_get_public(dh_a, dh_pubkey_a, sizeof(dh_pubkey_a));
+    crypto_dh_get_public(dh_b, dh_pubkey_b, sizeof(dh_pubkey_b));
+    slen_a = crypto_dh_compute_secret(LOG_NOTICE,
+                                      dh_a, dh_pubkey_b, sizeof(dh_pubkey_b),
+                                      secret_a, sizeof(secret_a));
+    slen_b = crypto_dh_compute_secret(LOG_NOTICE,
+                                      dh_b, dh_pubkey_a, sizeof(dh_pubkey_a),
+                                      secret_b, sizeof(secret_b));
+    tor_assert(slen_a == slen_b);
+    tor_assert(!memcmp(secret_a, secret_b, slen_a));
+    crypto_dh_free(dh_a);
+    crypto_dh_free(dh_b);
+  }
+  end = perftime();
+  printf("Complete DH handshakes (1024 bit, public and private ops):\n"
+         "      %f millisec each.\n", NANOCOUNT(start, end, iters)/1e6);
+}
+
+#if (!defined(OPENSSL_NO_EC)                    \
+     && OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,0,0))
+#define HAVE_EC_BENCHMARKS
+static void
+bench_ecdh_impl(int nid, const char *name)
+{
+  const int iters = 1<<10;
+  int i;
+  uint64_t start, end;
+
+  reset_perftime();
+  start = perftime();
+  for (i = 0; i < iters; ++i) {
+    char secret_a[DH_BYTES], secret_b[DH_BYTES];
+    ssize_t slen_a, slen_b;
+    EC_KEY *dh_a = EC_KEY_new_by_curve_name(nid);
+    EC_KEY *dh_b = EC_KEY_new_by_curve_name(nid);
+
+    EC_KEY_generate_key(dh_a);
+    EC_KEY_generate_key(dh_b);
+    slen_a = ECDH_compute_key(secret_a, DH_BYTES,
+                              EC_KEY_get0_public_key(dh_b), dh_a,
+                              NULL);
+    slen_b = ECDH_compute_key(secret_b, DH_BYTES,
+                              EC_KEY_get0_public_key(dh_a), dh_b,
+                              NULL);
+
+    tor_assert(slen_a == slen_b);
+    tor_assert(!memcmp(secret_a, secret_b, slen_a));
+    EC_KEY_free(dh_a);
+    EC_KEY_free(dh_b);
+  }
+  end = perftime();
+  printf("Complete ECDH %s handshakes (2 public and 2 private ops):\n"
+         "      %f millisec each.\n", name, NANOCOUNT(start, end, iters)/1e6);
+}
+
+static void
+bench_ecdh_p256(void)
+{
+  bench_ecdh_impl(NID_X9_62_prime256v1, "P-256");
+}
+
+static void
+bench_ecdh_p224(void)
+{
+  bench_ecdh_impl(NID_secp224r1, "P-224");
+}
+#endif
+
 typedef void (*bench_fn)(void);
 
 typedef struct benchmark_t {
@@ -263,6 +357,11 @@ static struct benchmark_t benchmarks[] = {
   ENT(aes),
   ENT(cell_aes),
   ENT(cell_ops),
+  ENT(dh),
+#ifdef HAVE_EC_BENCHMARKS
+  ENT(ecdh_p256),
+  ENT(ecdh_p224),
+#endif
   {NULL,NULL,0}
 };