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.
This commit is contained in:
Nick Mathewson 2012-11-28 13:31:17 -05:00
parent 63208aa1e5
commit 175b2678d7
4 changed files with 131 additions and 19 deletions

24
changes/tls_ecdhe Normal file
View File

@ -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.

View File

@ -678,11 +678,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
@ -1410,15 +1441,22 @@ prune_v2_cipher_list(void)
v2_cipher_list_pruned = 1; 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 /** 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, * client it is. Return one of CIPHERS_ERR, CIPHERS_V1, CIPHERS_V2,
* CIPHERS_UNRESTRICTED. * CIPHERS_UNRESTRICTED.
**/ **/
static int 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; int i, res;
SSL_SESSION *session;
tor_tls_t *tor_tls; tor_tls_t *tor_tls;
if (PREDICT_UNLIKELY(!v2_cipher_list_pruned)) if (PREDICT_UNLIKELY(!v2_cipher_list_pruned))
prune_v2_cipher_list(); 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 /* 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))) { if (!peer_ciphers) {
log_info(LD_NET, "No session on TLS?");
res = CIPHERS_ERR;
goto done;
}
if (!session->ciphers) {
log_info(LD_NET, "No ciphers on session"); log_info(LD_NET, "No ciphers on session");
res = CIPHERS_ERR; res = CIPHERS_ERR;
goto done; 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) { for (i = 0; i < sk_SSL_CIPHER_num(peer_ciphers); ++i) {
SSL_CIPHER *cipher = sk_SSL_CIPHER_value(session->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) &&
@ -1458,8 +1491,8 @@ tor_tls_classify_client_ciphers(const SSL *ssl)
v2_or_higher: v2_or_higher:
{ {
const uint16_t *v2_cipher = v2_cipher_list; const uint16_t *v2_cipher = v2_cipher_list;
for (i = 0; i < sk_SSL_CIPHER_num(session->ciphers); ++i) { for (i = 0; i < sk_SSL_CIPHER_num(peer_ciphers); ++i) {
SSL_CIPHER *cipher = sk_SSL_CIPHER_value(session->ciphers, i); SSL_CIPHER *cipher = sk_SSL_CIPHER_value(peer_ciphers, i);
uint16_t id = cipher->id & 0xffff; uint16_t id = cipher->id & 0xffff;
if (id == 0x00ff) /* extended renegotiation indicator. */ if (id == 0x00ff) /* extended renegotiation indicator. */
continue; continue;
@ -1480,8 +1513,8 @@ tor_tls_classify_client_ciphers(const SSL *ssl)
{ {
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) { for (i = 0; i < sk_SSL_CIPHER_num(peer_ciphers); ++i) {
SSL_CIPHER *cipher = sk_SSL_CIPHER_value(session->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);
} }
@ -1504,9 +1537,57 @@ tor_tls_classify_client_ciphers(const SSL *ssl)
static int static int
tor_tls_client_is_using_v2_ciphers(const SSL *ssl) 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' */ /** 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)
@ -1773,6 +1854,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;

View File

@ -129,6 +129,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

View File

@ -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();