[tor-commits] [tor/master] Let servers choose better ciphersuites when clients support them

nickm at torproject.org nickm at torproject.org
Wed Dec 26 03:13:09 UTC 2012


commit 175b2678d7dd0ff8b00b597169e4a9a0d8e86f12
Author: Nick Mathewson <nickm at torproject.org>
Date:   Wed Nov 28 13:31:17 2012 -0500

    Let servers choose better ciphersuites when clients support them
    
    This implements the server-side of proposal 198 by detecting when
    clients lack the magic list of ciphersuites that indicates that
    they're lying faking some ciphers they don't really have.  When
    clients lack this list, we can choose any cipher that we'd actually
    like.  The newly allowed ciphersuites are, currently, "All ECDHE-RSA
    ciphers that openssl supports, except for ECDHE-RSA-RC4".
    
    The code to detect the cipher list relies on on (ab)use of
    SSL_set_session_secret_cb.
---
 changes/tls_ecdhe      |   24 ++++++++++
 src/common/tortls.c    |  116 +++++++++++++++++++++++++++++++++++++++++-------
 src/common/tortls.h    |    1 +
 src/or/connection_or.c |    9 +++-
 4 files changed, 131 insertions(+), 19 deletions(-)

diff --git a/changes/tls_ecdhe b/changes/tls_ecdhe
new file mode 100644
index 0000000..58a8f90
--- /dev/null
+++ b/changes/tls_ecdhe
@@ -0,0 +1,24 @@
+  o Major features:
+
+    - Servers can now enable the ECDHE TLS ciphersuites when
+      available and appropriate. These ciphersuites, when used with
+      the P-256 elliptic curve, 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.
+
+      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.
+
diff --git a/src/common/tortls.c b/src/common/tortls.c
index 4513689..bba8926 100644
--- a/src/common/tortls.c
+++ b/src/common/tortls.c
@@ -678,11 +678,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
@@ -1410,15 +1441,22 @@ prune_v2_cipher_list(void)
   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_classify_client_ciphers(const SSL *ssl)
+tor_tls_classify_client_ciphers(const SSL *ssl,
+                                STACK_OF(SSL_CIPHER) *peer_ciphers)
 {
   int i, res;
-  SSL_SESSION *session;
   tor_tls_t *tor_tls;
   if (PREDICT_UNLIKELY(!v2_cipher_list_pruned))
     prune_v2_cipher_list();
@@ -1429,20 +1467,15 @@ tor_tls_classify_client_ciphers(const SSL *ssl)
 
   /* 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?");
-    res = CIPHERS_ERR;
-    goto done;
-  }
-  if (!session->ciphers) {
+  if (!peer_ciphers) {
     log_info(LD_NET, "No ciphers on session");
     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) &&
@@ -1458,8 +1491,8 @@ tor_tls_classify_client_ciphers(const SSL *ssl)
  v2_or_higher:
   {
     const uint16_t *v2_cipher = v2_cipher_list;
-    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);
       uint16_t id = cipher->id & 0xffff;
       if (id == 0x00ff) /* extended renegotiation indicator. */
         continue;
@@ -1480,8 +1513,8 @@ tor_tls_classify_client_ciphers(const SSL *ssl)
   {
     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);
     }
@@ -1504,8 +1537,56 @@ tor_tls_classify_client_ciphers(const SSL *ssl)
 static int
 tor_tls_client_is_using_v2_ciphers(const SSL *ssl)
 {
-  return tor_tls_classify_client_ciphers(ssl) >= CIPHERS_V2;
+  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
@@ -1773,6 +1854,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;
diff --git a/src/common/tortls.h b/src/common/tortls.h
index 7bc6c8e..8881827 100644
--- a/src/common/tortls.h
+++ b/src/common/tortls.h
@@ -129,6 +129,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
 
diff --git a/src/or/connection_or.c b/src/or/connection_or.c
index 7ac4d1e..c2e4375 100644
--- a/src/or/connection_or.c
+++ b/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();
 





More information about the tor-commits mailing list