diff options
| author | Ben Murdoch <benm@google.com> | 2010-11-18 18:32:45 +0000 |
|---|---|---|
| committer | Ben Murdoch <benm@google.com> | 2010-11-18 18:38:07 +0000 |
| commit | 513209b27ff55e2841eac0e4120199c23acce758 (patch) | |
| tree | aeba30bb08c5f47c57003544e378a377c297eee6 /net/socket/ssl_client_socket_nss.cc | |
| parent | 164f7496de0fbee436b385a79ead9e3cb81a50c1 (diff) | |
| download | external_chromium-513209b27ff55e2841eac0e4120199c23acce758.zip external_chromium-513209b27ff55e2841eac0e4120199c23acce758.tar.gz external_chromium-513209b27ff55e2841eac0e4120199c23acce758.tar.bz2 | |
Merge Chromium at r65505: Initial merge by git.
Change-Id: I31d8f1d8cd33caaf7f47ffa7350aef42d5fbdb45
Diffstat (limited to 'net/socket/ssl_client_socket_nss.cc')
| -rw-r--r-- | net/socket/ssl_client_socket_nss.cc | 882 |
1 files changed, 562 insertions, 320 deletions
diff --git a/net/socket/ssl_client_socket_nss.cc b/net/socket/ssl_client_socket_nss.cc index 136f138..a6aa458 100644 --- a/net/socket/ssl_client_socket_nss.cc +++ b/net/socket/ssl_client_socket_nss.cc @@ -50,6 +50,9 @@ #if defined(USE_SYSTEM_SSL) #include <dlfcn.h> #endif +#if defined(OS_MACOSX) +#include <Security/Security.h> +#endif #include <certdb.h> #include <hasht.h> #include <keyhi.h> @@ -60,6 +63,7 @@ #include <sechash.h> #include <ssl.h> #include <sslerr.h> +#include <sslproto.h> #include <limits> @@ -71,10 +75,12 @@ #include "base/string_number_conversions.h" #include "base/string_util.h" #include "base/stringprintf.h" +#include "base/thread_restrictions.h" #include "base/values.h" #include "net/base/address_list.h" #include "net/base/cert_status_flags.h" #include "net/base/cert_verifier.h" +#include "net/base/connection_type_histograms.h" #include "net/base/dns_util.h" #include "net/base/dnsrr_resolver.h" #include "net/base/dnssec_chain_verifier.h" @@ -87,6 +93,7 @@ #include "net/base/sys_addrinfo.h" #include "net/ocsp/nss_ocsp.h" #include "net/socket/client_socket_handle.h" +#include "net/socket/dns_cert_provenance_check.h" #include "net/socket/ssl_host_info.h" static const int kRecvBufferSize = 4096; @@ -176,6 +183,11 @@ class NSSSSLInitSingleton { // thread-safe, and the NSS SSL library will only ever be initialized once. // The NSS SSL library will be properly shut down on program exit. void EnsureNSSSSLInit() { + // Initializing SSL causes us to do blocking IO. + // Temporarily allow it until we fix + // http://code.google.com/p/chromium/issues/detail?id=59847 + base::ThreadRestrictions::ScopedAllowIO allow_io; + Singleton<NSSSSLInitSingleton>::get(); } @@ -206,6 +218,8 @@ int MapNSPRError(PRErrorCode err) { return ERR_INVALID_ARGUMENT; case PR_END_OF_FILE_ERROR: return ERR_CONNECTION_CLOSED; + case PR_NOT_IMPLEMENTED_ERROR: + return ERR_NOT_IMPLEMENTED; case SEC_ERROR_INVALID_ARGS: return ERR_INVALID_ARGUMENT; @@ -309,52 +323,13 @@ class SSLFailedNSSFunctionParams : public NetLog::EventParameters { void LogFailedNSSFunction(const BoundNetLog& net_log, const char* function, const char* param) { - net_log.AddEvent(NetLog::TYPE_SSL_NSS_ERROR, - new SSLFailedNSSFunctionParams(function, param)); + net_log.AddEvent( + NetLog::TYPE_SSL_NSS_ERROR, + make_scoped_refptr(new SSLFailedNSSFunctionParams(function, param))); } #if defined(OS_WIN) -// A certificate for COMODO EV SGC CA, issued by AddTrust External CA Root, -// causes CertGetCertificateChain to report CERT_TRUST_IS_NOT_VALID_FOR_USAGE. -// It seems to be caused by the szOID_APPLICATION_CERT_POLICIES extension in -// that certificate. -// -// This function is used in the workaround for http://crbug.com/43538 -bool IsProblematicComodoEVCACert(const CERTCertificate& cert) { - // Issuer: - // CN = AddTrust External CA Root - // OU = AddTrust External TTP Network - // O = AddTrust AB - // C = SE - static const uint8 kIssuer[] = { - 0x30, 0x6f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, - 0x06, 0x13, 0x02, 0x53, 0x45, 0x31, 0x14, 0x30, 0x12, 0x06, - 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b, 0x41, 0x64, 0x64, 0x54, - 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31, 0x26, 0x30, - 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64, - 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, - 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, - 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, - 0x20, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x19, 0x41, 0x64, - 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, - 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52, - 0x6f, 0x6f, 0x74 - }; - - // Serial number: 79:0A:83:4D:48:40:6B:AB:6C:35:2A:D5:1F:42:83:FE. - static const uint8 kSerialNumber[] = { - 0x79, 0x0a, 0x83, 0x4d, 0x48, 0x40, 0x6b, 0xab, 0x6c, 0x35, - 0x2a, 0xd5, 0x1f, 0x42, 0x83, 0xfe - }; - - return cert.derIssuer.len == sizeof(kIssuer) && - memcmp(cert.derIssuer.data, kIssuer, cert.derIssuer.len) == 0 && - cert.serialNumber.len == sizeof(kSerialNumber) && - memcmp(cert.serialNumber.data, kSerialNumber, - cert.serialNumber.len) == 0; -} - // This callback is intended to be used with CertFindChainInStore. In addition // to filtering by extended/enhanced key usage, we do not show expired // certificates and require digital signature usage in the key usage @@ -391,17 +366,61 @@ BOOL WINAPI ClientCertFindCallback(PCCERT_CONTEXT cert_context, #endif -} // namespace +// PeerCertificateChain is a helper object which extracts the certificate +// chain, as given by the server, from an NSS socket and performs the needed +// resource management. The first element of the chain is the leaf certificate +// and the other elements are in the order given by the server. +class PeerCertificateChain { + public: + explicit PeerCertificateChain(PRFileDesc* nss_fd) + : num_certs_(0), + certs_(NULL) { + SECStatus rv = SSL_PeerCertificateChain(nss_fd, NULL, &num_certs_); + DCHECK_EQ(rv, SECSuccess); -#if defined(OS_WIN) -// static -HCERTSTORE SSLClientSocketNSS::cert_store_ = NULL; -#endif + certs_ = new CERTCertificate*[num_certs_]; + const unsigned expected_num_certs = num_certs_; + rv = SSL_PeerCertificateChain(nss_fd, certs_, &num_certs_); + DCHECK_EQ(rv, SECSuccess); + DCHECK_EQ(num_certs_, expected_num_certs); + } + + ~PeerCertificateChain() { + for (unsigned i = 0; i < num_certs_; i++) + CERT_DestroyCertificate(certs_[i]); + delete[] certs_; + } + + unsigned size() const { return num_certs_; } + + CERTCertificate* operator[](unsigned i) { + DCHECK_LT(i, num_certs_); + return certs_[i]; + } + + std::vector<base::StringPiece> AsStringPieceVector() const { + std::vector<base::StringPiece> v(size()); + for (unsigned i = 0; i < size(); i++) { + v[i] = base::StringPiece( + reinterpret_cast<const char*>(certs_[i]->derCert.data), + certs_[i]->derCert.len); + } + + return v; + } + + private: + unsigned num_certs_; + CERTCertificate** certs_; +}; + +} // namespace SSLClientSocketNSS::SSLClientSocketNSS(ClientSocketHandle* transport_socket, const std::string& hostname, const SSLConfig& ssl_config, - SSLHostInfo* ssl_host_info) + SSLHostInfo* ssl_host_info, + DnsRRResolver* dnsrr_resolver) : ALLOW_THIS_IN_INITIALIZER_LIST(buffer_send_callback_( this, &SSLClientSocketNSS::BufferSendComplete)), ALLOW_THIS_IN_INITIALIZER_LIST(buffer_recv_callback_( @@ -420,12 +439,15 @@ SSLClientSocketNSS::SSLClientSocketNSS(ClientSocketHandle* transport_socket, user_read_buf_len_(0), user_write_buf_len_(0), server_cert_nss_(NULL), + server_cert_verify_result_(NULL), + ssl_connection_status_(0), client_auth_cert_needed_(false), handshake_callback_called_(false), completed_handshake_(false), pseudo_connected_(false), eset_mitm_detected_(false), - netnanny_mitm_detected_(false), + predicted_cert_chain_correct_(false), + peername_initialized_(false), dnssec_provider_(NULL), next_handshake_state_(STATE_NONE), nss_fd_(NULL), @@ -433,7 +455,8 @@ SSLClientSocketNSS::SSLClientSocketNSS(ClientSocketHandle* transport_socket, net_log_(transport_socket->socket()->NetLog()), predicted_npn_status_(kNextProtoUnsupported), predicted_npn_proto_used_(false), - ssl_host_info_(ssl_host_info) { + ssl_host_info_(ssl_host_info), + dnsrr_resolver_(dnsrr_resolver) { EnterFunction(""); } @@ -475,6 +498,8 @@ void SSLClientSocketNSS::SaveSnapStartInfo() { NOTREACHED(); return; } + net_log_.AddEvent(NetLog::TYPE_SSL_SNAP_START, + new NetLogIntegerParameter("type", snap_start_type)); LOG(ERROR) << "Snap Start: " << snap_start_type << " " << hostname_; if (snap_start_type == SSL_SNAP_START_FULL || snap_start_type == SSL_SNAP_START_RESUME) { @@ -490,43 +515,32 @@ void SSLClientSocketNSS::SaveSnapStartInfo() { NOTREACHED(); return; } - // If the server doesn't support Snap Start then |hello_data_len| is zero. - if (!hello_data_len) - return; if (hello_data_len > std::numeric_limits<uint16>::max()) return; SSLHostInfo::State* state = ssl_host_info_->mutable_state(); state->server_hello = std::string(reinterpret_cast<const char *>(hello_data), hello_data_len); - state->npn_valid = true; - state->npn_status = GetNextProto(&state->npn_protocol); - - // TODO(wtc): CERT_GetCertChainFromCert might not return the same cert chain - // that the Certificate message actually contained. http://crbug.com/48854 - CERTCertList* cert_list = CERT_GetCertChainFromCert( - server_cert_nss_, PR_Now(), certUsageSSLCA); - if (!cert_list) - return; + if (hello_data_len > 0) { + state->npn_valid = true; + state->npn_status = GetNextProto(&state->npn_protocol); + } else { + state->npn_valid = false; + } - for (CERTCertListNode* node = CERT_LIST_HEAD(cert_list); - !CERT_LIST_END(node, cert_list); - node = CERT_LIST_NEXT(node)) { - if (node->cert->derCert.len > std::numeric_limits<uint16>::max()) { - CERT_DestroyCertList(cert_list); + state->certs.clear(); + PeerCertificateChain certs(nss_fd_); + for (unsigned i = 0; i < certs.size(); i++) { + if (certs[i]->derCert.len > std::numeric_limits<uint16>::max()) return; - } - if (node->cert->isRoot == PR_TRUE) - continue; + state->certs.push_back(std::string( - reinterpret_cast<char*>(node->cert->derCert.data), - node->cert->derCert.len)); + reinterpret_cast<char*>(certs[i]->derCert.data), + certs[i]->derCert.len)); } LOG(ERROR) << "Setting Snap Start info " << hostname_; ssl_host_info_->Persist(); - - CERT_DestroyCertList(cert_list); } static void DestroyCertificates(CERTCertificate** certs, unsigned len) { @@ -625,6 +639,16 @@ int SSLClientSocketNSS::Connect(CompletionCallback* callback) { return rv; } + // Attempt to initialize the peer name. In the case of TCP FastOpen, + // we don't have the peer yet. + if (!UsingTCPFastOpen()) { + rv = InitializeSSLPeerName(); + if (rv != OK) { + net_log_.EndEvent(NetLog::TYPE_SSL_CONNECT, NULL); + return rv; + } + } + if (ssl_config_.snap_start_enabled && ssl_host_info_.get()) { GotoState(STATE_SNAP_START_LOAD_INFO); } else { @@ -655,28 +679,6 @@ int SSLClientSocketNSS::InitializeSSLOptions() { return ERR_OUT_OF_MEMORY; // TODO(port): map NSPR error code. } - // Tell NSS who we're connected to - AddressList peer_address; - int err = transport_->socket()->GetPeerAddress(&peer_address); - if (err != OK) - return err; - - const struct addrinfo* ai = peer_address.head(); - - PRNetAddr peername; - memset(&peername, 0, sizeof(peername)); - DCHECK_LE(ai->ai_addrlen, sizeof(peername)); - size_t len = std::min(static_cast<size_t>(ai->ai_addrlen), sizeof(peername)); - memcpy(&peername, ai->ai_addr, len); - - // Adjust the address family field for BSD, whose sockaddr - // structure has a one-byte length and one-byte address family - // field at the beginning. PRNetAddr has a two-byte address - // family field at the beginning. - peername.raw.family = ai->ai_addr->sa_family; - - memio_SetPeerName(nss_fd_, &peername); - // Grab pointer to buffers nss_bufs_ = memio_GetSecret(nss_fd_); @@ -761,7 +763,7 @@ int SSLClientSocketNSS::InitializeSSLOptions() { // TODO(agl): check that SSL_ENABLE_SNAP_START actually does something in the // current NSS code. rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_SNAP_START, - SSLConfigService::snap_start_enabled()); + ssl_config_.snap_start_enabled); if (rv != SECSuccess) VLOG(1) << "SSL_ENABLE_SNAP_START failed. Old system nss?"; #endif @@ -816,7 +818,12 @@ int SSLClientSocketNSS::InitializeSSLOptions() { return ERR_UNEXPECTED; } +#if defined(NSS_PLATFORM_CLIENT_AUTH) + rv = SSL_GetPlatformClientAuthDataHook(nss_fd_, PlatformClientAuthHandler, + this); +#else rv = SSL_GetClientAuthDataHook(nss_fd_, ClientAuthHandler, this); +#endif if (rv != SECSuccess) { LogFailedNSSFunction(net_log_, "SSL_GetClientAuthDataHook", ""); return ERR_UNEXPECTED; @@ -831,6 +838,36 @@ int SSLClientSocketNSS::InitializeSSLOptions() { // Tell SSL the hostname we're trying to connect to. SSL_SetURL(nss_fd_, hostname_.c_str()); + // Tell SSL we're a client; needed if not letting NSPR do socket I/O + SSL_ResetHandshake(nss_fd_, 0); + + return OK; +} + +int SSLClientSocketNSS::InitializeSSLPeerName() { + // Tell NSS who we're connected to + AddressList peer_address; + int err = transport_->socket()->GetPeerAddress(&peer_address); + if (err != OK) + return err; + + const struct addrinfo* ai = peer_address.head(); + + PRNetAddr peername; + memset(&peername, 0, sizeof(peername)); + DCHECK_LE(ai->ai_addrlen, sizeof(peername)); + size_t len = std::min(static_cast<size_t>(ai->ai_addrlen), + sizeof(peername)); + memcpy(&peername, ai->ai_addr, len); + + // Adjust the address family field for BSD, whose sockaddr + // structure has a one-byte length and one-byte address family + // field at the beginning. PRNetAddr has a two-byte address + // family field at the beginning. + peername.raw.family = ai->ai_addr->sa_family; + + memio_SetPeerName(nss_fd_, &peername); + // Set the peer ID for session reuse. This is necessary when we create an // SSL tunnel through a proxy -- GetPeerName returns the proxy's address // rather than the destination server's address in that case. @@ -838,13 +875,11 @@ int SSLClientSocketNSS::InitializeSSLOptions() { // used. std::string peer_id = base::StringPrintf("%s:%d", hostname_.c_str(), peer_address.GetPort()); - rv = SSL_SetSockPeerID(nss_fd_, const_cast<char*>(peer_id.c_str())); + SECStatus rv = SSL_SetSockPeerID(nss_fd_, const_cast<char*>(peer_id.c_str())); if (rv != SECSuccess) LogFailedNSSFunction(net_log_, "SSL_SetSockPeerID", peer_id.c_str()); - // Tell SSL we're a client; needed if not letting NSPR do socket I/O - SSL_ResetHandshake(nss_fd_, 0); - + peername_initialized_ = true; return OK; } @@ -885,11 +920,14 @@ void SSLClientSocketNSS::Disconnect() { CERT_DestroyCertificate(server_cert_nss_); server_cert_nss_ = NULL; } - server_cert_verify_result_.Reset(); + local_server_cert_verify_result_.Reset(); + server_cert_verify_result_ = NULL; + ssl_connection_status_ = 0; completed_handshake_ = false; pseudo_connected_ = false; eset_mitm_detected_ = false; - netnanny_mitm_detected_= false; + predicted_cert_chain_correct_ = false; + peername_initialized_ = false; nss_bufs_ = NULL; client_certs_.clear(); client_auth_cert_needed_ = false; @@ -955,6 +993,14 @@ bool SSLClientSocketNSS::WasEverUsed() const { return false; } +bool SSLClientSocketNSS::UsingTCPFastOpen() const { + if (transport_.get() && transport_->socket()) { + return transport_->socket()->UsingTCPFastOpen(); + } + NOTREACHED(); + return false; +} + int SSLClientSocketNSS::Read(IOBuffer* buf, int buf_len, CompletionCallback* callback) { EnterFunction(buf_len); @@ -1044,157 +1090,110 @@ bool SSLClientSocketNSS::SetSendBufferSize(int32 size) { return transport_->socket()->SetSendBufferSize(size); } -#if defined(OS_WIN) -// static -X509Certificate::OSCertHandle SSLClientSocketNSS::CreateOSCert( - const SECItem& der_cert) { - // TODO(wtc): close cert_store_ at shutdown. - if (!cert_store_) - cert_store_ = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, NULL, 0, NULL); - - X509Certificate::OSCertHandle cert_handle = NULL; - BOOL ok = CertAddEncodedCertificateToStore( - cert_store_, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - der_cert.data, der_cert.len, CERT_STORE_ADD_USE_EXISTING, &cert_handle); - return ok ? cert_handle : NULL; -} -#elif defined(OS_MACOSX) -// static -X509Certificate::OSCertHandle SSLClientSocketNSS::CreateOSCert( - const SECItem& der_cert) { - return X509Certificate::CreateOSCertHandleFromBytes( - reinterpret_cast<char*>(der_cert.data), der_cert.len); -} -#endif - +// Sets server_cert_ and server_cert_nss_ if not yet set. +// Returns server_cert_. X509Certificate *SSLClientSocketNSS::UpdateServerCert() { - // We set the server_cert_ from HandshakeCallback(), but this handler - // does not necessarily get called if we are continuing a cached SSL - // session. + // We set the server_cert_ from HandshakeCallback(). if (server_cert_ == NULL) { server_cert_nss_ = SSL_PeerCertificate(nss_fd_); if (server_cert_nss_) { -#if defined(OS_MACOSX) || defined(OS_WIN) - // Get each of the intermediate certificates in the server's chain. - // These will be added to the server's X509Certificate object, making - // them available to X509Certificate::Verify() for chain building. - X509Certificate::OSCertHandles intermediate_ca_certs; - X509Certificate::OSCertHandle cert_handle = NULL; - CERTCertList* cert_list = CERT_GetCertChainFromCert( - server_cert_nss_, PR_Now(), certUsageSSLCA); - if (cert_list) { - for (CERTCertListNode* node = CERT_LIST_HEAD(cert_list); - !CERT_LIST_END(node, cert_list); - node = CERT_LIST_NEXT(node)) { - if (node->cert == server_cert_nss_) - continue; -#if defined(OS_WIN) - // Work around http://crbug.com/43538 by not importing the - // problematic COMODO EV SGC CA certificate. CryptoAPI will - // download a good certificate for that CA, issued by COMODO - // Certification Authority, using the AIA extension in the server - // certificate. - if (IsProblematicComodoEVCACert(*node->cert)) - continue; -#endif - cert_handle = CreateOSCert(node->cert->derCert); - DCHECK(cert_handle); - intermediate_ca_certs.push_back(cert_handle); - } - CERT_DestroyCertList(cert_list); - } - - // Finally create the X509Certificate object. - cert_handle = CreateOSCert(server_cert_nss_->derCert); - DCHECK(cert_handle); - server_cert_ = X509Certificate::CreateFromHandle( - cert_handle, - X509Certificate::SOURCE_FROM_NETWORK, - intermediate_ca_certs); - X509Certificate::FreeOSCertHandle(cert_handle); - for (size_t i = 0; i < intermediate_ca_certs.size(); ++i) - X509Certificate::FreeOSCertHandle(intermediate_ca_certs[i]); -#else - server_cert_ = X509Certificate::CreateFromHandle( - server_cert_nss_, - X509Certificate::SOURCE_FROM_NETWORK, - X509Certificate::OSCertHandles()); -#endif + PeerCertificateChain certs(nss_fd_); + server_cert_ = X509Certificate::CreateFromDERCertChain( + certs.AsStringPieceVector()); } } return server_cert_; } -// Log an informational message if the server does not support secure -// renegotiation (RFC 5746). -void SSLClientSocketNSS::CheckSecureRenegotiation() const { - // SSL_HandshakeNegotiatedExtension was added in NSS 3.12.6. - // Since SSL_MAX_EXTENSIONS was added at the same time, we can test - // SSL_MAX_EXTENSIONS for the presence of SSL_HandshakeNegotiatedExtension. -#if defined(SSL_MAX_EXTENSIONS) - PRBool received_renego_info; - if (SSL_HandshakeNegotiatedExtension(nss_fd_, ssl_renegotiation_info_xtn, - &received_renego_info) == SECSuccess && - !received_renego_info) { - VLOG(1) << "The server " << hostname_ - << " does not support the TLS renegotiation_info extension."; - } -#endif -} - -void SSLClientSocketNSS::GetSSLInfo(SSLInfo* ssl_info) { - EnterFunction(""); - ssl_info->Reset(); - - if (!server_cert_) { - LOG(DFATAL) << "!server_cert_"; - return; - } - +// Sets ssl_connection_status_. +void SSLClientSocketNSS::UpdateConnectionStatus() { SSLChannelInfo channel_info; SECStatus ok = SSL_GetChannelInfo(nss_fd_, &channel_info, sizeof(channel_info)); if (ok == SECSuccess && channel_info.length == sizeof(channel_info) && channel_info.cipherSuite) { - SSLCipherSuiteInfo cipher_info; - ok = SSL_GetCipherSuiteInfo(channel_info.cipherSuite, - &cipher_info, sizeof(cipher_info)); - if (ok == SECSuccess) { - ssl_info->security_bits = cipher_info.effectiveKeyBits; - } else { - ssl_info->security_bits = -1; - LOG(DFATAL) << "SSL_GetCipherSuiteInfo returned " << PR_GetError() - << " for cipherSuite " << channel_info.cipherSuite; - } - ssl_info->connection_status |= - (((int)channel_info.cipherSuite) & SSL_CONNECTION_CIPHERSUITE_MASK) << + ssl_connection_status_ |= + (static_cast<int>(channel_info.cipherSuite) & + SSL_CONNECTION_CIPHERSUITE_MASK) << SSL_CONNECTION_CIPHERSUITE_SHIFT; - ssl_info->connection_status |= - (((int)channel_info.compressionMethod) & + ssl_connection_status_ |= + (static_cast<int>(channel_info.compressionMethod) & SSL_CONNECTION_COMPRESSION_MASK) << SSL_CONNECTION_COMPRESSION_SHIFT; - UpdateServerCert(); + // NSS 3.12.x doesn't have version macros for TLS 1.1 and 1.2 (because NSS + // doesn't support them yet), so we use 0x0302 and 0x0303 directly. + int version = SSL_CONNECTION_VERSION_UNKNOWN; + if (channel_info.protocolVersion < SSL_LIBRARY_VERSION_3_0) { + // All versions less than SSL_LIBRARY_VERSION_3_0 are treated as SSL + // version 2. + version = SSL_CONNECTION_VERSION_SSL2; + } else if (channel_info.protocolVersion == SSL_LIBRARY_VERSION_3_0) { + version = SSL_CONNECTION_VERSION_SSL3; + } else if (channel_info.protocolVersion == SSL_LIBRARY_VERSION_3_1_TLS) { + version = SSL_CONNECTION_VERSION_TLS1; + } else if (channel_info.protocolVersion == 0x0302) { + version = SSL_CONNECTION_VERSION_TLS1_1; + } else if (channel_info.protocolVersion == 0x0303) { + version = SSL_CONNECTION_VERSION_TLS1_2; + } + ssl_connection_status_ |= + (version & SSL_CONNECTION_VERSION_MASK) << + SSL_CONNECTION_VERSION_SHIFT; } - ssl_info->cert_status = server_cert_verify_result_.cert_status; - DCHECK(server_cert_ != NULL); - ssl_info->cert = server_cert_; + // SSL_HandshakeNegotiatedExtension was added in NSS 3.12.6. + // Since SSL_MAX_EXTENSIONS was added at the same time, we can test + // SSL_MAX_EXTENSIONS for the presence of SSL_HandshakeNegotiatedExtension. +#if defined(SSL_MAX_EXTENSIONS) PRBool peer_supports_renego_ext; ok = SSL_HandshakeNegotiatedExtension(nss_fd_, ssl_renegotiation_info_xtn, &peer_supports_renego_ext); if (ok == SECSuccess) { - if (!peer_supports_renego_ext) - ssl_info->connection_status |= SSL_CONNECTION_NO_RENEGOTIATION_EXTENSION; + if (!peer_supports_renego_ext) { + ssl_connection_status_ |= SSL_CONNECTION_NO_RENEGOTIATION_EXTENSION; + // Log an informational message if the server does not support secure + // renegotiation (RFC 5746). + VLOG(1) << "The server " << hostname_ + << " does not support the TLS renegotiation_info extension."; + } UMA_HISTOGRAM_ENUMERATION("Net.RenegotiationExtensionSupported", - (int)peer_supports_renego_ext, 2); + peer_supports_renego_ext, 2); } +#endif if (ssl_config_.ssl3_fallback) - ssl_info->connection_status |= SSL_CONNECTION_SSL3_FALLBACK; + ssl_connection_status_ |= SSL_CONNECTION_SSL3_FALLBACK; +} + +void SSLClientSocketNSS::GetSSLInfo(SSLInfo* ssl_info) { + EnterFunction(""); + ssl_info->Reset(); + if (!server_cert_) { + LOG(DFATAL) << "!server_cert_"; + return; + } + + ssl_info->cert_status = server_cert_verify_result_->cert_status; + DCHECK(server_cert_ != NULL); + ssl_info->cert = server_cert_; + ssl_info->connection_status = ssl_connection_status_; + + PRUint16 cipher_suite = + SSLConnectionStatusToCipherSuite(ssl_connection_status_); + SSLCipherSuiteInfo cipher_info; + SECStatus ok = SSL_GetCipherSuiteInfo(cipher_suite, + &cipher_info, sizeof(cipher_info)); + if (ok == SECSuccess) { + ssl_info->security_bits = cipher_info.effectiveKeyBits; + } else { + ssl_info->security_bits = -1; + LOG(DFATAL) << "SSL_GetCipherSuiteInfo returned " << PR_GetError() + << " for cipherSuite " << cipher_suite; + } LeaveFunction(""); } @@ -1378,8 +1377,11 @@ static PRErrorCode MapErrorToNSS(int result) { case ERR_IO_PENDING: return PR_WOULD_BLOCK_ERROR; case ERR_ACCESS_DENIED: + case ERR_NETWORK_ACCESS_DENIED: // For connect, this could be mapped to PR_ADDRESS_NOT_SUPPORTED_ERROR. return PR_NO_ACCESS_RIGHTS_ERROR; + case ERR_NOT_IMPLEMENTED: + return PR_NOT_IMPLEMENTED_ERROR; case ERR_INTERNET_DISCONNECTED: // Equivalent to ENETDOWN. return PR_NETWORK_UNREACHABLE_ERROR; // Best approximation. case ERR_CONNECTION_TIMED_OUT: @@ -1437,7 +1439,7 @@ int SSLClientSocketNSS::BufferSend(void) { int rv = 0; if (len) { - scoped_refptr<IOBuffer> send_buffer = new IOBuffer(len); + scoped_refptr<IOBuffer> send_buffer(new IOBuffer(len)); memcpy(send_buffer->data(), buf1, len1); memcpy(send_buffer->data() + len1, buf2, len2); rv = transport_->socket()->Write(send_buffer, len, @@ -1455,6 +1457,11 @@ int SSLClientSocketNSS::BufferSend(void) { void SSLClientSocketNSS::BufferSendComplete(int result) { EnterFunction(result); + + // In the case of TCP FastOpen, connect is now finished. + if (!peername_initialized_ && UsingTCPFastOpen()) + InitializeSSLPeerName(); + memio_PutWriteResult(nss_bufs_, MapErrorToNSS(result)); transport_send_busy_ = false; OnSendComplete(result); @@ -1565,7 +1572,8 @@ int SSLClientSocketNSS::DoReadLoop(int result) { if (!nss_bufs_) { LOG(DFATAL) << "!nss_bufs_"; int rv = ERR_UNEXPECTED; - net_log_.AddEvent(NetLog::TYPE_SSL_READ_ERROR, new SSLErrorParams(rv, 0)); + net_log_.AddEvent(NetLog::TYPE_SSL_READ_ERROR, + make_scoped_refptr(new SSLErrorParams(rv, 0))); return rv; } @@ -1591,7 +1599,8 @@ int SSLClientSocketNSS::DoWriteLoop(int result) { if (!nss_bufs_) { LOG(DFATAL) << "!nss_bufs_"; int rv = ERR_UNEXPECTED; - net_log_.AddEvent(NetLog::TYPE_SSL_WRITE_ERROR, new SSLErrorParams(rv, 0)); + net_log_.AddEvent(NetLog::TYPE_SSL_WRITE_ERROR, + make_scoped_refptr(new SSLErrorParams(rv, 0))); return rv; } @@ -1624,16 +1633,10 @@ SECStatus SSLClientSocketNSS::OwnAuthCertHandler(void* arg, // different reads or not, depending on network conditions. PRBool false_start = 0; SECStatus rv = SSL_OptionGet(socket, SSL_ENABLE_FALSE_START, &false_start); - if (rv != SECSuccess) - NOTREACHED(); + DCHECK_EQ(SECSuccess, rv); + if (false_start) { SSLClientSocketNSS* that = reinterpret_cast<SSLClientSocketNSS*>(arg); - if (!that->handshake_callback_called_) { - that->corked_ = true; - that->uncork_timer_.Start( - base::TimeDelta::FromMilliseconds(kCorkTimeoutMs), - that, &SSLClientSocketNSS::UncorkAfterTimeout); - } // ESET anti-virus is capable of intercepting HTTPS connections on Windows. // However, it is False Start intolerant and causes the connections to hang @@ -1646,12 +1649,24 @@ SECStatus SSLClientSocketNSS::OwnAuthCertHandler(void* arg, if (common_name) { if (strcmp(common_name, "ESET_RootSslCert") == 0) that->eset_mitm_detected_ = true; - if (strcmp(common_name, "ContentWatch Root Certificate Authority") == 0) - that->netnanny_mitm_detected_ = true; + if (strcmp(common_name, "ContentWatch Root Certificate Authority") == 0) { + // This is NetNanny. NetNanny are updating their product so we + // silently disable False Start for now. + rv = SSL_OptionSet(socket, SSL_ENABLE_FALSE_START, PR_FALSE); + DCHECK_EQ(SECSuccess, rv); + false_start = 0; + } PORT_Free(common_name); } CERT_DestroyCertificate(cert); } + + if (false_start && !that->handshake_callback_called_) { + that->corked_ = true; + that->uncork_timer_.Start( + base::TimeDelta::FromMilliseconds(kCorkTimeoutMs), + that, &SSLClientSocketNSS::UncorkAfterTimeout); + } } #endif @@ -1659,24 +1674,69 @@ SECStatus SSLClientSocketNSS::OwnAuthCertHandler(void* arg, return SECSuccess; } +#if defined(NSS_PLATFORM_CLIENT_AUTH) // static // NSS calls this if a client certificate is needed. -// Based on Mozilla's NSS_GetClientAuthData. -SECStatus SSLClientSocketNSS::ClientAuthHandler( +SECStatus SSLClientSocketNSS::PlatformClientAuthHandler( void* arg, PRFileDesc* socket, CERTDistNames* ca_names, - CERTCertificate** result_certificate, - SECKEYPrivateKey** result_private_key) { + CERTCertList** result_certs, + void** result_private_key) { SSLClientSocketNSS* that = reinterpret_cast<SSLClientSocketNSS*>(arg); that->client_auth_cert_needed_ = !that->ssl_config_.send_client_cert; - #if defined(OS_WIN) if (that->ssl_config_.send_client_cert) { - // TODO(wtc): SSLClientSocketNSS can't do SSL client authentication using - // CryptoAPI yet (http://crbug.com/37560), so client_cert must be NULL. - DCHECK(!that->ssl_config_.client_cert); + if (that->ssl_config_.client_cert) { + PCCERT_CONTEXT cert_context = + that->ssl_config_.client_cert->os_cert_handle(); + HCRYPTPROV provider = NULL; + DWORD key_spec = AT_KEYEXCHANGE; + BOOL must_free = FALSE; + BOOL acquired_key = CryptAcquireCertificatePrivateKey( + cert_context, + CRYPT_ACQUIRE_CACHE_FLAG | CRYPT_ACQUIRE_COMPARE_KEY_FLAG, + NULL, &provider, &key_spec, &must_free); + if (acquired_key && provider) { + DCHECK_NE(key_spec, CERT_NCRYPT_KEY_SPEC); + + // The certificate cache may have been updated/used, in which case, + // duplicate the existing handle, since NSS will free it when no + // longer in use. + if (!must_free) + CryptContextAddRef(provider, NULL, 0); + + SECItem der_cert; + der_cert.type = siDERCertBuffer; + der_cert.data = cert_context->pbCertEncoded; + der_cert.len = cert_context->cbCertEncoded; + + // TODO(rsleevi): Error checking for NSS allocation errors. + *result_certs = CERT_NewCertList(); + CERTCertDBHandle* db_handle = CERT_GetDefaultCertDB(); + CERTCertificate* user_cert = CERT_NewTempCertificate( + db_handle, &der_cert, NULL, PR_FALSE, PR_TRUE); + CERT_AddCertToListTail(*result_certs, user_cert); + + // Add the intermediates. + X509Certificate::OSCertHandles intermediates = + that->ssl_config_.client_cert->GetIntermediateCertificates(); + for (X509Certificate::OSCertHandles::const_iterator it = + intermediates.begin(); it != intermediates.end(); ++it) { + der_cert.data = (*it)->pbCertEncoded; + der_cert.len = (*it)->cbCertEncoded; + + CERTCertificate* intermediate = CERT_NewTempCertificate( + db_handle, &der_cert, NULL, PR_FALSE, PR_TRUE); + CERT_AddCertToListTail(*result_certs, intermediate); + } + // TODO(wtc): |key_spec| should be passed along with |provider|. + *result_private_key = reinterpret_cast<void*>(provider); + return SECSuccess; + } + LOG(WARNING) << "Client cert found without private key"; + } // Send no client certificate. return SECFailure; } @@ -1708,10 +1768,6 @@ SECStatus SSLClientSocketNSS::ClientAuthHandler( PCCERT_CHAIN_CONTEXT chain_context = NULL; - // TODO(wtc): close cert_store_ at shutdown. - if (!cert_store_) - cert_store_ = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, NULL, 0, NULL); - for (;;) { // Find a certificate chain. chain_context = CertFindChainInStore(my_cert_store, @@ -1733,18 +1789,42 @@ SECStatus SSLClientSocketNSS::ClientAuthHandler( // Copy it to our own certificate store, so that we can close the "MY" // certificate store before returning from this function. PCCERT_CONTEXT cert_context2; - BOOL ok = CertAddCertificateContextToStore(cert_store_, cert_context, + BOOL ok = CertAddCertificateContextToStore(X509Certificate::cert_store(), + cert_context, CERT_STORE_ADD_USE_EXISTING, &cert_context2); if (!ok) { NOTREACHED(); continue; } + + // Copy the rest of the chain to our own store as well. Copying the chain + // stops gracefully if an error is encountered, with the partial chain + // being used as the intermediates, rather than failing to consider the + // client certificate. + net::X509Certificate::OSCertHandles intermediates; + for (DWORD i = 1; i < chain_context->rgpChain[0]->cElement; i++) { + PCCERT_CONTEXT intermediate_copy; + ok = CertAddCertificateContextToStore(X509Certificate::cert_store(), + chain_context->rgpChain[0]->rgpElement[i]->pCertContext, + CERT_STORE_ADD_USE_EXISTING, &intermediate_copy); + if (!ok) { + NOTREACHED(); + break; + } + intermediates.push_back(intermediate_copy); + } + scoped_refptr<X509Certificate> cert = X509Certificate::CreateFromHandle( cert_context2, X509Certificate::SOURCE_LONE_CERT_IMPORT, - X509Certificate::OSCertHandles()); - X509Certificate::FreeOSCertHandle(cert_context2); + intermediates); that->client_certs_.push_back(cert); + + X509Certificate::FreeOSCertHandle(cert_context2); + for (net::X509Certificate::OSCertHandles::iterator it = + intermediates.begin(); it != intermediates.end(); ++it) { + net::X509Certificate::FreeOSCertHandle(*it); + } } BOOL ok = CertCloseStore(my_cert_store, CERT_CLOSE_STORE_CHECK_FLAG); @@ -1755,9 +1835,63 @@ SECStatus SSLClientSocketNSS::ClientAuthHandler( return SECWouldBlock; #elif defined(OS_MACOSX) if (that->ssl_config_.send_client_cert) { - // TODO(wtc): SSLClientSocketNSS can't do SSL client authentication using - // CDSA/CSSM yet (http://crbug.com/45369), so client_cert must be NULL. - DCHECK(!that->ssl_config_.client_cert); + if (that->ssl_config_.client_cert) { + OSStatus os_error = noErr; + SecIdentityRef identity = NULL; + SecKeyRef private_key = NULL; + CFArrayRef chain = + that->ssl_config_.client_cert->CreateClientCertificateChain(); + if (chain) { + identity = reinterpret_cast<SecIdentityRef>( + const_cast<void*>(CFArrayGetValueAtIndex(chain, 0))); + } + if (identity) + os_error = SecIdentityCopyPrivateKey(identity, &private_key); + + if (chain && identity && os_error == noErr) { + // TODO(rsleevi): Error checking for NSS allocation errors. + *result_certs = CERT_NewCertList(); + *result_private_key = reinterpret_cast<void*>(private_key); + + for (CFIndex i = 0; i < CFArrayGetCount(chain); ++i) { + CSSM_DATA cert_data; + SecCertificateRef cert_ref; + if (i == 0) { + cert_ref = that->ssl_config_.client_cert->os_cert_handle(); + } else { + cert_ref = reinterpret_cast<SecCertificateRef>( + const_cast<void*>(CFArrayGetValueAtIndex(chain, i))); + } + os_error = SecCertificateGetData(cert_ref, &cert_data); + if (os_error != noErr) + break; + + SECItem der_cert; + der_cert.type = siDERCertBuffer; + der_cert.data = cert_data.Data; + der_cert.len = cert_data.Length; + CERTCertificate* nss_cert = CERT_NewTempCertificate( + CERT_GetDefaultCertDB(), &der_cert, NULL, PR_FALSE, PR_TRUE); + CERT_AddCertToListTail(*result_certs, nss_cert); + } + } + if (os_error == noErr) { + CFRelease(chain); + return SECSuccess; + } + LOG(WARNING) << "Client cert found, but could not be used: " + << os_error; + if (*result_certs) { + CERT_DestroyCertList(*result_certs); + *result_certs = NULL; + } + if (*result_private_key) + *result_private_key = NULL; + if (private_key) + CFRelease(private_key); + if (chain) + CFRelease(chain); + } // Send no client certificate. return SECFailure; } @@ -1785,6 +1919,24 @@ SECStatus SSLClientSocketNSS::ClientAuthHandler( // handshake by returning ERR_SSL_CLIENT_AUTH_CERT_NEEDED. return SECWouldBlock; #else + return SECFailure; +#endif +} + +#else // NSS_PLATFORM_CLIENT_AUTH + +// static +// NSS calls this if a client certificate is needed. +// Based on Mozilla's NSS_GetClientAuthData. +SECStatus SSLClientSocketNSS::ClientAuthHandler( + void* arg, + PRFileDesc* socket, + CERTDistNames* ca_names, + CERTCertificate** result_certificate, + SECKEYPrivateKey** result_private_key) { + SSLClientSocketNSS* that = reinterpret_cast<SSLClientSocketNSS*>(arg); + + that->client_auth_cert_needed_ = !that->ssl_config_.send_client_cert; void* wincx = SSL_RevealPinArg(socket); // Second pass: a client certificate should have been selected. @@ -1838,8 +1990,8 @@ SECStatus SSLClientSocketNSS::ClientAuthHandler( // Tell NSS to suspend the client authentication. We will then abort the // handshake by returning ERR_SSL_CLIENT_AUTH_CERT_NEEDED. return SECWouldBlock; -#endif } +#endif // NSS_PLATFORM_CLIENT_AUTH // static // NSS calls this when handshake is completed. @@ -1852,22 +2004,32 @@ void SSLClientSocketNSS::HandshakeCallback(PRFileDesc* socket, that->handshake_callback_called_ = true; that->UpdateServerCert(); - - that->CheckSecureRenegotiation(); + that->UpdateConnectionStatus(); } int SSLClientSocketNSS::DoSnapStartLoadInfo() { EnterFunction(""); int rv = ssl_host_info_->WaitForDataReady(&handshake_io_callback_); + GotoState(STATE_HANDSHAKE); if (rv == OK) { - if (LoadSnapStartInfo()) { - pseudo_connected_ = true; - GotoState(STATE_SNAP_START_WAIT_FOR_WRITE); - if (user_connect_callback_) - DoConnectCallback(OK); - } else { - GotoState(STATE_HANDSHAKE); + if (ssl_host_info_->WaitForCertVerification(NULL) == OK) { + if (LoadSnapStartInfo()) { + pseudo_connected_ = true; + GotoState(STATE_SNAP_START_WAIT_FOR_WRITE); + if (user_connect_callback_) + DoConnectCallback(OK); + } + } else if (!ssl_host_info_->state().server_hello.empty()) { + // A non-empty ServerHello suggests that we would have tried a Snap Start + // connection. + base::TimeTicks now = base::TimeTicks::Now(); + const base::TimeDelta duration = + now - ssl_host_info_->verification_start_time(); + UMA_HISTOGRAM_TIMES("Net.SSLSnapStartNeededVerificationInMs", duration); + VLOG(1) << "Cannot snap start because verification isn't ready. " + << "Wanted verification after " + << duration.InMilliseconds() << "ms"; } } else { DCHECK_EQ(ERR_IO_PENDING, rv); @@ -1909,7 +2071,6 @@ int SSLClientSocketNSS::DoSnapStartWaitForWrite() { nss_fd_, reinterpret_cast<const unsigned char*>(user_write_buf_->data()), user_write_buf_len_); DCHECK_EQ(SECSuccess, rv); - user_write_buf_ = NULL; GotoState(STATE_HANDSHAKE); LeaveFunction(""); @@ -1924,7 +2085,7 @@ int SSLClientSocketNSS::DoHandshake() { if (client_auth_cert_needed_) { net_error = ERR_SSL_CLIENT_AUTH_CERT_NEEDED; net_log_.AddEvent(NetLog::TYPE_SSL_HANDSHAKE_ERROR, - new SSLErrorParams(net_error, 0)); + make_scoped_refptr(new SSLErrorParams(net_error, 0))); // If the handshake already succeeded (because the server requests but // doesn't require a client cert), we need to invalidate the SSL session // so that we won't try to resume the non-client-authenticated session in @@ -1937,9 +2098,27 @@ int SSLClientSocketNSS::DoHandshake() { if (handshake_callback_called_) { if (eset_mitm_detected_) { net_error = ERR_ESET_ANTI_VIRUS_SSL_INTERCEPTION; - } else if (netnanny_mitm_detected_) { - net_error = ERR_NETNANNY_SSL_INTERCEPTION; } else { + // We need to see if the predicted certificate chain (in + // |ssl_host_info_->state().certs) matches the actual certificate chain + // before we call SaveSnapStartInfo, as that will update + // |ssl_host_info_|. + if (ssl_host_info_.get() && !ssl_host_info_->state().certs.empty()) { + PeerCertificateChain certs(nss_fd_); + const SSLHostInfo::State& state = ssl_host_info_->state(); + predicted_cert_chain_correct_ = certs.size() == state.certs.size(); + if (predicted_cert_chain_correct_) { + for (unsigned i = 0; i < certs.size(); i++) { + if (certs[i]->derCert.len != state.certs[i].size() || + memcmp(certs[i]->derCert.data, state.certs[i].data(), + certs[i]->derCert.len) != 0) { + predicted_cert_chain_correct_ = false; + break; + } + } + } + } + SaveSnapStartInfo(); // SSL handshake is completed. It's possible that we mispredicted the // NPN agreed protocol. In this case, we've just sent a request in the @@ -1961,7 +2140,7 @@ int SSLClientSocketNSS::DoHandshake() { rv = SECFailure; net_error = ERR_SSL_PROTOCOL_ERROR; net_log_.AddEvent(NetLog::TYPE_SSL_HANDSHAKE_ERROR, - new SSLErrorParams(net_error, 0)); + make_scoped_refptr(new SSLErrorParams(net_error, 0))); } } else { PRErrorCode prerr = PR_GetError(); @@ -1973,8 +2152,9 @@ int SSLClientSocketNSS::DoHandshake() { } else { LOG(ERROR) << "handshake failed; NSS error code " << prerr << ", net_error " << net_error; - net_log_.AddEvent(NetLog::TYPE_SSL_HANDSHAKE_ERROR, - new SSLErrorParams(net_error, prerr)); + net_log_.AddEvent( + NetLog::TYPE_SSL_HANDSHAKE_ERROR, + make_scoped_refptr(new SSLErrorParams(net_error, prerr))); } } @@ -2135,10 +2315,19 @@ static DNSValidationResult CheckDNSSECChain( } int SSLClientSocketNSS::DoVerifyDNSSEC(int result) { +#if !defined(USE_OPENSSL) + if (ssl_config_.dns_cert_provenance_checking_enabled && dnsrr_resolver_) { + PeerCertificateChain certs(nss_fd_); + DoAsyncDNSCertProvenanceVerification( + hostname_, dnsrr_resolver_, certs.AsStringPieceVector()); + } +#endif + if (ssl_config_.dnssec_enabled) { DNSValidationResult r = CheckDNSSECChain(hostname_, server_cert_nss_); if (r == DNSVR_SUCCESS) { - server_cert_verify_result_.cert_status |= CERT_STATUS_IS_DNSSEC; + local_server_cert_verify_result_.cert_status |= CERT_STATUS_IS_DNSSEC; + server_cert_verify_result_ = &local_server_cert_verify_result_; GotoState(STATE_VERIFY_CERT_COMPLETE); return OK; } @@ -2183,13 +2372,15 @@ int SSLClientSocketNSS::DoVerifyDNSSECComplete(int result) { switch (r) { case DNSVR_FAILURE: GotoState(STATE_VERIFY_CERT_COMPLETE); - server_cert_verify_result_.cert_status |= CERT_STATUS_NOT_IN_DNS; + local_server_cert_verify_result_.cert_status |= CERT_STATUS_NOT_IN_DNS; + server_cert_verify_result_ = &local_server_cert_verify_result_; return ERR_CERT_NOT_IN_DNS; case DNSVR_CONTINUE: GotoState(STATE_VERIFY_CERT); break; case DNSVR_SUCCESS: - server_cert_verify_result_.cert_status |= CERT_STATUS_IS_DNSSEC; + local_server_cert_verify_result_.cert_status |= CERT_STATUS_IS_DNSSEC; + server_cert_verify_result_ = &local_server_cert_verify_result_; GotoState(STATE_VERIFY_CERT_COMPLETE); break; default: @@ -2202,16 +2393,35 @@ int SSLClientSocketNSS::DoVerifyDNSSECComplete(int result) { int SSLClientSocketNSS::DoVerifyCert(int result) { DCHECK(server_cert_); + GotoState(STATE_VERIFY_CERT_COMPLETE); - int flags = 0; + if (ssl_host_info_.get() && !ssl_host_info_->state().certs.empty() && + predicted_cert_chain_correct_) { + // If the SSLHostInfo had a prediction for the certificate chain of this + // server then it will have optimistically started a verification of that + // chain. So, if the prediction was correct, we should wait for that + // verification to finish rather than start our own. + net_log_.AddEvent(NetLog::TYPE_SSL_VERIFICATION_MERGED, NULL); + UMA_HISTOGRAM_ENUMERATION("Net.SSLVerificationMerged", 1 /* true */, 2); + base::TimeTicks now = base::TimeTicks::Now(); + UMA_HISTOGRAM_TIMES("Net.SSLVerificationMergedMsSaved", + now - ssl_host_info_->verification_start_time()); + server_cert_verify_result_ = &ssl_host_info_->cert_verify_result(); + return ssl_host_info_->WaitForCertVerification(&handshake_io_callback_); + } else { + UMA_HISTOGRAM_ENUMERATION("Net.SSLVerificationMerged", 0 /* false */, 2); + } + + int flags = 0; if (ssl_config_.rev_checking_enabled) flags |= X509Certificate::VERIFY_REV_CHECKING_ENABLED; if (ssl_config_.verify_ev_cert) flags |= X509Certificate::VERIFY_EV_CERT; verifier_.reset(new CertVerifier); + server_cert_verify_result_ = &local_server_cert_verify_result_; return verifier_->Verify(server_cert_, hostname_, flags, - &server_cert_verify_result_, + &local_server_cert_verify_result_, &handshake_io_callback_); } @@ -2220,45 +2430,15 @@ int SSLClientSocketNSS::DoVerifyCert(int result) { int SSLClientSocketNSS::DoVerifyCertComplete(int result) { verifier_.reset(); - // Using Snap Start disables certificate verification for now. - if (SSLConfigService::snap_start_enabled()) - result = OK; - - if (result == OK) { - // Remember the intermediate CA certs if the server sends them to us. - // - // We used to remember the intermediate CA certs in the NSS database - // persistently. However, NSS opens a connection to the SQLite database - // during NSS initialization and doesn't close the connection until NSS - // shuts down. If the file system where the database resides is gone, - // the database connection goes bad. What's worse, the connection won't - // recover when the file system comes back. Until this NSS or SQLite bug - // is fixed, we need to avoid using the NSS database for non-essential - // purposes. See https://bugzilla.mozilla.org/show_bug.cgi?id=508081 and - // http://crbug.com/15630 for more info. - CERTCertList* cert_list = CERT_GetCertChainFromCert( - server_cert_nss_, PR_Now(), certUsageSSLCA); - if (cert_list) { - for (CERTCertListNode* node = CERT_LIST_HEAD(cert_list); - !CERT_LIST_END(node, cert_list); - node = CERT_LIST_NEXT(node)) { - if (node->cert->slot || node->cert->isRoot || node->cert->isperm || - node->cert == server_cert_nss_) { - // Some certs we don't want to remember are: - // - found on a token. - // - the root cert. - // - already stored in perm db. - // - the server cert itself. - continue; - } - - // We have found a CA cert that we want to remember. - // TODO(wtc): Remember the intermediate CA certs in a std::set - // temporarily (http://crbug.com/15630). - } - CERT_DestroyCertList(cert_list); - } - } + // We used to remember the intermediate CA certs in the NSS database + // persistently. However, NSS opens a connection to the SQLite database + // during NSS initialization and doesn't close the connection until NSS + // shuts down. If the file system where the database resides is gone, + // the database connection goes bad. What's worse, the connection won't + // recover when the file system comes back. Until this NSS or SQLite bug + // is fixed, we need to avoid using the NSS database for non-essential + // purposes. See https://bugzilla.mozilla.org/show_bug.cgi?id=508081 and + // http://crbug.com/15630 for more info. // If we have been explicitly told to accept this certificate, override the // result of verifier_.Verify. @@ -2272,16 +2452,45 @@ int SSLClientSocketNSS::DoVerifyCertComplete(int result) { result = OK; } + if (result == OK) + LogConnectionTypeMetrics(); + completed_handshake_ = true; // TODO(ukai): we may not need this call because it is now harmless to have a // session with a bad cert. InvalidateSessionIfBadCertificate(); - // Likewise, if we merged a Write call into the handshake we need to make the + // If we merged a Write call into the handshake we need to make the // callback now. if (user_write_callback_) { corked_ = false; - DoWriteCallback(user_write_buf_len_); + if (result != OK) { + DoWriteCallback(result); + } else { + SSLSnapStartResult snap_start_type; + SECStatus rv = SSL_GetSnapStartResult(nss_fd_, &snap_start_type); + DCHECK_EQ(rv, SECSuccess); + DCHECK_NE(snap_start_type, SSL_SNAP_START_NONE); + if (snap_start_type == SSL_SNAP_START_RECOVERY || + snap_start_type == SSL_SNAP_START_RESUME_RECOVERY) { + // If we mispredicted the server's handshake then Snap Start will have + // triggered a recovery mode. The misprediction could have been caused + // by the server having a different certificate so the application data + // wasn't resent. Now that we have verified the certificate, we need to + // resend the application data. + int bytes_written = DoPayloadWrite(); + if (bytes_written != ERR_IO_PENDING) + DoWriteCallback(bytes_written); + } else { + DoWriteCallback(user_write_buf_len_); + } + } + } + + if (user_read_callback_) { + int rv = DoReadLoop(OK); + if (rv != ERR_IO_PENDING) + DoReadCallback(rv); } // Exit DoHandshakeLoop and return the result to the caller to Connect. @@ -2300,7 +2509,7 @@ int SSLClientSocketNSS::DoPayloadRead() { LeaveFunction(""); rv = ERR_SSL_CLIENT_AUTH_CERT_NEEDED; net_log_.AddEvent(NetLog::TYPE_SSL_READ_ERROR, - new SSLErrorParams(rv, 0)); + make_scoped_refptr(new SSLErrorParams(rv, 0))); return rv; } if (rv >= 0) { @@ -2315,7 +2524,8 @@ int SSLClientSocketNSS::DoPayloadRead() { } LeaveFunction(""); rv = MapNSPRError(prerr); - net_log_.AddEvent(NetLog::TYPE_SSL_READ_ERROR, new SSLErrorParams(rv, prerr)); + net_log_.AddEvent(NetLog::TYPE_SSL_READ_ERROR, + make_scoped_refptr(new SSLErrorParams(rv, prerr))); return rv; } @@ -2336,8 +2546,40 @@ int SSLClientSocketNSS::DoPayloadWrite() { LeaveFunction(""); rv = MapNSPRError(prerr); net_log_.AddEvent(NetLog::TYPE_SSL_WRITE_ERROR, - new SSLErrorParams(rv, prerr)); + make_scoped_refptr(new SSLErrorParams(rv, prerr))); return rv; } +void SSLClientSocketNSS::LogConnectionTypeMetrics() const { + UpdateConnectionTypeHistograms(CONNECTION_SSL); + if (server_cert_verify_result_->has_md5) + UpdateConnectionTypeHistograms(CONNECTION_SSL_MD5); + if (server_cert_verify_result_->has_md2) + UpdateConnectionTypeHistograms(CONNECTION_SSL_MD2); + if (server_cert_verify_result_->has_md4) + UpdateConnectionTypeHistograms(CONNECTION_SSL_MD4); + if (server_cert_verify_result_->has_md5_ca) + UpdateConnectionTypeHistograms(CONNECTION_SSL_MD5_CA); + if (server_cert_verify_result_->has_md2_ca) + UpdateConnectionTypeHistograms(CONNECTION_SSL_MD2_CA); + int ssl_version = SSLConnectionStatusToVersion(ssl_connection_status_); + switch (ssl_version) { + case SSL_CONNECTION_VERSION_SSL2: + UpdateConnectionTypeHistograms(CONNECTION_SSL_SSL2); + break; + case SSL_CONNECTION_VERSION_SSL3: + UpdateConnectionTypeHistograms(CONNECTION_SSL_SSL3); + break; + case SSL_CONNECTION_VERSION_TLS1: + UpdateConnectionTypeHistograms(CONNECTION_SSL_TLS1); + break; + case SSL_CONNECTION_VERSION_TLS1_1: + UpdateConnectionTypeHistograms(CONNECTION_SSL_TLS1_1); + break; + case SSL_CONNECTION_VERSION_TLS1_2: + UpdateConnectionTypeHistograms(CONNECTION_SSL_TLS1_2); + break; + }; +} + } // namespace net |
