summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Mathewson <nickm@torproject.org>2012-12-25 22:10:57 -0500
committerNick Mathewson <nickm@torproject.org>2012-12-25 22:10:57 -0500
commit3eb39005106209240832b96e9292385747f703fa (patch)
treeb4cd5942d7266111fe89a55306cdd05f1ca6727f
parent747d28408884b5600b695bcf8a5dd7101188cda5 (diff)
parentddbe28919ab7056a81db920426b12285f0166dd8 (diff)
downloadtor-3eb39005106209240832b96e9292385747f703fa.tar.gz
tor-3eb39005106209240832b96e9292385747f703fa.zip
Merge branch 'tls_ecdhe_rebased_v2'
-rw-r--r--changes/dh_benchmarks3
-rw-r--r--changes/tls_ecdhe26
-rw-r--r--doc/tor.1.txt6
-rw-r--r--src/common/tortls.c339
-rw-r--r--src/common/tortls.h8
-rw-r--r--src/or/config.c10
-rw-r--r--src/or/connection_or.c9
-rw-r--r--src/or/or.h2
-rw-r--r--src/or/router.c13
-rw-r--r--src/test/bench.c99
10 files changed, 480 insertions, 35 deletions
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/changes/tls_ecdhe b/changes/tls_ecdhe
new file mode 100644
index 0000000000..48c6384dad
--- /dev/null
+++ b/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.
+
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 af3059a02d..f0c4c7dc57 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
@@ -152,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
@@ -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,
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);
@@ -505,6 +528,37 @@ 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 much faster.");
+ }
+#endif
+
tor_tls_allocate_tor_tls_object_ex_data_index();
tls_library_is_initialized = 1;
@@ -657,11 +711,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
@@ -1014,17 +1099,20 @@ tor_tls_context_incref(tor_tls_context_t *ctx)
/** Create new global client and server TLS contexts.
*
* 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
- * 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
-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;
@@ -1034,7 +1122,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;
@@ -1051,6 +1139,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;
@@ -1064,6 +1153,7 @@ tor_tls_context_init(int is_public_server,
rv2 = tor_tls_context_init_one(&client_tls_context,
client_identity,
key_lifetime,
+ flags,
1);
}
@@ -1080,10 +1170,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;
@@ -1107,7 +1199,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;
@@ -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_ECDH_USE);
#ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
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));
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,
always_accept_verify_cb);
/* 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
-/** 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
-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
* 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");
- return 0;
+ 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) &&
@@ -1339,28 +1532,111 @@ 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:
+ 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();
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);
}
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);
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' */
static void
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. */
- 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. */
@@ -1627,6 +1903,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;
@@ -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. */
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. */
diff --git a/src/common/tortls.h b/src/common/tortls.h
index 7bc6c8e76b..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);
@@ -129,6 +134,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/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/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();
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,
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 <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)
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}
};