diff options
Diffstat (limited to 'net/socket')
-rw-r--r-- | net/socket/ssl_client_socket_openssl.cc | 214 | ||||
-rw-r--r-- | net/socket/ssl_client_socket_unittest.cc | 11 |
2 files changed, 195 insertions, 30 deletions
diff --git a/net/socket/ssl_client_socket_openssl.cc b/net/socket/ssl_client_socket_openssl.cc index 0c02507..b290a65f 100644 --- a/net/socket/ssl_client_socket_openssl.cc +++ b/net/socket/ssl_client_socket_openssl.cc @@ -19,6 +19,7 @@ #include "net/base/ssl_cert_request_info.h" #include "net/base/ssl_connection_status_flags.h" #include "net/base/ssl_info.h" +#include "net/socket/ssl_error_params.h" namespace net { @@ -37,7 +38,137 @@ const size_t kMaxRecvBufferSize = 4096; const int kSessionCacheTimeoutSeconds = 60 * 60; const size_t kSessionCacheMaxEntires = 1024; -int MapOpenSSLError(int err) { +// This method doesn't seemed to have made it into the OpenSSL headers. +unsigned long SSL_CIPHER_get_id(const SSL_CIPHER* cipher) { return cipher->id; } + +// Used for encoding the |connection_status| field of an SSLInfo object. +int EncodeSSLConnectionStatus(int cipher_suite, + int compression, + int version) { + return ((cipher_suite & SSL_CONNECTION_CIPHERSUITE_MASK) << + SSL_CONNECTION_CIPHERSUITE_SHIFT) | + ((compression & SSL_CONNECTION_COMPRESSION_MASK) << + SSL_CONNECTION_COMPRESSION_SHIFT) | + ((version & SSL_CONNECTION_VERSION_MASK) << + SSL_CONNECTION_VERSION_SHIFT); +} + +// Returns the net SSL version number (see ssl_connection_status_flags.h) for +// this SSL connection. +int GetNetSSLVersion(SSL* ssl) { + switch(SSL_version(ssl)) { + case SSL2_VERSION: + return SSL_CONNECTION_VERSION_SSL2; + case SSL3_VERSION: + return SSL_CONNECTION_VERSION_SSL3; + case TLS1_VERSION: + return SSL_CONNECTION_VERSION_TLS1; + case 0x0302: + return SSL_CONNECTION_VERSION_TLS1_1; + case 0x0303: + return SSL_CONNECTION_VERSION_TLS1_2; + default: + return SSL_CONNECTION_VERSION_UNKNOWN; + } +} + +int MapOpenSSLErrorSSL() { + // Walk down the error stack to find the SSLerr generated reason. + unsigned long error_code; + do { + error_code = ERR_get_error(); + if (error_code == 0) + return ERR_SSL_PROTOCOL_ERROR; + } while (ERR_GET_LIB(error_code) != ERR_LIB_SSL); + + DVLOG(1) << "OpenSSL SSL error, reason: " << ERR_GET_REASON(error_code) + << ", name: " << ERR_error_string(error_code, NULL); + switch (ERR_GET_REASON(error_code)) { + case SSL_R_READ_TIMEOUT_EXPIRED: + return ERR_TIMED_OUT; + case SSL_R_BAD_RESPONSE_ARGUMENT: + return ERR_INVALID_ARGUMENT; + case SSL_R_UNKNOWN_CERTIFICATE_TYPE: + case SSL_R_UNKNOWN_CIPHER_TYPE: + case SSL_R_UNKNOWN_KEY_EXCHANGE_TYPE: + case SSL_R_UNKNOWN_PKEY_TYPE: + case SSL_R_UNKNOWN_REMOTE_ERROR_TYPE: + case SSL_R_UNKNOWN_SSL_VERSION: + return ERR_NOT_IMPLEMENTED; + // SSL_R_UNKNOWN_PROTOCOL is reported if all the protocol versions + // supported by the server were disabled in this socket instance. + case SSL_R_UNKNOWN_PROTOCOL: + case SSL_R_UNSUPPORTED_SSL_VERSION: + case SSL_R_NO_CIPHER_MATCH: + case SSL_R_NO_SHARED_CIPHER: + case SSL_R_TLSV1_ALERT_INSUFFICIENT_SECURITY: + case SSL_R_TLSV1_ALERT_PROTOCOL_VERSION: + return ERR_SSL_VERSION_OR_CIPHER_MISMATCH; + case SSL_R_SSLV3_ALERT_BAD_CERTIFICATE: + case SSL_R_SSLV3_ALERT_UNSUPPORTED_CERTIFICATE: + case SSL_R_SSLV3_ALERT_CERTIFICATE_REVOKED: + case SSL_R_SSLV3_ALERT_CERTIFICATE_EXPIRED: + case SSL_R_SSLV3_ALERT_CERTIFICATE_UNKNOWN: + case SSL_R_TLSV1_ALERT_ACCESS_DENIED: + case SSL_R_TLSV1_ALERT_UNKNOWN_CA: + return ERR_BAD_SSL_CLIENT_AUTH_CERT; + case SSL_R_BAD_DECOMPRESSION: + case SSL_R_SSLV3_ALERT_DECOMPRESSION_FAILURE: + return ERR_SSL_DECOMPRESSION_FAILURE_ALERT; + case SSL_R_SSLV3_ALERT_BAD_RECORD_MAC: + return ERR_SSL_BAD_RECORD_MAC_ALERT; + case SSL_R_UNSAFE_LEGACY_RENEGOTIATION_DISABLED: + return ERR_SSL_UNSAFE_NEGOTIATION; + case SSL_R_WRONG_NUMBER_OF_KEY_BITS: + return ERR_SSL_WEAK_SERVER_EPHEMERAL_DH_KEY; + case SSL_R_SSL_HANDSHAKE_FAILURE: + case SSL_R_DECRYPTION_FAILED: + case SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC: + case SSL_R_DH_PUBLIC_VALUE_LENGTH_IS_WRONG: + case SSL_R_DIGEST_CHECK_FAILED: + case SSL_R_DUPLICATE_COMPRESSION_ID: + case SSL_R_ECGROUP_TOO_LARGE_FOR_CIPHER: + case SSL_R_ENCRYPTED_LENGTH_TOO_LONG: + case SSL_R_ERROR_IN_RECEIVED_CIPHER_LIST: + case SSL_R_EXCESSIVE_MESSAGE_SIZE: + case SSL_R_EXTRA_DATA_IN_MESSAGE: + case SSL_R_GOT_A_FIN_BEFORE_A_CCS: + case SSL_R_ILLEGAL_PADDING: + case SSL_R_INVALID_CHALLENGE_LENGTH: + case SSL_R_INVALID_COMMAND: + case SSL_R_INVALID_PURPOSE: + case SSL_R_INVALID_STATUS_RESPONSE: + case SSL_R_INVALID_TICKET_KEYS_LENGTH: + case SSL_R_KEY_ARG_TOO_LONG: + case SSL_R_READ_WRONG_PACKET_TYPE: + case SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE: + // TODO(joth): SSL_R_SSLV3_ALERT_HANDSHAKE_FAILURE may be returned from the + // server after receiving ClientHello if there's no common supported cipher. + // Ideally we'd map that specific case to ERR_SSL_VERSION_OR_CIPHER_MISMATCH + // to match the NSS implementation. See also http://goo.gl/oMtZW + case SSL_R_SSLV3_ALERT_HANDSHAKE_FAILURE: + case SSL_R_SSLV3_ALERT_NO_CERTIFICATE: + case SSL_R_SSLV3_ALERT_ILLEGAL_PARAMETER: + case SSL_R_TLSV1_ALERT_DECODE_ERROR: + case SSL_R_TLSV1_ALERT_DECRYPTION_FAILED: + case SSL_R_TLSV1_ALERT_DECRYPT_ERROR: + case SSL_R_TLSV1_ALERT_EXPORT_RESTRICTION: + case SSL_R_TLSV1_ALERT_INTERNAL_ERROR: + case SSL_R_TLSV1_ALERT_NO_RENEGOTIATION: + case SSL_R_TLSV1_ALERT_RECORD_OVERFLOW: + case SSL_R_TLSV1_ALERT_USER_CANCELLED: + return ERR_SSL_PROTOCOL_ERROR; + default: + LOG(WARNING) << "Unmapped error reason: " << ERR_GET_REASON(error_code); + return ERR_FAILED; + } +} + +// Converts an OpenSSL error code into a net error code, walking the OpenSSL +// error stack if needed. Note that |tracer| is not currently used in the +// implementation, but is passed in anyway as this ensures the caller will clear +// any residual codes left on the error stack. +int MapOpenSSLError(int err, const base::OpenSSLErrStackTracer& tracer) { switch (err) { case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: @@ -45,6 +176,8 @@ int MapOpenSSLError(int err) { case SSL_ERROR_SYSCALL: DVLOG(1) << "OpenSSL SYSCALL error, errno " << errno; return ERR_SSL_PROTOCOL_ERROR; + case SSL_ERROR_SSL: + return MapOpenSSLErrorSSL(); default: // TODO(joth): Implement full mapping. LOG(WARNING) << "Unknown OpenSSL error " << err; @@ -128,7 +261,7 @@ class SSLSessionCache { private: // A pair of maps to allow bi-directional lookups between host:port and an - // associated seesion. + // associated session. // TODO(joth): When client certificates are implemented we should key the // cache on the client certificate used in addition to the host-port pair. typedef std::map<HostPortPair, SSL_SESSION*> HostPortMap; @@ -320,6 +453,44 @@ bool SSLClientSocketOpenSSL::Init() { SSL_set_mode(ssl_, mode.set_mask); SSL_clear_mode(ssl_, mode.clear_mask); + + // Removing ciphers by ID from OpenSSL is a bit involved as we must use the + // textual name with SSL_set_cipher_list because there is no public API to + // directly remove a cipher by ID. + STACK_OF(SSL_CIPHER)* ciphers = SSL_get_ciphers(ssl_); + DCHECK(ciphers); + // See SSLConfig::disabled_cipher_suites for description of the suites + // disabled by default. + std::string command("DEFAULT:!NULL:!aNULL:!IDEA:!FZA"); + // Walk through all the installed ciphers, seeing if any need to be + // appended to the cipher removal |command|. + for (int i = 0; i < sk_SSL_CIPHER_num(ciphers); ++i) { + const SSL_CIPHER* cipher = sk_SSL_CIPHER_value(ciphers, i); + const uint16 id = SSL_CIPHER_get_id(cipher); + // Remove any ciphers with a strength of less than 80 bits. Note the NSS + // implementation uses "effective" bits here but OpenSSL does not provide + // this detail. This only impacts Triple DES: reports 112 vs. 168 bits, + // both of which are greater than 80 anyway. + bool disable = SSL_CIPHER_get_bits(cipher, NULL) < 80; + if (!disable) { + disable = std::find(ssl_config_.disabled_cipher_suites.begin(), + ssl_config_.disabled_cipher_suites.end(), id) != + ssl_config_.disabled_cipher_suites.end(); + } + if (disable) { + const char* name = SSL_CIPHER_get_name(cipher); + DVLOG(3) << "Found cipher to remove: '" << name << "', ID: " << id + << " strength: " << SSL_CIPHER_get_bits(cipher, NULL); + command.append(":!"); + command.append(name); + } + } + int rv = SSL_set_cipher_list(ssl_, command.c_str()); + // If this fails (rv = 0) it means there are no ciphers enabled on this SSL. + // This will almost certainly result in the socket failing to complete the + // handshake at which point the appropriate error is bubbled up to the client. + LOG_IF(WARNING, rv != 1) << "SSL_set_cipher_list('" << command << "') " + "returned " << rv; return true; } @@ -361,30 +532,28 @@ void SSLClientSocketOpenSSL::GetSSLInfo(SSLInfo* ssl_info) { const SSL_CIPHER* cipher = SSL_get_current_cipher(ssl_); CHECK(cipher); ssl_info->security_bits = SSL_CIPHER_get_bits(cipher, NULL); - ssl_info->connection_status |= (cipher->id & SSL_CONNECTION_CIPHERSUITE_MASK) - << SSL_CONNECTION_CIPHERSUITE_SHIFT; - DVLOG(2) << SSL_CIPHER_get_name(cipher) << ": cipher ID " << cipher->id - << " security bits " << ssl_info->security_bits; - - // Experimenting suggests the compression object is optional, whereas the - // cipher (above) is always present. const COMP_METHOD* compression = SSL_get_current_compression(ssl_); - if (compression) { - ssl_info->connection_status |= - (compression->type & SSL_CONNECTION_COMPRESSION_MASK) - << SSL_CONNECTION_COMPRESSION_SHIFT; - DVLOG(2) << SSL_COMP_get_name(compression) - << ": compression ID " << compression->type; - } + + ssl_info->connection_status = EncodeSSLConnectionStatus( + SSL_CIPHER_get_id(cipher), + compression ? compression->type : 0, + GetNetSSLVersion(ssl_)); bool peer_supports_renego_ext = !!SSL_get_secure_renegotiation_support(ssl_); if (!peer_supports_renego_ext) ssl_info->connection_status |= SSL_CONNECTION_NO_RENEGOTIATION_EXTENSION; - UMA_HISTOGRAM_ENUMERATION("Net.RenegotiationExtensionSupported", - (int)peer_supports_renego_ext, 2); + UMA_HISTOGRAM_ENUMERATION("Net.RenegotiationExtensionSupported", + implicit_cast<int>(peer_supports_renego_ext), 2); if (ssl_config_.ssl3_fallback) ssl_info->connection_status |= SSL_CONNECTION_SSL3_FALLBACK; + + DVLOG(3) << "Encoded connection status: cipher suite = " + << SSLConnectionStatusToCipherSuite(ssl_info->connection_status) + << " compression = " + << SSLConnectionStatusToCompression(ssl_info->connection_status) + << " version = " + << SSLConnectionStatusToVersion(ssl_info->connection_status); } void SSLClientSocketOpenSSL::GetSSLCertRequestInfo( @@ -553,7 +722,7 @@ int SSLClientSocketOpenSSL::DoHandshake() { GotoState(STATE_VERIFY_CERT); } else { int ssl_error = SSL_get_error(ssl_, rv); - net_error = MapOpenSSLError(ssl_error); + net_error = MapOpenSSLError(ssl_error, err_tracer); // If not done, stay in this state if (net_error == ERR_IO_PENDING) { @@ -562,6 +731,9 @@ int SSLClientSocketOpenSSL::DoHandshake() { LOG(ERROR) << "handshake failed; returned " << rv << ", SSL error code " << ssl_error << ", net_error " << net_error; + net_log_.AddEvent( + NetLog::TYPE_SSL_HANDSHAKE_ERROR, + make_scoped_refptr(new SSLErrorParams(net_error, ssl_error))); } } return net_error; @@ -936,7 +1108,7 @@ int SSLClientSocketOpenSSL::DoPayloadRead() { return rv; int err = SSL_get_error(ssl_, rv); - return MapOpenSSLError(err); + return MapOpenSSLError(err, err_tracer); } int SSLClientSocketOpenSSL::DoPayloadWrite() { @@ -947,7 +1119,7 @@ int SSLClientSocketOpenSSL::DoPayloadWrite() { return rv; int err = SSL_get_error(ssl_, rv); - return MapOpenSSLError(err); + return MapOpenSSLError(err, err_tracer); } } // namespace net diff --git a/net/socket/ssl_client_socket_unittest.cc b/net/socket/ssl_client_socket_unittest.cc index a95585c..0410a06 100644 --- a/net/socket/ssl_client_socket_unittest.cc +++ b/net/socket/ssl_client_socket_unittest.cc @@ -525,16 +525,9 @@ TEST_F(SSLClientSocketTest, PrematureApplicationData) { EXPECT_EQ(net::ERR_SSL_PROTOCOL_ERROR, rv); } -#if defined(USE_OPENSSL) -// TODO(rsleevi): Not implemented for Schannel or OpenSSL. Schannel is -// controlled by the SSL client socket factory, rather than a define, so it -// cannot be conditionally disabled here. As Schannel is only used when +// TODO(rsleevi): Not implemented for Schannel. As Schannel is only used when // performing client authentication, it will not be tested here. -#define MAYBE_CipherSuiteDisables DISABLED_CipherSuiteDisables -#else -#define MAYBE_CipherSuiteDisables CipherSuiteDisables -#endif -TEST_F(SSLClientSocketTest, MAYBE_CipherSuiteDisables) { +TEST_F(SSLClientSocketTest, CipherSuiteDisables) { // Rather than exhaustively disabling every RC4 ciphersuite defined at // http://www.iana.org/assignments/tls-parameters/tls-parameters.xml, // only disabling those cipher suites that the test server actually |