From 175b2678d7dd0ff8b00b597169e4a9a0d8e86f12 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 28 Nov 2012 13:31:17 -0500 Subject: [PATCH] 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(-) create mode 100644 changes/tls_ecdhe diff --git a/changes/tls_ecdhe b/changes/tls_ecdhe new file mode 100644 index 0000000000..58a8f90692 --- /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 4513689cf9..bba89268e1 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 tls */ +const char * +tor_tls_get_ciphersuite_name(tor_tls_t *tls) +{ + return SSL_get_cipher(tls->ssl); +} + /** Examine the client cipher list in ssl, 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,9 +1537,57 @@ 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 tor_tls_debug_state_callback(const SSL *ssl, int type, int val) @@ -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 7bc6c8e76b..8881827cef 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 7ac4d1ee95..c2e4375db2 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();