summaryrefslogtreecommitdiffstats
path: root/net/socket
diff options
context:
space:
mode:
Diffstat (limited to 'net/socket')
-rw-r--r--net/socket/ssl_client_socket_openssl.cc214
-rw-r--r--net/socket/ssl_client_socket_unittest.cc11
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