diff options
author | Nick Mathewson <nickm@torproject.org> | 2012-11-26 23:19:32 -0500 |
---|---|---|
committer | Nick Mathewson <nickm@torproject.org> | 2012-12-25 20:14:07 -0500 |
commit | 2a26e1d45f6a0a93feb65cc02dc433303d07c002 (patch) | |
tree | b73a1f2146217a60bb83395ed98ad6502822c450 | |
parent | bbaf4d964377f2f43625b56517ae21a80c220e7f (diff) | |
download | tor-2a26e1d45f6a0a93feb65cc02dc433303d07c002.tar tor-2a26e1d45f6a0a93feb65cc02dc433303d07c002.tar.gz |
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.
-rw-r--r-- | src/common/tortls.c | 145 |
1 files changed, 132 insertions, 13 deletions
diff --git a/src/common/tortls.c b/src/common/tortls.c index 02d09515a..804cec8f1 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 <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; +} + +/** 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, const char *address) { - int i; + 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 <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, 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' */ |