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__::
 **GeoIPv6File** __filename__::
     A filename containing IPv6 GeoIP data, for use with by-country statistics.
     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**::
 **CellStatistics** **0**|**1**::
     When this option is enabled, Tor writes statistics on the mean time that
     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)
     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;
   crypto_pk_t *auth_key;
 } tor_tls_context_t;
 } 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
 #define TOR_TLS_MAGIC 0x71571571
 
 
 /** Holds a SSL object and its associated data.  Members are only
 /** Holds a SSL object and its associated data.  Members are only
@@ -152,6 +170,9 @@ struct tor_tls_t {
                                   * one certificate). */
                                   * one certificate). */
   /** True iff we should call negotiated_callback when we're done reading. */
   /** True iff we should call negotiated_callback when we're done reading. */
   unsigned int got_renegotiate:1;
   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. */
   /** Incremented every time we start the server side of a handshake. */
   uint8_t server_handshake_count;
   uint8_t server_handshake_count;
   size_t wantwrite_n; /**< 0 normally, >0 if we returned wantwrite last
   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,
 static int tor_tls_context_init_one(tor_tls_context_t **ppcontext,
                                     crypto_pk_t *identity,
                                     crypto_pk_t *identity,
                                     unsigned int key_lifetime,
                                     unsigned int key_lifetime,
+                                    unsigned int flags,
                                     int is_client);
                                     int is_client);
 static tor_tls_context_t *tor_tls_context_new(crypto_pk_t *identity,
 static tor_tls_context_t *tor_tls_context_new(crypto_pk_t *identity,
                                               unsigned int key_lifetime,
                                               unsigned int key_lifetime,
+                                              unsigned int flags,
                                               int is_client);
                                               int is_client);
 static int check_cert_lifetime_internal(int severity, const X509 *cert,
 static int check_cert_lifetime_internal(int severity, const X509 *cert,
                                    int past_tolerance, int future_tolerance);
                                    int past_tolerance, int future_tolerance);
@@ -505,6 +528,37 @@ tor_tls_init(void)
                SSLeay_version(SSLEAY_VERSION), version);
                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();
     tor_tls_allocate_tor_tls_object_ex_data_index();
 
 
     tls_library_is_initialized = 1;
     tls_library_is_initialized = 1;
@@ -657,11 +711,42 @@ tor_tls_create_certificate(crypto_pk_t *rsa,
 #undef SERIAL_NUMBER_SIZE
 #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                         \
 #define SERVER_CIPHER_LIST                         \
   (TLS1_TXT_DHE_RSA_WITH_AES_256_SHA ":"           \
   (TLS1_TXT_DHE_RSA_WITH_AES_256_SHA ":"           \
    TLS1_TXT_DHE_RSA_WITH_AES_128_SHA ":"           \
    TLS1_TXT_DHE_RSA_WITH_AES_128_SHA ":"           \
    SSL3_TXT_EDH_RSA_DES_192_CBC3_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
 /* Note: to set up your own private testing network with link crypto
  * disabled, set your Tors' cipher list to
  * disabled, set your Tors' cipher list to
  * (SSL3_TXT_RSA_NULL_SHA).  If you do this, you won't be able to communicate
  * (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.
 /** Create new global client and server TLS contexts.
  *
  *
  * If <b>server_identity</b> is NULL, this will not generate a server
  * 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
  * 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
 int
-tor_tls_context_init(int is_public_server,
+tor_tls_context_init(unsigned flags,
                      crypto_pk_t *client_identity,
                      crypto_pk_t *client_identity,
                      crypto_pk_t *server_identity,
                      crypto_pk_t *server_identity,
                      unsigned int key_lifetime)
                      unsigned int key_lifetime)
 {
 {
   int rv1 = 0;
   int rv1 = 0;
   int rv2 = 0;
   int rv2 = 0;
+  const int is_public_server = flags & TOR_TLS_CTX_IS_PUBLIC_SERVER;
 
 
   if (is_public_server) {
   if (is_public_server) {
     tor_tls_context_t *new_ctx;
     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,
     rv1 = tor_tls_context_init_one(&server_tls_context,
                                    server_identity,
                                    server_identity,
-                                   key_lifetime, 0);
+                                   key_lifetime, flags, 0);
 
 
     if (rv1 >= 0) {
     if (rv1 >= 0) {
       new_ctx = server_tls_context;
       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,
       rv1 = tor_tls_context_init_one(&server_tls_context,
                                      server_identity,
                                      server_identity,
                                      key_lifetime,
                                      key_lifetime,
+                                     flags,
                                      0);
                                      0);
     } else {
     } else {
       tor_tls_context_t *old_ctx = server_tls_context;
       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,
     rv2 = tor_tls_context_init_one(&client_tls_context,
                                    client_identity,
                                    client_identity,
                                    key_lifetime,
                                    key_lifetime,
+                                   flags,
                                    1);
                                    1);
   }
   }
 
 
@@ -1080,10 +1170,12 @@ static int
 tor_tls_context_init_one(tor_tls_context_t **ppcontext,
 tor_tls_context_init_one(tor_tls_context_t **ppcontext,
                          crypto_pk_t *identity,
                          crypto_pk_t *identity,
                          unsigned int key_lifetime,
                          unsigned int key_lifetime,
+                         unsigned int flags,
                          int is_client)
                          int is_client)
 {
 {
   tor_tls_context_t *new_ctx = tor_tls_context_new(identity,
   tor_tls_context_t *new_ctx = tor_tls_context_new(identity,
                                                    key_lifetime,
                                                    key_lifetime,
+                                                   flags,
                                                    is_client);
                                                    is_client);
   tor_tls_context_t *old_ctx = *ppcontext;
   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 *
 static tor_tls_context_t *
 tor_tls_context_new(crypto_pk_t *identity, unsigned int key_lifetime,
 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;
   crypto_pk_t *rsa = NULL, *rsa_auth = NULL;
   EVP_PKEY *pkey = 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_DH_USE);
+  SSL_CTX_set_options(result->ctx, SSL_OP_SINGLE_ECDH_USE);
 
 
 #ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
 #ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
   SSL_CTX_set_options(result->ctx,
   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));
     SSL_CTX_set_tmp_dh(result->ctx, crypto_dh_get_dh_(dh));
     crypto_dh_free(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,
   SSL_CTX_set_verify(result->ctx, SSL_VERIFY_PEER,
                      always_accept_verify_cb);
                      always_accept_verify_cb);
   /* let us realloc bufs that we're writing from */
   /* 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
 #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
 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
   /* If we reached this point, we just got a client hello.  See if there is
    * a cipher list. */
    * 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");
     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
   /* Now we need to see if there are any ciphers whose presence means we're
    * dealing with an updated Tor. */
    * 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);
     const char *ciphername = SSL_CIPHER_get_name(cipher);
     if (strcmp(ciphername, TLS1_TXT_DHE_RSA_WITH_AES_128_SHA) &&
     if (strcmp(ciphername, TLS1_TXT_DHE_RSA_WITH_AES_128_SHA) &&
         strcmp(ciphername, TLS1_TXT_DHE_RSA_WITH_AES_256_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)")) {
         strcmp(ciphername, "(NONE)")) {
       log_debug(LD_NET, "Got a non-version-1 cipher called '%s'", ciphername);
       log_debug(LD_NET, "Got a non-version-1 cipher called '%s'", ciphername);
       // return 1;
       // 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();
     smartlist_t *elts = smartlist_new();
     char *s;
     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);
       const char *ciphername = SSL_CIPHER_get_name(cipher);
       smartlist_add(elts, (char*)ciphername);
       smartlist_add(elts, (char*)ciphername);
     }
     }
     s = smartlist_join_strings(elts, ":", 0, NULL);
     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);
     tor_free(s);
     smartlist_free(elts);
     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' */
 /** Invoked when a TLS state changes: log the change at severity 'debug' */
 static void
 static void
 tor_tls_debug_state_callback(const SSL *ssl, int type, int val)
 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. */
   /* 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)
     if (tls->wasV2Handshake)
       return; /* We already turned this stuff off for the first handshake;
       return; /* We already turned this stuff off for the first handshake;
                * This is a renegotiation. */
                * 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);
     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. */
   /* Not expected to get called. */
   tls_log_errors(NULL, LOG_WARN, LD_NET, "creating tor_tls_t object");
   tls_log_errors(NULL, LOG_WARN, LD_NET, "creating tor_tls_t object");
   return result;
   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. */
     /* There doesn't seem to be a clear OpenSSL API to clear mode flags. */
     tls->ssl->mode &= ~SSL_MODE_NO_AUTO_CHAIN;
     tls->ssl->mode &= ~SSL_MODE_NO_AUTO_CHAIN;
 #ifdef V2_HANDSHAKE_SERVER
 #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,
       /* 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
        * we might have not been able to look up the tor_tls_t if the code
        * was buggy.  Fixing that. */
        * 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_get_state_description(tor_tls_t *tls, char *buf, size_t sz);
 
 
 void tor_tls_free_all(void);
 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 *client_identity,
                          crypto_pk_t *server_identity,
                          crypto_pk_t *server_identity,
                          unsigned int key_lifetime);
                          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 *cert,
                           const tor_cert_t *signing_cert,
                           const tor_cert_t *signing_cert,
                           int check_rsa_1024);
                           int check_rsa_1024);
+const char *tor_tls_get_ciphersuite_name(tor_tls_t *tls);
 
 
 #endif
 #endif
 
 

+ 10 - 0
src/or/config.c

@@ -372,6 +372,7 @@ static config_var_t option_vars_[] = {
   OBSOLETE("TestVia"),
   OBSOLETE("TestVia"),
   V(TokenBucketRefillInterval,   MSEC_INTERVAL, "100 msec"),
   V(TokenBucketRefillInterval,   MSEC_INTERVAL, "100 msec"),
   V(Tor2webMode,                 BOOL,     "0"),
   V(Tor2webMode,                 BOOL,     "0"),
+  V(TLSECGroup,                  STRING,   NULL),
   V(TrackHostExits,              CSV,      NULL),
   V(TrackHostExits,              CSV,      NULL),
   V(TrackHostExitsExpire,        INTERVAL, "30 minutes"),
   V(TrackHostExitsExpire,        INTERVAL, "30 minutes"),
   OBSOLETE("TrafficShaping"),
   OBSOLETE("TrafficShaping"),
@@ -1193,6 +1194,9 @@ options_transition_requires_fresh_tls_context(const or_options_t *old_options,
     return 1;
     return 1;
   }
   }
 
 
+  if (!opt_streq(old_options->TLSECGroup, new_options->TLSECGroup))
+    return 1;
+
   return 0;
   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) {
   if (options->ExcludeNodes && options->StrictNodes) {
     COMPLAIN("You have asked to exclude certain relays from all positions "
     COMPLAIN("You have asked to exclude certain relays from all positions "
              "in your circuits. Expect hidden services and other Tor "
              "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 (conn->base_.state == OR_CONN_STATE_TLS_HANDSHAKING) {
             if (tor_tls_received_v3_certificate(conn->tls)) {
             if (tor_tls_received_v3_certificate(conn->tls)) {
               log_info(LD_OR, "Client got a v3 cert!  Moving on to v3 "
               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);
               return connection_or_launch_v3_or_handshake(conn);
             } else {
             } else {
               log_debug(LD_OR, "Done with initial SSL handshake (client-side)."
               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];
   char digest_rcvd[DIGEST_LEN];
   int started_here = connection_or_nonopen_was_started_here(conn);
   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",
             started_here?"outgoing":"incoming",
             conn,
             conn,
-            safe_str_client(conn->base_.address));
+            safe_str_client(conn->base_.address),
+            tor_tls_get_ciphersuite_name(conn->tls));
 
 
   directory_set_dirty();
   directory_set_dirty();
 
 

+ 2 - 0
src/or/or.h

@@ -3854,6 +3854,8 @@ typedef struct {
 
 
   int IPv6Exit; /**< Do we support exiting to IPv6 addresses? */
   int IPv6Exit; /**< Do we support exiting to IPv6 addresses? */
 
 
+  char *TLSECGroup; /**< One of "P256", "P224", or nil for auto */
+
 } or_options_t;
 } or_options_t;
 
 
 /** Persistent state for an onion router, as saved to disk. */
 /** 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
 int
 router_initialize_tls_context(void)
 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(),
                               get_tlsclient_identity_key(),
                               server_mode(get_options()) ?
                               server_mode(get_options()) ?
                               get_server_identity_key() : NULL,
                               get_server_identity_key() : NULL,

+ 99 - 0
src/test/bench.c

@@ -18,6 +18,15 @@ const char tor_git_revision[] = "";
 
 
 #include "or.h"
 #include "or.h"
 #include "relay.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)
 #if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_PROCESS_CPUTIME_ID)
 static uint64_t nanostart;
 static uint64_t nanostart;
@@ -248,6 +257,91 @@ bench_cell_ops(void)
   tor_free(cell);
   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 void (*bench_fn)(void);
 
 
 typedef struct benchmark_t {
 typedef struct benchmark_t {
@@ -263,6 +357,11 @@ static struct benchmark_t benchmarks[] = {
   ENT(aes),
   ENT(aes),
   ENT(cell_aes),
   ENT(cell_aes),
   ENT(cell_ops),
   ENT(cell_ops),
+  ENT(dh),
+#ifdef HAVE_EC_BENCHMARKS
+  ENT(ecdh_p256),
+  ENT(ecdh_p224),
+#endif
   {NULL,NULL,0}
   {NULL,NULL,0}
 };
 };