From bbaf4d964377f2f43625b56517ae21a80c220e7f Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 26 Nov 2012 23:19:02 -0500 Subject: [PATCH 1/9] Configure SSL context to know about using P-256 for ECDHE. --- src/common/tortls.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/common/tortls.c b/src/common/tortls.c index af3059a02d..02d09515a5 100644 --- a/src/common/tortls.c +++ b/src/common/tortls.c @@ -1224,6 +1224,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 +1275,17 @@ 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) { + EC_KEY *ec_key; + /* Use P-256 for ECDHE. */ + ec_key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + 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 */ From 2a26e1d45f6a0a93feb65cc02dc433303d07c002 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 26 Nov 2012 23:19:32 -0500 Subject: [PATCH 2/9] prop198: Detect the list of ciphersuites we used to lie about having This is less easy than you might think; we can't just look at the client ciphers list, since openssl doesn't remember client ciphers if it doesn't know about them. So we have to keep a list of the "v2" ciphers, with the ones we don't know about removed. --- src/common/tortls.c | 147 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 133 insertions(+), 14 deletions(-) diff --git a/src/common/tortls.c b/src/common/tortls.c index 02d09515a5..804cec8f1f 100644 --- a/src/common/tortls.c +++ b/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 @@ -1322,23 +1340,94 @@ 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 ssl 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, const char *address) + +/* 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) { - int i; + 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; +} + +/** 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, const char *address) +{ + int i, res; SSL_SESSION *session; + if (PREDICT_UNLIKELY(!v2_cipher_list_pruned)) + prune_v2_cipher_list(); + /* 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; + return CIPHERS_ERR; } if (!session->ciphers) { log_info(LD_NET, "No ciphers on session"); - return 0; + return CIPHERS_ERR; } /* Now we need to see if there are any ciphers whose presence means we're * dealing with an updated Tor. */ @@ -1351,11 +1440,32 @@ 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: + return CIPHERS_V1; + 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); + 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; @@ -1365,12 +1475,21 @@ tor_tls_client_is_using_v2_ciphers(const SSL *ssl, const char *address) 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", address, s); tor_free(s); smartlist_free(elts); } - return 1; + return res; +} + +/** Return true iff the cipher list suggested by the client for ssl 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, const char *address) +{ + return tor_tls_classify_client_ciphers(ssl, address) >= CIPHERS_V2; } /** Invoked when a TLS state changes: log the change at severity 'debug' */ From 047d9e57b0b6432c3fe0ba365e905916665f4740 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 28 Nov 2012 12:06:57 -0500 Subject: [PATCH 3/9] Cache the type of client cipher list we have in the tor_tls_t We already use this classification for deciding whether (as a server) to do a v2/v3 handshake, and we're about to start using it for deciding whether we can use good ciphersuites too. --- src/common/tortls.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/common/tortls.c b/src/common/tortls.c index 804cec8f1f..715b91b22d 100644 --- a/src/common/tortls.c +++ b/src/common/tortls.c @@ -170,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 @@ -1416,18 +1419,25 @@ tor_tls_classify_client_ciphers(const SSL *ssl, const char *address) { int i, res; SSL_SESSION *session; + 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 CIPHERS_ERR; + res = CIPHERS_ERR; + goto done; } if (!session->ciphers) { log_info(LD_NET, "No ciphers on session"); - return CIPHERS_ERR; + 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. */ @@ -1443,7 +1453,8 @@ tor_tls_classify_client_ciphers(const SSL *ssl, const char *address) goto v2_or_higher; } } - return CIPHERS_V1; + res = CIPHERS_V1; + goto done; v2_or_higher: { const uint16_t *v2_cipher = v2_cipher_list; @@ -1480,6 +1491,10 @@ tor_tls_classify_client_ciphers(const SSL *ssl, const char *address) tor_free(s); smartlist_free(elts); } + done: + if (tor_tls) + return tor_tls->client_cipher_list_type = res; + return res; } From 63208aa1e53f5ffc3ecbe47402cc2736bbce6af0 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 28 Nov 2012 12:35:02 -0500 Subject: [PATCH 4/9] Remove the address argument from client cipher classification fns --- src/common/tortls.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/common/tortls.c b/src/common/tortls.c index 715b91b22d..4513689cf9 100644 --- a/src/common/tortls.c +++ b/src/common/tortls.c @@ -1415,7 +1415,7 @@ prune_v2_cipher_list(void) * CIPHERS_UNRESTRICTED. **/ static int -tor_tls_classify_client_ciphers(const SSL *ssl, const char *address) +tor_tls_classify_client_ciphers(const SSL *ssl) { int i, res; SSL_SESSION *session; @@ -1487,7 +1487,7 @@ tor_tls_classify_client_ciphers(const SSL *ssl, const char *address) } s = smartlist_join_strings(elts, ":", 0, NULL); log_debug(LD_NET, "Got a %s V2/V3 cipher list from %s. It is: '%s'", - (res == CIPHERS_V2) ? "fictitious" : "real", address, s); + (res == CIPHERS_V2) ? "fictitious" : "real", ADDR(tor_tls), s); tor_free(s); smartlist_free(elts); } @@ -1502,9 +1502,9 @@ tor_tls_classify_client_ciphers(const SSL *ssl, const char *address) * 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, const char *address) +tor_tls_client_is_using_v2_ciphers(const SSL *ssl) { - return tor_tls_classify_client_ciphers(ssl, address) >= CIPHERS_V2; + return tor_tls_classify_client_ciphers(ssl) >= CIPHERS_V2; } /** Invoked when a TLS state changes: log the change at severity 'debug' */ @@ -1548,7 +1548,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. */ @@ -2014,7 +2014,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. */ From 175b2678d7dd0ff8b00b597169e4a9a0d8e86f12 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 28 Nov 2012 13:31:17 -0500 Subject: [PATCH 5/9] 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(); From 2f8fd53750737e4402adc0b896f00f789fb38181 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 28 Nov 2012 14:14:35 -0500 Subject: [PATCH 6/9] Add benchmark for DH handshake and ECDH-P-224/56 handshake --- changes/dh_benchmarks | 3 ++ src/test/bench.c | 99 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 changes/dh_benchmarks diff --git a/changes/dh_benchmarks b/changes/dh_benchmarks new file mode 100644 index 0000000000..2301995a74 --- /dev/null +++ b/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. diff --git a/src/test/bench.c b/src/test/bench.c index 2e65d0b2d4..da1ae9bc5d 100644 --- a/src/test/bench.c +++ b/src/test/bench.c @@ -18,6 +18,15 @@ const char tor_git_revision[] = ""; #include "or.h" #include "relay.h" +#include +#include +#if OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,0,0) +#ifndef OPENSSL_NO_EC +#include +#include +#include +#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} }; From c8b3bdb78215d4d7821d9bfa15bb8f7786403ce9 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 25 Dec 2012 17:39:09 -0500 Subject: [PATCH 7/9] Inform the user if they're passing up a 10x ECDH speedup. --- src/common/tortls.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/common/tortls.c b/src/common/tortls.c index bba89268e1..47b8f2815a 100644 --- a/src/common/tortls.c +++ b/src/common/tortls.c @@ -526,6 +526,38 @@ 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 about 5-10x " + "faster."); + } +#endif + tor_tls_allocate_tor_tls_object_ex_data_index(); tls_library_is_initialized = 1; From 25afecdbf999eb91ed9216be1f8b8cdf0f78135b Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 25 Dec 2012 20:04:54 -0500 Subject: [PATCH 8/9] Make ECDHE group configurable: 224 for public, 256 for bridges (default) --- changes/tls_ecdhe | 12 +++++++----- doc/tor.1.txt | 6 ++++++ src/common/tortls.c | 30 ++++++++++++++++++++++++------ src/common/tortls.h | 7 ++++++- src/or/config.c | 10 ++++++++++ src/or/or.h | 2 ++ src/or/router.c | 13 ++++++++++++- 7 files changed, 67 insertions(+), 13 deletions(-) diff --git a/changes/tls_ecdhe b/changes/tls_ecdhe index 58a8f90692..48c6384dad 100644 --- a/changes/tls_ecdhe +++ b/changes/tls_ecdhe @@ -1,10 +1,12 @@ 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. + - 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 diff --git a/doc/tor.1.txt b/doc/tor.1.txt index 85b7f4c35a..7d1742c0ea 100644 --- a/doc/tor.1.txt +++ b/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) diff --git a/src/common/tortls.c b/src/common/tortls.c index 47b8f2815a..ce6c7d67c6 100644 --- a/src/common/tortls.c +++ b/src/common/tortls.c @@ -236,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); @@ -1098,17 +1100,20 @@ tor_tls_context_incref(tor_tls_context_t *ctx) /** Create new global client and server TLS contexts. * * If server_identity is NULL, this will not generate a server - * TLS context. If is_public_server is non-zero, this will use + * TLS context. If TOR_TLS_CTX_IS_PUBLIC_SERVER is set in flags, use * the same TLS context for incoming and outgoing connections, and - * ignore client_identity. */ + * ignore client_identity. If one of TOR_TLS_CTX_USE_ECDHE_P{224,256} + * is set in flags, 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; @@ -1118,7 +1123,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; @@ -1135,6 +1140,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; @@ -1148,6 +1154,7 @@ tor_tls_context_init(int is_public_server, rv2 = tor_tls_context_init_one(&client_tls_context, client_identity, key_lifetime, + flags, 1); } @@ -1164,10 +1171,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; @@ -1191,7 +1200,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; @@ -1362,9 +1371,18 @@ tor_tls_context_new(crypto_pk_t *identity, unsigned int key_lifetime, #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_X9_62_prime256v1); + 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); diff --git a/src/common/tortls.h b/src/common/tortls.h index 8881827cef..df3ab87a17 100644 --- a/src/common/tortls.h +++ b/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); diff --git a/src/or/config.c b/src/or/config.c index 1df10e110e..b81edf749c 100644 --- a/src/or/config.c +++ b/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 " diff --git a/src/or/or.h b/src/or/or.h index 3a8e50c801..a65ca44ed6 100644 --- a/src/or/or.h +++ b/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. */ diff --git a/src/or/router.c b/src/or/router.c index 5786103b94..c7380cb444 100644 --- a/src/or/router.c +++ b/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, From ddbe28919ab7056a81db920426b12285f0166dd8 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 25 Dec 2012 21:08:42 -0500 Subject: [PATCH 9/9] Be more noncomittal about performance improvement of uint128 backend. --- src/common/tortls.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/common/tortls.c b/src/common/tortls.c index ce6c7d67c6..f0c4c7dc57 100644 --- a/src/common/tortls.c +++ b/src/common/tortls.c @@ -555,8 +555,7 @@ tor_tls_init(void) "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 about 5-10x " - "faster."); + "when configuring it) would make ECDH much faster."); } #endif