Index: net/socket/ssl_client_socket_nss.cc |
diff --git a/net/socket/ssl_client_socket_nss.cc b/net/socket/ssl_client_socket_nss.cc |
index d006d58ad8296a464ccc49f7154d43cf4bbfde1a..a71af2caad33b336a4ea688091cb8ebfd4b0a499 100644 |
--- a/net/socket/ssl_client_socket_nss.cc |
+++ b/net/socket/ssl_client_socket_nss.cc |
@@ -279,6 +279,162 @@ class PeerCertificateChain { |
CERTCertificate** certs_; |
}; |
+void DestroyCertificates(CERTCertificate** certs, unsigned len) { |
+ for (unsigned i = 0; i < len; i++) |
+ CERT_DestroyCertificate(certs[i]); |
+} |
+ |
+// DNSValidationResult enumerates the possible outcomes from processing a |
+// set of DNS records. |
+enum DNSValidationResult { |
+ DNSVR_SUCCESS, // the cert is immediately acceptable. |
+ DNSVR_FAILURE, // the cert is unconditionally rejected. |
+ DNSVR_CONTINUE, // perform CA validation as usual. |
+}; |
+ |
+// VerifyTXTRecords processes the RRDATA for a number of DNS TXT records and |
+// checks them against the given certificate. |
+// dnssec: if true then the TXT records are DNSSEC validated. In this case, |
+// DNSVR_SUCCESS may be returned. |
+// server_cert_nss: the certificate to validate |
+// rrdatas: the TXT records for the current domain. |
+DNSValidationResult VerifyTXTRecords( |
+ bool dnssec, |
+ CERTCertificate* server_cert_nss, |
+ const std::vector<base::StringPiece>& rrdatas) { |
+ bool found_well_formed_record = false; |
+ bool matched_record = false; |
+ |
+ for (std::vector<base::StringPiece>::const_iterator |
+ i = rrdatas.begin(); i != rrdatas.end(); ++i) { |
+ std::map<std::string, std::string> m( |
+ DNSSECChainVerifier::ParseTLSTXTRecord(*i)); |
+ if (m.empty()) |
+ continue; |
+ |
+ std::map<std::string, std::string>::const_iterator j; |
+ j = m.find("v"); |
+ if (j == m.end() || j->second != "tls1") |
+ continue; |
+ |
+ j = m.find("ha"); |
+ |
+ HASH_HashType hash_algorithm; |
+ unsigned hash_length; |
+ if (j == m.end() || j->second == "sha1") { |
+ hash_algorithm = HASH_AlgSHA1; |
+ hash_length = SHA1_LENGTH; |
+ } else if (j->second == "sha256") { |
+ hash_algorithm = HASH_AlgSHA256; |
+ hash_length = SHA256_LENGTH; |
+ } else { |
+ continue; |
+ } |
+ |
+ j = m.find("h"); |
+ if (j == m.end()) |
+ continue; |
+ |
+ std::vector<uint8> given_hash; |
+ if (!base::HexStringToBytes(j->second, &given_hash)) |
+ continue; |
+ |
+ if (given_hash.size() != hash_length) |
+ continue; |
+ |
+ uint8 calculated_hash[SHA256_LENGTH]; // SHA256 is the largest. |
+ SECStatus rv; |
+ |
+ j = m.find("hr"); |
+ if (j == m.end() || j->second == "pubkey") { |
+ rv = HASH_HashBuf(hash_algorithm, calculated_hash, |
+ server_cert_nss->derPublicKey.data, |
+ server_cert_nss->derPublicKey.len); |
+ } else if (j->second == "cert") { |
+ rv = HASH_HashBuf(hash_algorithm, calculated_hash, |
+ server_cert_nss->derCert.data, |
+ server_cert_nss->derCert.len); |
+ } else { |
+ continue; |
+ } |
+ |
+ if (rv != SECSuccess) |
+ NOTREACHED(); |
+ |
+ found_well_formed_record = true; |
+ |
+ if (memcmp(calculated_hash, &given_hash[0], hash_length) == 0) { |
+ matched_record = true; |
+ if (dnssec) |
+ return DNSVR_SUCCESS; |
+ } |
+ } |
+ |
+ if (found_well_formed_record && !matched_record) |
+ return DNSVR_FAILURE; |
+ |
+ return DNSVR_CONTINUE; |
+} |
+ |
+// CheckDNSSECChain tries to validate a DNSSEC chain embedded in |
+// |server_cert_nss_|. It returns true iff a chain is found that proves the |
+// value of a TXT record that contains a valid public key fingerprint. |
+DNSValidationResult CheckDNSSECChain( |
+ const std::string& hostname, |
+ CERTCertificate* server_cert_nss) { |
+ if (!server_cert_nss) |
+ return DNSVR_CONTINUE; |
+ |
+ // CERT_FindCertExtensionByOID isn't exported so we have to install an OID, |
+ // get a tag for it and find the extension by using that tag. |
+ static SECOidTag dnssec_chain_tag; |
+ static bool dnssec_chain_tag_valid; |
+ if (!dnssec_chain_tag_valid) { |
+ // It's harmless if multiple threads enter this block concurrently. |
+ static const uint8 kDNSSECChainOID[] = |
+ // 1.3.6.1.4.1.11129.2.1.4 |
+ // (iso.org.dod.internet.private.enterprises.google.googleSecurity. |
+ // certificateExtensions.dnssecEmbeddedChain) |
+ {0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x01, 0x04}; |
+ SECOidData oid_data; |
+ memset(&oid_data, 0, sizeof(oid_data)); |
+ oid_data.oid.data = const_cast<uint8*>(kDNSSECChainOID); |
+ oid_data.oid.len = sizeof(kDNSSECChainOID); |
+ oid_data.desc = "DNSSEC chain"; |
+ oid_data.supportedExtension = SUPPORTED_CERT_EXTENSION; |
+ dnssec_chain_tag = SECOID_AddEntry(&oid_data); |
+ DCHECK_NE(SEC_OID_UNKNOWN, dnssec_chain_tag); |
+ dnssec_chain_tag_valid = true; |
+ } |
+ |
+ SECItem dnssec_embedded_chain; |
+ SECStatus rv = CERT_FindCertExtension(server_cert_nss, |
+ dnssec_chain_tag, &dnssec_embedded_chain); |
+ if (rv != SECSuccess) |
+ return DNSVR_CONTINUE; |
+ |
+ base::StringPiece chain( |
+ reinterpret_cast<char*>(dnssec_embedded_chain.data), |
+ dnssec_embedded_chain.len); |
+ std::string dns_hostname; |
+ if (!DNSDomainFromDot(hostname, &dns_hostname)) |
+ return DNSVR_CONTINUE; |
+ DNSSECChainVerifier verifier(dns_hostname, chain); |
+ DNSSECChainVerifier::Error err = verifier.Verify(); |
+ if (err != DNSSECChainVerifier::OK) { |
+ LOG(ERROR) << "DNSSEC chain verification failed: " << err; |
+ return DNSVR_CONTINUE; |
+ } |
+ |
+ if (verifier.rrtype() != kDNS_TXT) |
+ return DNSVR_CONTINUE; |
+ |
+ DNSValidationResult r = VerifyTXTRecords( |
+ true /* DNSSEC verified */, server_cert_nss, verifier.rrdatas()); |
+ SECITEM_FreeItem(&dnssec_embedded_chain, PR_FALSE); |
+ return r; |
+} |
+ |
} // namespace |
SSLClientSocketNSS::SSLClientSocketNSS(ClientSocketHandle* transport_socket, |
@@ -333,156 +489,94 @@ SSLClientSocketNSS::~SSLClientSocketNSS() { |
LeaveFunction(""); |
} |
-int SSLClientSocketNSS::Init() { |
- EnterFunction(""); |
- // Initialize the NSS SSL library in a threadsafe way. This also |
- // initializes the NSS base library. |
- EnsureNSSSSLInit(); |
- if (!NSS_IsInitialized()) |
- return ERR_UNEXPECTED; |
-#if !defined(OS_MACOSX) && !defined(OS_WIN) |
- // We must call EnsureOCSPInit() here, on the IO thread, to get the IO loop |
- // by MessageLoopForIO::current(). |
- // X509Certificate::Verify() runs on a worker thread of CertVerifier. |
- EnsureOCSPInit(); |
-#endif |
- |
- LeaveFunction(""); |
- return OK; |
+// static |
+void SSLClientSocketNSS::ClearSessionCache() { |
+ SSL_ClearSessionCache(); |
} |
-// SaveSnapStartInfo extracts the information needed to perform a Snap Start |
-// with this server in the future (if any) and tells |ssl_host_info_| to |
-// preserve it. |
-void SSLClientSocketNSS::SaveSnapStartInfo() { |
- if (!ssl_host_info_.get()) |
- return; |
- |
- // If the SSLHostInfo hasn't managed to load from disk yet then we can't save |
- // anything. |
- if (ssl_host_info_->WaitForDataReady(NULL) != OK) |
- return; |
+void SSLClientSocketNSS::GetSSLInfo(SSLInfo* ssl_info) { |
+ EnterFunction(""); |
+ ssl_info->Reset(); |
- SECStatus rv; |
- SSLSnapStartResult snap_start_type; |
- rv = SSL_GetSnapStartResult(nss_fd_, &snap_start_type); |
- if (rv != SECSuccess) { |
- NOTREACHED(); |
- return; |
- } |
- net_log_.AddEvent(NetLog::TYPE_SSL_SNAP_START, |
- new NetLogIntegerParameter("type", snap_start_type)); |
- if (snap_start_type == SSL_SNAP_START_FULL || |
- snap_start_type == SSL_SNAP_START_RESUME) { |
- // If we did a successful Snap Start then our information was correct and |
- // there's no point saving it again. |
+ if (!server_cert_) { |
+ LOG(DFATAL) << "!server_cert_"; |
return; |
} |
- const unsigned char* hello_data; |
- unsigned hello_data_len; |
- rv = SSL_GetPredictedServerHelloData(nss_fd_, &hello_data, &hello_data_len); |
- if (rv != SECSuccess) { |
- NOTREACHED(); |
- return; |
- } |
- if (hello_data_len > std::numeric_limits<uint16>::max()) |
- return; |
- SSLHostInfo::State* state = ssl_host_info_->mutable_state(); |
+ 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_; |
- if (hello_data_len > 0) { |
- 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); |
+ 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 { |
- state->server_hello.clear(); |
- state->npn_valid = false; |
- } |
- |
- 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; |
- |
- state->certs.push_back(std::string( |
- reinterpret_cast<char*>(certs[i]->derCert.data), |
- certs[i]->derCert.len)); |
+ ssl_info->security_bits = -1; |
+ LOG(DFATAL) << "SSL_GetCipherSuiteInfo returned " << PR_GetError() |
+ << " for cipherSuite " << cipher_suite; |
} |
- |
- ssl_host_info_->Persist(); |
+ LeaveFunction(""); |
} |
-static void DestroyCertificates(CERTCertificate** certs, unsigned len) { |
- for (unsigned i = 0; i < len; i++) |
- CERT_DestroyCertificate(certs[i]); |
+void SSLClientSocketNSS::GetSSLCertRequestInfo( |
+ SSLCertRequestInfo* cert_request_info) { |
+ EnterFunction(""); |
+ // TODO(rch): switch SSLCertRequestInfo.host_and_port to a HostPortPair |
+ cert_request_info->host_and_port = host_and_port_.ToString(); |
+ cert_request_info->client_certs = client_certs_; |
+ LeaveFunction(cert_request_info->client_certs.size()); |
} |
-// LoadSnapStartInfo parses |info|, which contains data previously serialised |
-// by |SaveSnapStartInfo|, and sets the predicted certificates and ServerHello |
-// data on the NSS socket. Returns true on success. If this function returns |
-// false, the caller should try a normal TLS handshake. |
-bool SSLClientSocketNSS::LoadSnapStartInfo() { |
- const SSLHostInfo::State& state(ssl_host_info_->state()); |
- |
- if (state.server_hello.empty() || |
- state.certs.empty() || |
- !state.npn_valid) { |
- return false; |
+SSLClientSocket::NextProtoStatus |
+SSLClientSocketNSS::GetNextProto(std::string* proto) { |
+#if defined(SSL_NEXT_PROTO_NEGOTIATED) |
+ if (!handshake_callback_called_) { |
+ DCHECK(pseudo_connected_); |
+ predicted_npn_proto_used_ = true; |
+ *proto = predicted_npn_proto_; |
+ return predicted_npn_status_; |
} |
- SECStatus rv; |
- rv = SSL_SetPredictedServerHelloData( |
- nss_fd_, |
- reinterpret_cast<const uint8*>(state.server_hello.data()), |
- state.server_hello.size()); |
- DCHECK_EQ(SECSuccess, rv); |
- |
- const std::vector<std::string>& certs_in = state.certs; |
- scoped_array<CERTCertificate*> certs(new CERTCertificate*[certs_in.size()]); |
- for (size_t i = 0; i < certs_in.size(); i++) { |
- SECItem derCert; |
- derCert.data = |
- const_cast<uint8*>(reinterpret_cast<const uint8*>(certs_in[i].data())); |
- derCert.len = certs_in[i].size(); |
- certs[i] = CERT_NewTempCertificate( |
- CERT_GetDefaultCertDB(), &derCert, NULL /* no nickname given */, |
- PR_FALSE /* not permanent */, PR_TRUE /* copy DER data */); |
- if (!certs[i]) { |
- DestroyCertificates(&certs[0], i); |
- NOTREACHED(); |
- return false; |
- } |
+ unsigned char buf[255]; |
+ int state; |
+ unsigned len; |
+ SECStatus rv = SSL_GetNextProto(nss_fd_, &state, buf, &len, sizeof(buf)); |
+ if (rv != SECSuccess) { |
+ NOTREACHED() << "Error return from SSL_GetNextProto: " << rv; |
+ proto->clear(); |
+ return kNextProtoUnsupported; |
} |
- |
- rv = SSL_SetPredictedPeerCertificates(nss_fd_, certs.get(), certs_in.size()); |
- DestroyCertificates(&certs[0], certs_in.size()); |
- DCHECK_EQ(SECSuccess, rv); |
- |
- if (state.npn_valid) { |
- predicted_npn_status_ = state.npn_status; |
- predicted_npn_proto_ = state.npn_protocol; |
+ // We don't check for truncation because sizeof(buf) is large enough to hold |
+ // the maximum protocol size. |
+ switch (state) { |
+ case SSL_NEXT_PROTO_NO_SUPPORT: |
+ proto->clear(); |
+ return kNextProtoUnsupported; |
+ case SSL_NEXT_PROTO_NEGOTIATED: |
+ *proto = std::string(reinterpret_cast<char*>(buf), len); |
+ return kNextProtoNegotiated; |
+ case SSL_NEXT_PROTO_NO_OVERLAP: |
+ *proto = std::string(reinterpret_cast<char*>(buf), len); |
+ return kNextProtoNoOverlap; |
+ default: |
+ NOTREACHED() << "Unknown status from SSL_GetNextProto: " << state; |
+ proto->clear(); |
+ return kNextProtoUnsupported; |
} |
- |
- return true; |
-} |
- |
-bool SSLClientSocketNSS::IsNPNProtocolMispredicted() { |
- DCHECK(handshake_callback_called_); |
- if (!predicted_npn_proto_used_) |
- return false; |
- std::string npn_proto; |
- GetNextProto(&npn_proto); |
- return predicted_npn_proto_ != npn_proto; |
+#else |
+ // No NPN support in the libssl that we are building with. |
+ proto->clear(); |
+ return kNextProtoUnsupported; |
+#endif |
} |
-void SSLClientSocketNSS::UncorkAfterTimeout() { |
- corked_ = false; |
- int nsent; |
- do { |
- nsent = BufferSend(); |
- } while (nsent > 0); |
+void SSLClientSocketNSS::UseDNSSEC(DNSSECProvider* provider) { |
+ dnssec_provider_ = provider; |
} |
int SSLClientSocketNSS::Connect(CompletionCallback* callback) { |
@@ -542,85 +636,307 @@ int SSLClientSocketNSS::Connect(CompletionCallback* callback) { |
return rv > OK ? OK : rv; |
} |
-int SSLClientSocketNSS::InitializeSSLOptions() { |
- // Transport connected, now hook it up to nss |
- // TODO(port): specify rx and tx buffer sizes separately |
- nss_fd_ = memio_CreateIOLayer(kRecvBufferSize); |
- if (nss_fd_ == NULL) { |
- return ERR_OUT_OF_MEMORY; // TODO(port): map NSPR error code. |
+void SSLClientSocketNSS::Disconnect() { |
+ EnterFunction(""); |
+ |
+ // TODO(wtc): Send SSL close_notify alert. |
+ if (nss_fd_ != NULL) { |
+ PR_Close(nss_fd_); |
+ nss_fd_ = NULL; |
} |
- // Grab pointer to buffers |
- nss_bufs_ = memio_GetSecret(nss_fd_); |
+ // Shut down anything that may call us back (through buffer_send_callback_, |
+ // buffer_recv_callback, or handshake_io_callback_). |
+ verifier_.reset(); |
+ transport_->socket()->Disconnect(); |
- /* Create SSL state machine */ |
- /* Push SSL onto our fake I/O socket */ |
- nss_fd_ = SSL_ImportFD(NULL, nss_fd_); |
- if (nss_fd_ == NULL) { |
- LogFailedNSSFunction(net_log_, "SSL_ImportFD", ""); |
- return ERR_OUT_OF_MEMORY; // TODO(port): map NSPR/NSS error code. |
+ // Reset object state |
+ transport_send_busy_ = false; |
+ transport_recv_busy_ = false; |
+ user_connect_callback_ = NULL; |
+ user_read_callback_ = NULL; |
+ user_write_callback_ = NULL; |
+ user_read_buf_ = NULL; |
+ user_read_buf_len_ = 0; |
+ user_write_buf_ = NULL; |
+ user_write_buf_len_ = 0; |
+ server_cert_ = NULL; |
+ if (server_cert_nss_) { |
+ CERT_DestroyCertificate(server_cert_nss_); |
+ server_cert_nss_ = NULL; |
} |
- // TODO(port): set more ssl options! Check errors! |
+ 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; |
+ start_cert_verification_time_ = base::TimeTicks(); |
+ predicted_cert_chain_correct_ = false; |
+ peername_initialized_ = false; |
+ nss_bufs_ = NULL; |
+ client_certs_.clear(); |
+ client_auth_cert_needed_ = false; |
- int rv; |
+ LeaveFunction(""); |
+} |
- rv = SSL_OptionSet(nss_fd_, SSL_SECURITY, PR_TRUE); |
- if (rv != SECSuccess) { |
- LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_SECURITY"); |
- return ERR_UNEXPECTED; |
- } |
+bool SSLClientSocketNSS::IsConnected() const { |
+ // Ideally, we should also check if we have received the close_notify alert |
+ // message from the server, and return false in that case. We're not doing |
+ // that, so this function may return a false positive. Since the upper |
+ // layer (HttpNetworkTransaction) needs to handle a persistent connection |
+ // closed by the server when we send a request anyway, a false positive in |
+ // exchange for simpler code is a good trade-off. |
+ EnterFunction(""); |
+ bool ret = (pseudo_connected_ || completed_handshake_) && |
+ transport_->socket()->IsConnected(); |
+ LeaveFunction(""); |
+ return ret; |
+} |
- rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_SSL2, PR_FALSE); |
- if (rv != SECSuccess) { |
- LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_ENABLE_SSL2"); |
- return ERR_UNEXPECTED; |
- } |
+bool SSLClientSocketNSS::IsConnectedAndIdle() const { |
+ // Unlike IsConnected, this method doesn't return a false positive. |
+ // |
+ // Strictly speaking, we should check if we have received the close_notify |
+ // alert message from the server, and return false in that case. Although |
+ // the close_notify alert message means EOF in the SSL layer, it is just |
+ // bytes to the transport layer below, so |
+ // transport_->socket()->IsConnectedAndIdle() returns the desired false |
+ // when we receive close_notify. |
+ EnterFunction(""); |
+ bool ret = (pseudo_connected_ || completed_handshake_) && |
+ transport_->socket()->IsConnectedAndIdle(); |
+ LeaveFunction(""); |
+ return ret; |
+} |
- // Don't do V2 compatible hellos because they don't support TLS extensions. |
- rv = SSL_OptionSet(nss_fd_, SSL_V2_COMPATIBLE_HELLO, PR_FALSE); |
- if (rv != SECSuccess) { |
- LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_V2_COMPATIBLE_HELLO"); |
- return ERR_UNEXPECTED; |
- } |
+int SSLClientSocketNSS::GetPeerAddress(AddressList* address) const { |
+ return transport_->socket()->GetPeerAddress(address); |
+} |
- rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_SSL3, ssl_config_.ssl3_enabled); |
- if (rv != SECSuccess) { |
- LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_ENABLE_SSL3"); |
- return ERR_UNEXPECTED; |
+const BoundNetLog& SSLClientSocketNSS::NetLog() const { |
+ return net_log_; |
+} |
+ |
+void SSLClientSocketNSS::SetSubresourceSpeculation() { |
+ if (transport_.get() && transport_->socket()) { |
+ transport_->socket()->SetSubresourceSpeculation(); |
+ } else { |
+ NOTREACHED(); |
} |
+} |
- rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_TLS, ssl_config_.tls1_enabled); |
- if (rv != SECSuccess) { |
- LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_ENABLE_TLS"); |
- return ERR_UNEXPECTED; |
+void SSLClientSocketNSS::SetOmniboxSpeculation() { |
+ if (transport_.get() && transport_->socket()) { |
+ transport_->socket()->SetOmniboxSpeculation(); |
+ } else { |
+ NOTREACHED(); |
} |
+} |
- for (std::vector<uint16>::const_iterator it = |
- ssl_config_.disabled_cipher_suites.begin(); |
- it != ssl_config_.disabled_cipher_suites.end(); ++it) { |
- // This will fail if the specified cipher is not implemented by NSS, but |
- // the failure is harmless. |
- SSL_CipherPrefSet(nss_fd_, *it, PR_FALSE); |
+bool SSLClientSocketNSS::WasEverUsed() const { |
+ if (transport_.get() && transport_->socket()) { |
+ return transport_->socket()->WasEverUsed(); |
} |
+ NOTREACHED(); |
+ return false; |
+} |
-#ifdef SSL_ENABLE_SESSION_TICKETS |
- // Support RFC 5077 |
- rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_SESSION_TICKETS, PR_TRUE); |
- if (rv != SECSuccess) { |
- LogFailedNSSFunction( |
- net_log_, "SSL_OptionSet", "SSL_ENABLE_SESSION_TICKETS"); |
+bool SSLClientSocketNSS::UsingTCPFastOpen() const { |
+ if (transport_.get() && transport_->socket()) { |
+ return transport_->socket()->UsingTCPFastOpen(); |
} |
-#else |
- #error "You need to install NSS-3.12 or later to build chromium" |
-#endif |
+ NOTREACHED(); |
+ return false; |
+} |
-#ifdef SSL_ENABLE_DEFLATE |
- // Some web servers have been found to break if TLS is used *or* if DEFLATE |
- // is advertised. Thus, if TLS is disabled (probably because we are doing |
- // SSLv3 fallback), we disable DEFLATE also. |
- // See http://crbug.com/31628 |
- rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_DEFLATE, ssl_config_.tls1_enabled); |
- if (rv != SECSuccess) |
+int SSLClientSocketNSS::Read(IOBuffer* buf, int buf_len, |
+ CompletionCallback* callback) { |
+ EnterFunction(buf_len); |
+ DCHECK(!user_read_callback_); |
+ DCHECK(!user_connect_callback_); |
+ DCHECK(!user_read_buf_); |
+ DCHECK(nss_bufs_); |
+ |
+ user_read_buf_ = buf; |
+ user_read_buf_len_ = buf_len; |
+ |
+ if (!completed_handshake_) { |
+ // In this case we have lied about being connected in order to merge the |
+ // first Write into a Snap Start handshake. We'll leave the read hanging |
+ // until the handshake has completed. |
+ DCHECK(pseudo_connected_); |
+ |
+ user_read_callback_ = callback; |
+ LeaveFunction(ERR_IO_PENDING); |
+ return ERR_IO_PENDING; |
+ } |
+ |
+ int rv = DoReadLoop(OK); |
+ |
+ if (rv == ERR_IO_PENDING) { |
+ user_read_callback_ = callback; |
+ } else { |
+ user_read_buf_ = NULL; |
+ user_read_buf_len_ = 0; |
+ } |
+ LeaveFunction(rv); |
+ return rv; |
+} |
+ |
+int SSLClientSocketNSS::Write(IOBuffer* buf, int buf_len, |
+ CompletionCallback* callback) { |
+ EnterFunction(buf_len); |
+ if (!pseudo_connected_) { |
+ DCHECK(completed_handshake_); |
+ DCHECK(next_handshake_state_ == STATE_NONE); |
+ DCHECK(!user_connect_callback_); |
+ } |
+ DCHECK(!user_write_callback_); |
+ DCHECK(!user_write_buf_); |
+ DCHECK(nss_bufs_); |
+ |
+ user_write_buf_ = buf; |
+ user_write_buf_len_ = buf_len; |
+ |
+ if (next_handshake_state_ == STATE_SNAP_START_WAIT_FOR_WRITE) { |
+ // We lied about being connected and we have been waiting for this write in |
+ // order to merge it into the Snap Start handshake. We'll leave the write |
+ // pending until the handshake completes. |
+ DCHECK(pseudo_connected_); |
+ int rv = DoHandshakeLoop(OK); |
+ if (rv == ERR_IO_PENDING) { |
+ user_write_callback_ = callback; |
+ } else { |
+ user_write_buf_ = NULL; |
+ user_write_buf_len_ = 0; |
+ } |
+ if (rv != OK) |
+ return rv; |
+ } |
+ |
+ if (corked_) { |
+ corked_ = false; |
+ uncork_timer_.Reset(); |
+ } |
+ int rv = DoWriteLoop(OK); |
+ |
+ if (rv == ERR_IO_PENDING) { |
+ user_write_callback_ = callback; |
+ } else { |
+ user_write_buf_ = NULL; |
+ user_write_buf_len_ = 0; |
+ } |
+ LeaveFunction(rv); |
+ return rv; |
+} |
+ |
+bool SSLClientSocketNSS::SetReceiveBufferSize(int32 size) { |
+ return transport_->socket()->SetReceiveBufferSize(size); |
+} |
+ |
+bool SSLClientSocketNSS::SetSendBufferSize(int32 size) { |
+ return transport_->socket()->SetSendBufferSize(size); |
+} |
+ |
+int SSLClientSocketNSS::Init() { |
+ EnterFunction(""); |
+ // Initialize the NSS SSL library in a threadsafe way. This also |
+ // initializes the NSS base library. |
+ EnsureNSSSSLInit(); |
+ if (!NSS_IsInitialized()) |
+ return ERR_UNEXPECTED; |
+#if !defined(OS_MACOSX) && !defined(OS_WIN) |
+ // We must call EnsureOCSPInit() here, on the IO thread, to get the IO loop |
+ // by MessageLoopForIO::current(). |
+ // X509Certificate::Verify() runs on a worker thread of CertVerifier. |
+ EnsureOCSPInit(); |
+#endif |
+ |
+ LeaveFunction(""); |
+ return OK; |
+} |
+ |
+int SSLClientSocketNSS::InitializeSSLOptions() { |
+ // Transport connected, now hook it up to nss |
+ // TODO(port): specify rx and tx buffer sizes separately |
+ nss_fd_ = memio_CreateIOLayer(kRecvBufferSize); |
+ if (nss_fd_ == NULL) { |
+ return ERR_OUT_OF_MEMORY; // TODO(port): map NSPR error code. |
+ } |
+ |
+ // Grab pointer to buffers |
+ nss_bufs_ = memio_GetSecret(nss_fd_); |
+ |
+ /* Create SSL state machine */ |
+ /* Push SSL onto our fake I/O socket */ |
+ nss_fd_ = SSL_ImportFD(NULL, nss_fd_); |
+ if (nss_fd_ == NULL) { |
+ LogFailedNSSFunction(net_log_, "SSL_ImportFD", ""); |
+ return ERR_OUT_OF_MEMORY; // TODO(port): map NSPR/NSS error code. |
+ } |
+ // TODO(port): set more ssl options! Check errors! |
+ |
+ int rv; |
+ |
+ rv = SSL_OptionSet(nss_fd_, SSL_SECURITY, PR_TRUE); |
+ if (rv != SECSuccess) { |
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_SECURITY"); |
+ return ERR_UNEXPECTED; |
+ } |
+ |
+ rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_SSL2, PR_FALSE); |
+ if (rv != SECSuccess) { |
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_ENABLE_SSL2"); |
+ return ERR_UNEXPECTED; |
+ } |
+ |
+ // Don't do V2 compatible hellos because they don't support TLS extensions. |
+ rv = SSL_OptionSet(nss_fd_, SSL_V2_COMPATIBLE_HELLO, PR_FALSE); |
+ if (rv != SECSuccess) { |
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_V2_COMPATIBLE_HELLO"); |
+ return ERR_UNEXPECTED; |
+ } |
+ |
+ rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_SSL3, ssl_config_.ssl3_enabled); |
+ if (rv != SECSuccess) { |
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_ENABLE_SSL3"); |
+ return ERR_UNEXPECTED; |
+ } |
+ |
+ rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_TLS, ssl_config_.tls1_enabled); |
+ if (rv != SECSuccess) { |
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_ENABLE_TLS"); |
+ return ERR_UNEXPECTED; |
+ } |
+ |
+ for (std::vector<uint16>::const_iterator it = |
+ ssl_config_.disabled_cipher_suites.begin(); |
+ it != ssl_config_.disabled_cipher_suites.end(); ++it) { |
+ // This will fail if the specified cipher is not implemented by NSS, but |
+ // the failure is harmless. |
+ SSL_CipherPrefSet(nss_fd_, *it, PR_FALSE); |
+ } |
+ |
+#ifdef SSL_ENABLE_SESSION_TICKETS |
+ // Support RFC 5077 |
+ rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_SESSION_TICKETS, PR_TRUE); |
+ if (rv != SECSuccess) { |
+ LogFailedNSSFunction( |
+ net_log_, "SSL_OptionSet", "SSL_ENABLE_SESSION_TICKETS"); |
+ } |
+#else |
+ #error "You need to install NSS-3.12 or later to build chromium" |
+#endif |
+ |
+#ifdef SSL_ENABLE_DEFLATE |
+ // Some web servers have been found to break if TLS is used *or* if DEFLATE |
+ // is advertised. Thus, if TLS is disabled (probably because we are doing |
+ // SSLv3 fallback), we disable DEFLATE also. |
+ // See http://crbug.com/31628 |
+ rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_DEFLATE, ssl_config_.tls1_enabled); |
+ if (rv != SECSuccess) |
LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_ENABLE_DEFLATE"); |
#endif |
@@ -764,247 +1080,39 @@ int SSLClientSocketNSS::InitializeSSLPeerName() { |
return OK; |
} |
-void SSLClientSocketNSS::Disconnect() { |
- EnterFunction(""); |
- // TODO(wtc): Send SSL close_notify alert. |
- if (nss_fd_ != NULL) { |
- PR_Close(nss_fd_); |
- nss_fd_ = NULL; |
+// Sets server_cert_ and server_cert_nss_ if not yet set. |
+// Returns server_cert_. |
+X509Certificate *SSLClientSocketNSS::UpdateServerCert() { |
+ // We set the server_cert_ from HandshakeCallback(). |
+ if (server_cert_ == NULL) { |
+ server_cert_nss_ = SSL_PeerCertificate(nss_fd_); |
+ if (server_cert_nss_) { |
+ PeerCertificateChain certs(nss_fd_); |
+ server_cert_ = X509Certificate::CreateFromDERCertChain( |
+ certs.AsStringPieceVector()); |
+ } |
} |
+ return server_cert_; |
+} |
- // Shut down anything that may call us back (through buffer_send_callback_, |
- // buffer_recv_callback, or handshake_io_callback_). |
- verifier_.reset(); |
- transport_->socket()->Disconnect(); |
- |
- // Reset object state |
- transport_send_busy_ = false; |
- transport_recv_busy_ = false; |
- user_connect_callback_ = NULL; |
- user_read_callback_ = NULL; |
- user_write_callback_ = NULL; |
- user_read_buf_ = NULL; |
- user_read_buf_len_ = 0; |
- user_write_buf_ = NULL; |
- user_write_buf_len_ = 0; |
- server_cert_ = NULL; |
- if (server_cert_nss_) { |
- CERT_DestroyCertificate(server_cert_nss_); |
- server_cert_nss_ = NULL; |
- } |
- 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; |
- start_cert_verification_time_ = base::TimeTicks(); |
- predicted_cert_chain_correct_ = false; |
- peername_initialized_ = false; |
- nss_bufs_ = NULL; |
- client_certs_.clear(); |
- client_auth_cert_needed_ = false; |
+// 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) { |
+ ssl_connection_status_ |= |
+ (static_cast<int>(channel_info.cipherSuite) & |
+ SSL_CONNECTION_CIPHERSUITE_MASK) << |
+ SSL_CONNECTION_CIPHERSUITE_SHIFT; |
- LeaveFunction(""); |
-} |
- |
-bool SSLClientSocketNSS::IsConnected() const { |
- // Ideally, we should also check if we have received the close_notify alert |
- // message from the server, and return false in that case. We're not doing |
- // that, so this function may return a false positive. Since the upper |
- // layer (HttpNetworkTransaction) needs to handle a persistent connection |
- // closed by the server when we send a request anyway, a false positive in |
- // exchange for simpler code is a good trade-off. |
- EnterFunction(""); |
- bool ret = (pseudo_connected_ || completed_handshake_) && |
- transport_->socket()->IsConnected(); |
- LeaveFunction(""); |
- return ret; |
-} |
- |
-bool SSLClientSocketNSS::IsConnectedAndIdle() const { |
- // Unlike IsConnected, this method doesn't return a false positive. |
- // |
- // Strictly speaking, we should check if we have received the close_notify |
- // alert message from the server, and return false in that case. Although |
- // the close_notify alert message means EOF in the SSL layer, it is just |
- // bytes to the transport layer below, so |
- // transport_->socket()->IsConnectedAndIdle() returns the desired false |
- // when we receive close_notify. |
- EnterFunction(""); |
- bool ret = (pseudo_connected_ || completed_handshake_) && |
- transport_->socket()->IsConnectedAndIdle(); |
- LeaveFunction(""); |
- return ret; |
-} |
- |
-int SSLClientSocketNSS::GetPeerAddress(AddressList* address) const { |
- return transport_->socket()->GetPeerAddress(address); |
-} |
- |
-const BoundNetLog& SSLClientSocketNSS::NetLog() const { |
- return net_log_; |
-} |
- |
-void SSLClientSocketNSS::SetSubresourceSpeculation() { |
- if (transport_.get() && transport_->socket()) { |
- transport_->socket()->SetSubresourceSpeculation(); |
- } else { |
- NOTREACHED(); |
- } |
-} |
- |
-void SSLClientSocketNSS::SetOmniboxSpeculation() { |
- if (transport_.get() && transport_->socket()) { |
- transport_->socket()->SetOmniboxSpeculation(); |
- } else { |
- NOTREACHED(); |
- } |
-} |
- |
-bool SSLClientSocketNSS::WasEverUsed() const { |
- if (transport_.get() && transport_->socket()) { |
- return transport_->socket()->WasEverUsed(); |
- } |
- NOTREACHED(); |
- 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); |
- DCHECK(!user_read_callback_); |
- DCHECK(!user_connect_callback_); |
- DCHECK(!user_read_buf_); |
- DCHECK(nss_bufs_); |
- |
- user_read_buf_ = buf; |
- user_read_buf_len_ = buf_len; |
- |
- if (!completed_handshake_) { |
- // In this case we have lied about being connected in order to merge the |
- // first Write into a Snap Start handshake. We'll leave the read hanging |
- // until the handshake has completed. |
- DCHECK(pseudo_connected_); |
- |
- user_read_callback_ = callback; |
- LeaveFunction(ERR_IO_PENDING); |
- return ERR_IO_PENDING; |
- } |
- |
- int rv = DoReadLoop(OK); |
- |
- if (rv == ERR_IO_PENDING) { |
- user_read_callback_ = callback; |
- } else { |
- user_read_buf_ = NULL; |
- user_read_buf_len_ = 0; |
- } |
- LeaveFunction(rv); |
- return rv; |
-} |
- |
-int SSLClientSocketNSS::Write(IOBuffer* buf, int buf_len, |
- CompletionCallback* callback) { |
- EnterFunction(buf_len); |
- if (!pseudo_connected_) { |
- DCHECK(completed_handshake_); |
- DCHECK(next_handshake_state_ == STATE_NONE); |
- DCHECK(!user_connect_callback_); |
- } |
- DCHECK(!user_write_callback_); |
- DCHECK(!user_write_buf_); |
- DCHECK(nss_bufs_); |
- |
- user_write_buf_ = buf; |
- user_write_buf_len_ = buf_len; |
- |
- if (next_handshake_state_ == STATE_SNAP_START_WAIT_FOR_WRITE) { |
- // We lied about being connected and we have been waiting for this write in |
- // order to merge it into the Snap Start handshake. We'll leave the write |
- // pending until the handshake completes. |
- DCHECK(pseudo_connected_); |
- int rv = DoHandshakeLoop(OK); |
- if (rv == ERR_IO_PENDING) { |
- user_write_callback_ = callback; |
- } else { |
- user_write_buf_ = NULL; |
- user_write_buf_len_ = 0; |
- } |
- if (rv != OK) |
- return rv; |
- } |
- |
- if (corked_) { |
- corked_ = false; |
- uncork_timer_.Reset(); |
- } |
- int rv = DoWriteLoop(OK); |
- |
- if (rv == ERR_IO_PENDING) { |
- user_write_callback_ = callback; |
- } else { |
- user_write_buf_ = NULL; |
- user_write_buf_len_ = 0; |
- } |
- LeaveFunction(rv); |
- return rv; |
-} |
- |
-bool SSLClientSocketNSS::SetReceiveBufferSize(int32 size) { |
- return transport_->socket()->SetReceiveBufferSize(size); |
-} |
- |
-bool SSLClientSocketNSS::SetSendBufferSize(int32 size) { |
- return transport_->socket()->SetSendBufferSize(size); |
-} |
- |
-// static |
-void SSLClientSocketNSS::ClearSessionCache() { |
- SSL_ClearSessionCache(); |
-} |
- |
-// Sets server_cert_ and server_cert_nss_ if not yet set. |
-// Returns server_cert_. |
-X509Certificate *SSLClientSocketNSS::UpdateServerCert() { |
- // We set the server_cert_ from HandshakeCallback(). |
- if (server_cert_ == NULL) { |
- server_cert_nss_ = SSL_PeerCertificate(nss_fd_); |
- if (server_cert_nss_) { |
- PeerCertificateChain certs(nss_fd_); |
- server_cert_ = X509Certificate::CreateFromDERCertChain( |
- certs.AsStringPieceVector()); |
- } |
- } |
- return server_cert_; |
-} |
- |
-// 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) { |
- ssl_connection_status_ |= |
- (static_cast<int>(channel_info.cipherSuite) & |
- SSL_CONNECTION_CIPHERSUITE_MASK) << |
- SSL_CONNECTION_CIPHERSUITE_SHIFT; |
- |
- ssl_connection_status_ |= |
- (static_cast<int>(channel_info.compressionMethod) & |
- SSL_CONNECTION_COMPRESSION_MASK) << |
- SSL_CONNECTION_COMPRESSION_SHIFT; |
+ ssl_connection_status_ |= |
+ (static_cast<int>(channel_info.compressionMethod) & |
+ SSL_CONNECTION_COMPRESSION_MASK) << |
+ SSL_CONNECTION_COMPRESSION_SHIFT; |
// 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. |
@@ -1051,119 +1159,34 @@ void SSLClientSocketNSS::UpdateConnectionStatus() { |
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_; |
+void SSLClientSocketNSS::DoReadCallback(int rv) { |
+ EnterFunction(rv); |
+ DCHECK(rv != ERR_IO_PENDING); |
+ DCHECK(user_read_callback_); |
- 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; |
- } |
+ // Since Run may result in Read being called, clear |user_read_callback_| |
+ // up front. |
+ CompletionCallback* c = user_read_callback_; |
+ user_read_callback_ = NULL; |
+ user_read_buf_ = NULL; |
+ user_read_buf_len_ = 0; |
+ c->Run(rv); |
LeaveFunction(""); |
} |
-void SSLClientSocketNSS::GetSSLCertRequestInfo( |
- SSLCertRequestInfo* cert_request_info) { |
- EnterFunction(""); |
- // TODO(rch): switch SSLCertRequestInfo.host_and_port to a HostPortPair |
- cert_request_info->host_and_port = host_and_port_.ToString(); |
- cert_request_info->client_certs = client_certs_; |
- LeaveFunction(cert_request_info->client_certs.size()); |
-} |
- |
-SSLClientSocket::NextProtoStatus |
-SSLClientSocketNSS::GetNextProto(std::string* proto) { |
-#if defined(SSL_NEXT_PROTO_NEGOTIATED) |
- if (!handshake_callback_called_) { |
- DCHECK(pseudo_connected_); |
- predicted_npn_proto_used_ = true; |
- *proto = predicted_npn_proto_; |
- return predicted_npn_status_; |
- } |
- |
- unsigned char buf[255]; |
- int state; |
- unsigned len; |
- SECStatus rv = SSL_GetNextProto(nss_fd_, &state, buf, &len, sizeof(buf)); |
- if (rv != SECSuccess) { |
- NOTREACHED() << "Error return from SSL_GetNextProto: " << rv; |
- proto->clear(); |
- return kNextProtoUnsupported; |
- } |
- // We don't check for truncation because sizeof(buf) is large enough to hold |
- // the maximum protocol size. |
- switch (state) { |
- case SSL_NEXT_PROTO_NO_SUPPORT: |
- proto->clear(); |
- return kNextProtoUnsupported; |
- case SSL_NEXT_PROTO_NEGOTIATED: |
- *proto = std::string(reinterpret_cast<char*>(buf), len); |
- return kNextProtoNegotiated; |
- case SSL_NEXT_PROTO_NO_OVERLAP: |
- *proto = std::string(reinterpret_cast<char*>(buf), len); |
- return kNextProtoNoOverlap; |
- default: |
- NOTREACHED() << "Unknown status from SSL_GetNextProto: " << state; |
- proto->clear(); |
- return kNextProtoUnsupported; |
- } |
-#else |
- // No NPN support in the libssl that we are building with. |
- proto->clear(); |
- return kNextProtoUnsupported; |
-#endif |
-} |
- |
-void SSLClientSocketNSS::UseDNSSEC(DNSSECProvider* provider) { |
- dnssec_provider_ = provider; |
-} |
- |
-void SSLClientSocketNSS::DoReadCallback(int rv) { |
- EnterFunction(rv); |
- DCHECK(rv != ERR_IO_PENDING); |
- DCHECK(user_read_callback_); |
- |
- // Since Run may result in Read being called, clear |user_read_callback_| |
- // up front. |
- CompletionCallback* c = user_read_callback_; |
- user_read_callback_ = NULL; |
- user_read_buf_ = NULL; |
- user_read_buf_len_ = 0; |
- c->Run(rv); |
- LeaveFunction(""); |
-} |
- |
-void SSLClientSocketNSS::DoWriteCallback(int rv) { |
- EnterFunction(rv); |
- DCHECK(rv != ERR_IO_PENDING); |
- DCHECK(user_write_callback_); |
- |
- // Since Run may result in Write being called, clear |user_write_callback_| |
- // up front. |
- CompletionCallback* c = user_write_callback_; |
- user_write_callback_ = NULL; |
- user_write_buf_ = NULL; |
- user_write_buf_len_ = 0; |
- c->Run(rv); |
- LeaveFunction(""); |
+void SSLClientSocketNSS::DoWriteCallback(int rv) { |
+ EnterFunction(rv); |
+ DCHECK(rv != ERR_IO_PENDING); |
+ DCHECK(user_write_callback_); |
+ |
+ // Since Run may result in Write being called, clear |user_write_callback_| |
+ // up front. |
+ CompletionCallback* c = user_write_callback_; |
+ user_write_callback_ = NULL; |
+ user_write_buf_ = NULL; |
+ user_write_buf_len_ = 0; |
+ c->Run(rv); |
+ LeaveFunction(""); |
} |
// As part of Connect(), the SSLClientSocketNSS object performs an SSL |
@@ -1250,109 +1273,6 @@ void SSLClientSocketNSS::OnRecvComplete(int result) { |
LeaveFunction(""); |
} |
-// Do network I/O between the given buffer and the given socket. |
-// Return true if some I/O performed, false otherwise (error or ERR_IO_PENDING) |
-bool SSLClientSocketNSS::DoTransportIO() { |
- EnterFunction(""); |
- bool network_moved = false; |
- if (nss_bufs_ != NULL) { |
- int nsent = BufferSend(); |
- int nreceived = BufferRecv(); |
- network_moved = (nsent > 0 || nreceived >= 0); |
- } |
- LeaveFunction(network_moved); |
- return network_moved; |
-} |
- |
-// Return 0 for EOF, |
-// > 0 for bytes transferred immediately, |
-// < 0 for error (or the non-error ERR_IO_PENDING). |
-int SSLClientSocketNSS::BufferSend(void) { |
- if (transport_send_busy_) |
- return ERR_IO_PENDING; |
- |
- EnterFunction(""); |
- const char* buf1; |
- const char* buf2; |
- unsigned int len1, len2; |
- memio_GetWriteParams(nss_bufs_, &buf1, &len1, &buf2, &len2); |
- const unsigned int len = len1 + len2; |
- |
- if (corked_ && len < kRecvBufferSize / 2) |
- return 0; |
- |
- int rv = 0; |
- if (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, |
- &buffer_send_callback_); |
- if (rv == ERR_IO_PENDING) { |
- transport_send_busy_ = true; |
- } else { |
- memio_PutWriteResult(nss_bufs_, MapErrorToNSS(rv)); |
- } |
- } |
- |
- LeaveFunction(rv); |
- return rv; |
-} |
- |
-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); |
- LeaveFunction(""); |
-} |
- |
- |
-int SSLClientSocketNSS::BufferRecv(void) { |
- if (transport_recv_busy_) return ERR_IO_PENDING; |
- |
- char *buf; |
- int nb = memio_GetReadParams(nss_bufs_, &buf); |
- EnterFunction(nb); |
- int rv; |
- if (!nb) { |
- // buffer too full to read into, so no I/O possible at moment |
- rv = ERR_IO_PENDING; |
- } else { |
- recv_buffer_ = new IOBuffer(nb); |
- rv = transport_->socket()->Read(recv_buffer_, nb, &buffer_recv_callback_); |
- if (rv == ERR_IO_PENDING) { |
- transport_recv_busy_ = true; |
- } else { |
- if (rv > 0) |
- memcpy(buf, recv_buffer_->data(), rv); |
- memio_PutReadResult(nss_bufs_, MapErrorToNSS(rv)); |
- recv_buffer_ = NULL; |
- } |
- } |
- LeaveFunction(rv); |
- return rv; |
-} |
- |
-void SSLClientSocketNSS::BufferRecvComplete(int result) { |
- EnterFunction(result); |
- if (result > 0) { |
- char *buf; |
- memio_GetReadParams(nss_bufs_, &buf); |
- memcpy(buf, recv_buffer_->data(), result); |
- } |
- recv_buffer_ = NULL; |
- memio_PutReadResult(nss_bufs_, MapErrorToNSS(result)); |
- transport_recv_busy_ = false; |
- OnRecvComplete(result); |
- LeaveFunction(""); |
-} |
- |
int SSLClientSocketNSS::DoHandshakeLoop(int last_io_result) { |
EnterFunction(last_io_result); |
bool network_moved; |
@@ -1459,1060 +1379,1139 @@ int SSLClientSocketNSS::DoWriteLoop(int result) { |
return rv; |
} |
-// static |
-// NSS calls this if an incoming certificate needs to be verified. |
-// Do nothing but return SECSuccess. |
-// This is called only in full handshake mode. |
-// Peer certificate is retrieved in HandshakeCallback() later, which is called |
-// in full handshake mode or in resumption handshake mode. |
-SECStatus SSLClientSocketNSS::OwnAuthCertHandler(void* arg, |
- PRFileDesc* socket, |
- PRBool checksig, |
- PRBool is_server) { |
-#ifdef SSL_ENABLE_FALSE_START |
- // In the event that we are False Starting this connection, we wish to send |
- // out the Finished message and first application data record in the same |
- // packet. This prevents non-determinism when talking to False Start |
- // intolerant servers which, otherwise, might see the two messages in |
- // different reads or not, depending on network conditions. |
- PRBool false_start = 0; |
- SECStatus rv = SSL_OptionGet(socket, SSL_ENABLE_FALSE_START, &false_start); |
- DCHECK_EQ(SECSuccess, rv); |
- |
- if (false_start) { |
- SSLClientSocketNSS* that = reinterpret_cast<SSLClientSocketNSS*>(arg); |
+int SSLClientSocketNSS::DoSnapStartLoadInfo() { |
+ EnterFunction(""); |
+ int rv = ssl_host_info_->WaitForDataReady(&handshake_io_callback_); |
+ GotoState(STATE_HANDSHAKE); |
- // ESET anti-virus is capable of intercepting HTTPS connections on Windows. |
- // However, it is False Start intolerant and causes the connections to hang |
- // forever. We detect ESET by the issuer of the leaf certificate and set a |
- // flag to return a specific error, giving the user instructions for |
- // reconfiguring ESET. |
- CERTCertificate* cert = SSL_PeerCertificate(that->nss_fd_); |
- if (cert) { |
- char* common_name = CERT_GetCommonName(&cert->issuer); |
- if (common_name) { |
- if (strcmp(common_name, "ESET_RootSslCert") == 0) |
- that->eset_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); |
+ if (rv == OK) { |
+ 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); |
} |
- CERT_DestroyCertificate(cert); |
- } |
- |
- if (false_start && !that->handshake_callback_called_) { |
- that->corked_ = true; |
- that->uncork_timer_.Start( |
- base::TimeDelta::FromMilliseconds(kCorkTimeoutMs), |
- that, &SSLClientSocketNSS::UncorkAfterTimeout); |
+ } 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); |
+ GotoState(STATE_SNAP_START_LOAD_INFO); |
} |
-#endif |
- // Tell NSS to not verify the certificate. |
- return SECSuccess; |
+ LeaveFunction(""); |
+ return rv; |
} |
-#if defined(NSS_PLATFORM_CLIENT_AUTH) |
-// static |
-// NSS calls this if a client certificate is needed. |
-SECStatus SSLClientSocketNSS::PlatformClientAuthHandler( |
- void* arg, |
- PRFileDesc* socket, |
- CERTDistNames* ca_names, |
- 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) { |
- if (that->ssl_config_.client_cert) { |
- PCCERT_CONTEXT cert_context = |
- that->ssl_config_.client_cert->os_cert_handle(); |
- if (VLOG_IS_ON(1)) { |
- do { |
- DWORD size_needed = 0; |
- BOOL got_info = CertGetCertificateContextProperty( |
- cert_context, CERT_KEY_PROV_INFO_PROP_ID, NULL, &size_needed); |
- if (!got_info) { |
- VLOG(1) << "Failed to get key prov info size " << GetLastError(); |
- break; |
- } |
- std::vector<BYTE> raw_info(size_needed); |
- got_info = CertGetCertificateContextProperty( |
- cert_context, CERT_KEY_PROV_INFO_PROP_ID, &raw_info[0], |
- &size_needed); |
- if (!got_info) { |
- VLOG(1) << "Failed to get key prov info " << GetLastError(); |
- break; |
- } |
- PCRYPT_KEY_PROV_INFO info = |
- reinterpret_cast<PCRYPT_KEY_PROV_INFO>(&raw_info[0]); |
- VLOG(1) << "Container Name: " << info->pwszContainerName |
- << "\nProvider Name: " << info->pwszProvName |
- << "\nProvider Type: " << info->dwProvType |
- << "\nFlags: " << info->dwFlags |
- << "\nProvider Param Count: " << info->cProvParam |
- << "\nKey Specifier: " << info->dwKeySpec; |
- } while (false); |
- |
- do { |
- DWORD size_needed = 0; |
- BOOL got_identifier = CertGetCertificateContextProperty( |
- cert_context, CERT_KEY_IDENTIFIER_PROP_ID, NULL, &size_needed); |
- if (!got_identifier) { |
- VLOG(1) << "Failed to get key identifier size " |
- << GetLastError(); |
- break; |
- } |
- std::vector<BYTE> raw_id(size_needed); |
- got_identifier = CertGetCertificateContextProperty( |
- cert_context, CERT_KEY_IDENTIFIER_PROP_ID, &raw_id[0], |
- &size_needed); |
- if (!got_identifier) { |
- VLOG(1) << "Failed to get key identifier " << GetLastError(); |
- break; |
- } |
- VLOG(1) << "Key Identifier: " << base::HexEncode(&raw_id[0], |
- size_needed); |
- } while (false); |
- } |
- 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; |
+int SSLClientSocketNSS::DoSnapStartWaitForWrite() { |
+ EnterFunction(""); |
+ // In this state, we're waiting for the first Write call so that we can merge |
+ // it into the Snap Start handshake. |
+ if (!user_write_buf_) { |
+ // We'll lie and say that we're connected in order that someone will call |
+ // Write. |
+ GotoState(STATE_SNAP_START_WAIT_FOR_WRITE); |
+ DCHECK(!user_connect_callback_); |
+ LeaveFunction(""); |
+ return ERR_IO_PENDING; |
} |
- that->client_certs_.clear(); |
+ // This is the largest Snap Start application data payload that we'll try to |
+ // use. A TCP client can only send three frames of data without an ACK and, |
+ // at 2048 bytes, this leaves some space for the rest of the ClientHello |
+ // (including possible session ticket). |
+ static const int kMaxSnapStartPayloadSize = 2048; |
- std::vector<CERT_NAME_BLOB> issuer_list(ca_names->nnames); |
- for (int i = 0; i < ca_names->nnames; ++i) { |
- issuer_list[i].cbData = ca_names->names[i].len; |
- issuer_list[i].pbData = ca_names->names[i].data; |
+ if (user_write_buf_len_ > kMaxSnapStartPayloadSize) { |
+ user_write_buf_len_ = kMaxSnapStartPayloadSize; |
+ // When we complete the handshake and call user_write_callback_ we'll say |
+ // that we only wrote |kMaxSnapStartPayloadSize| bytes. That way the rest |
+ // of the payload will be presented to |Write| again and transmitted as |
+ // normal application data. |
} |
- // Client certificates of the user are in the "MY" system certificate store. |
- HCERTSTORE my_cert_store = CertOpenSystemStore(NULL, L"MY"); |
- if (!my_cert_store) { |
- LOG(ERROR) << "Could not open the \"MY\" system certificate store: " |
- << GetLastError(); |
- return SECFailure; |
- } |
+ SECStatus rv = SSL_SetSnapStartApplicationData( |
+ nss_fd_, reinterpret_cast<const unsigned char*>(user_write_buf_->data()), |
+ user_write_buf_len_); |
+ DCHECK_EQ(SECSuccess, rv); |
- // Enumerate the client certificates. |
- CERT_CHAIN_FIND_BY_ISSUER_PARA find_by_issuer_para; |
- memset(&find_by_issuer_para, 0, sizeof(find_by_issuer_para)); |
- find_by_issuer_para.cbSize = sizeof(find_by_issuer_para); |
- find_by_issuer_para.pszUsageIdentifier = szOID_PKIX_KP_CLIENT_AUTH; |
- find_by_issuer_para.cIssuer = ca_names->nnames; |
- find_by_issuer_para.rgIssuer = ca_names->nnames ? &issuer_list[0] : NULL; |
- find_by_issuer_para.pfnFindCallback = ClientCertFindCallback; |
+ GotoState(STATE_HANDSHAKE); |
+ LeaveFunction(""); |
+ return OK; |
+} |
- PCCERT_CHAIN_CONTEXT chain_context = NULL; |
+int SSLClientSocketNSS::DoHandshake() { |
+ EnterFunction(""); |
+ int net_error = net::OK; |
+ SECStatus rv = SSL_ForceHandshake(nss_fd_); |
- for (;;) { |
- // Find a certificate chain. |
- chain_context = CertFindChainInStore(my_cert_store, |
- X509_ASN_ENCODING, |
- 0, |
- CERT_CHAIN_FIND_BY_ISSUER, |
- &find_by_issuer_para, |
- chain_context); |
- if (!chain_context) { |
- DWORD err = GetLastError(); |
- if (err != CRYPT_E_NOT_FOUND) |
- DLOG(ERROR) << "CertFindChainInStore failed: " << err; |
- break; |
+ if (client_auth_cert_needed_) { |
+ net_error = ERR_SSL_CLIENT_AUTH_CERT_NEEDED; |
+ net_log_.AddEvent(NetLog::TYPE_SSL_HANDSHAKE_ERROR, |
+ 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 |
+ // the next handshake. This will cause the server to ask for a client |
+ // cert again. |
+ if (rv == SECSuccess && SSL_InvalidateSession(nss_fd_) != SECSuccess) { |
+ LOG(WARNING) << "Couldn't invalidate SSL session: " << PR_GetError(); |
} |
+ } else if (rv == SECSuccess) { |
+ if (handshake_callback_called_) { |
+ if (eset_mitm_detected_) { |
+ net_error = ERR_ESET_ANTI_VIRUS_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; |
+ } |
+ } |
+ } |
+ } |
- // Get the leaf certificate. |
- PCCERT_CONTEXT cert_context = |
- chain_context->rgpChain[0]->rgpElement[0]->pCertContext; |
- // 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(X509Certificate::cert_store(), |
- cert_context, |
- CERT_STORE_ADD_USE_EXISTING, |
- &cert_context2); |
- if (!ok) { |
- NOTREACHED(); |
- continue; |
- } |
+#if defined(SSL_ENABLE_OCSP_STAPLING) |
+ const CacheOCSPResponseFromSideChannelFunction cache_ocsp_response = |
+ GetCacheOCSPResponseFromSideChannelFunction(); |
+ // TODO: we need to be able to plumb an OCSP response into the system |
+ // libraries. When we do, GetOCSPResponseFromSideChannelFunction |
+ // needs to be updated for those platforms. |
+ if (!predicted_cert_chain_correct_ && cache_ocsp_response) { |
+ unsigned int len = 0; |
+ SSL_GetStapledOCSPResponse(nss_fd_, NULL, &len); |
+ if (len) { |
+ const unsigned int orig_len = len; |
+ scoped_array<uint8> ocsp_response(new uint8[orig_len]); |
+ SSL_GetStapledOCSPResponse(nss_fd_, ocsp_response.get(), &len); |
+ DCHECK_EQ(orig_len, len); |
- // 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; |
+ SECItem ocsp_response_item; |
+ ocsp_response_item.type = siBuffer; |
+ ocsp_response_item.data = ocsp_response.get(); |
+ ocsp_response_item.len = len; |
+ |
+ cache_ocsp_response( |
+ CERT_GetDefaultCertDB(), server_cert_nss_, PR_Now(), |
+ &ocsp_response_item, NULL); |
+ } |
+ } |
+#endif |
+ |
+ 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 |
+ // wrong protocol! The higher levels of this network stack aren't |
+ // prepared for switching the protocol like that so we make up an error |
+ // and rely on the fact that the request will be retried. |
+ if (IsNPNProtocolMispredicted()) { |
+ LOG(WARNING) << "Mispredicted NPN protocol for " |
+ << host_and_port_.ToString(); |
+ net_error = ERR_SSL_SNAP_START_NPN_MISPREDICTION; |
+ } else { |
+ // Let's verify the certificate. |
+ GotoState(STATE_VERIFY_DNSSEC); |
+ } |
} |
- intermediates.push_back(intermediate_copy); |
+ // Done! |
+ } else { |
+ // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=562434 - |
+ // SSL_ForceHandshake returned SECSuccess prematurely. |
+ rv = SECFailure; |
+ net_error = ERR_SSL_PROTOCOL_ERROR; |
+ net_log_.AddEvent(NetLog::TYPE_SSL_HANDSHAKE_ERROR, |
+ make_scoped_refptr(new SSLErrorParams(net_error, 0))); |
} |
+ } else { |
+ PRErrorCode prerr = PR_GetError(); |
+ net_error = MapNSSHandshakeError(prerr); |
- scoped_refptr<X509Certificate> cert = X509Certificate::CreateFromHandle( |
- cert_context2, X509Certificate::SOURCE_LONE_CERT_IMPORT, |
- intermediates); |
- that->client_certs_.push_back(cert); |
+ // If not done, stay in this state |
+ if (net_error == ERR_IO_PENDING) { |
+ GotoState(STATE_HANDSHAKE); |
+ } else { |
+ LOG(ERROR) << "handshake failed; NSS error code " << prerr |
+ << ", net_error " << net_error; |
+ net_log_.AddEvent( |
+ NetLog::TYPE_SSL_HANDSHAKE_ERROR, |
+ make_scoped_refptr(new SSLErrorParams(net_error, prerr))); |
+ } |
+ } |
- X509Certificate::FreeOSCertHandle(cert_context2); |
- for (net::X509Certificate::OSCertHandles::iterator it = |
- intermediates.begin(); it != intermediates.end(); ++it) { |
- net::X509Certificate::FreeOSCertHandle(*it); |
+ LeaveFunction(""); |
+ return net_error; |
+} |
+ |
+int SSLClientSocketNSS::DoVerifyDNSSEC(int result) { |
+ if (ssl_config_.dns_cert_provenance_checking_enabled && |
+ dns_cert_checker_) { |
+ PeerCertificateChain certs(nss_fd_); |
+ dns_cert_checker_->DoAsyncVerification( |
+ host_and_port_.host(), certs.AsStringPieceVector()); |
+ } |
+ |
+ if (ssl_config_.dnssec_enabled) { |
+ DNSValidationResult r = CheckDNSSECChain(host_and_port_.host(), |
+ server_cert_nss_); |
+ if (r == DNSVR_SUCCESS) { |
+ 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; |
} |
} |
- BOOL ok = CertCloseStore(my_cert_store, CERT_CLOSE_STORE_CHECK_FLAG); |
- DCHECK(ok); |
+ if (dnssec_provider_ == NULL) { |
+ GotoState(STATE_VERIFY_CERT); |
+ return OK; |
+ } |
- // Tell NSS to suspend the client authentication. We will then abort the |
- // handshake by returning ERR_SSL_CLIENT_AUTH_CERT_NEEDED. |
- return SECWouldBlock; |
-#elif defined(OS_MACOSX) |
- if (that->ssl_config_.send_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); |
+ GotoState(STATE_VERIFY_DNSSEC_COMPLETE); |
+ RRResponse* response; |
+ dnssec_wait_start_time_ = base::Time::Now(); |
+ return dnssec_provider_->GetDNSSECRecords(&response, &handshake_io_callback_); |
+} |
- 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); |
+int SSLClientSocketNSS::DoVerifyDNSSECComplete(int result) { |
+ RRResponse* response; |
+ int err = dnssec_provider_->GetDNSSECRecords(&response, NULL); |
+ DCHECK_EQ(err, OK); |
- 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; |
+ const base::TimeDelta elapsed = base::Time::Now() - dnssec_wait_start_time_; |
+ HISTOGRAM_TIMES("Net.DNSSECWaitTime", elapsed); |
- 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; |
- } |
+ GotoState(STATE_VERIFY_CERT); |
+ if (!response || response->rrdatas.empty()) |
+ return OK; |
- that->client_certs_.clear(); |
+ std::vector<base::StringPiece> records; |
+ records.resize(response->rrdatas.size()); |
+ for (unsigned i = 0; i < response->rrdatas.size(); i++) |
+ records[i] = base::StringPiece(response->rrdatas[i]); |
+ DNSValidationResult r = |
+ VerifyTXTRecords(response->dnssec, server_cert_nss_, records); |
- // First, get the cert issuer names allowed by the server. |
- std::vector<CertPrincipal> valid_issuers; |
- int n = ca_names->nnames; |
- for (int i = 0; i < n; i++) { |
- // Parse each name into a CertPrincipal object. |
- CertPrincipal p; |
- if (p.ParseDistinguishedName(ca_names->names[i].data, |
- ca_names->names[i].len)) { |
- valid_issuers.push_back(p); |
- } |
+ if (!ssl_config_.dnssec_enabled) { |
+ // If DNSSEC is not enabled we don't take any action based on the result, |
+ // except to record the latency, above. |
+ return OK; |
} |
- // Now get the available client certs whose issuers are allowed by the server. |
- X509Certificate::GetSSLClientCertificates(that->host_and_port_.host(), |
- valid_issuers, |
- &that->client_certs_); |
+ switch (r) { |
+ case DNSVR_FAILURE: |
+ GotoState(STATE_VERIFY_CERT_COMPLETE); |
+ 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: |
+ 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: |
+ NOTREACHED(); |
+ GotoState(STATE_VERIFY_CERT); |
+ } |
- // Tell NSS to suspend the client authentication. We will then abort the |
- // handshake by returning ERR_SSL_CLIENT_AUTH_CERT_NEEDED. |
- return SECWouldBlock; |
-#else |
- return SECFailure; |
-#endif |
+ return OK; |
} |
-#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); |
+int SSLClientSocketNSS::DoVerifyCert(int result) { |
+ DCHECK(server_cert_); |
- that->client_auth_cert_needed_ = !that->ssl_config_.send_client_cert; |
- void* wincx = SSL_RevealPinArg(socket); |
+ GotoState(STATE_VERIFY_CERT_COMPLETE); |
+ start_cert_verification_time_ = base::TimeTicks::Now(); |
- // Second pass: a client certificate should have been selected. |
- if (that->ssl_config_.send_client_cert) { |
- if (that->ssl_config_.client_cert) { |
- CERTCertificate* cert = CERT_DupCertificate( |
- that->ssl_config_.client_cert->os_cert_handle()); |
- SECKEYPrivateKey* privkey = PK11_FindKeyByAnyCert(cert, wincx); |
- if (privkey) { |
- // TODO(jsorianopastor): We should wait for server certificate |
- // verification before sending our credentials. See |
- // http://crbug.com/13934. |
- *result_certificate = cert; |
- *result_private_key = privkey; |
- return SECSuccess; |
- } |
- LOG(WARNING) << "Client cert found without private key"; |
- } |
- // Send no client certificate. |
- return SECFailure; |
+ 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 end_time = ssl_host_info_->verification_end_time(); |
+ if (end_time.is_null()) |
+ end_time = base::TimeTicks::Now(); |
+ UMA_HISTOGRAM_TIMES("Net.SSLVerificationMergedMsSaved", |
+ end_time - 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); |
} |
- // Iterate over all client certificates. |
- CERTCertList* client_certs = CERT_FindUserCertsByUsage( |
- CERT_GetDefaultCertDB(), certUsageSSLClient, |
- PR_FALSE, PR_FALSE, wincx); |
- if (client_certs) { |
- for (CERTCertListNode* node = CERT_LIST_HEAD(client_certs); |
- !CERT_LIST_END(node, client_certs); |
- node = CERT_LIST_NEXT(node)) { |
- // Only offer unexpired certificates. |
- if (CERT_CheckCertValidTimes(node->cert, PR_Now(), PR_TRUE) != |
- secCertTimeValid) |
- continue; |
- // Filter by issuer. |
- // |
- // TODO(davidben): This does a binary comparison of the DER-encoded |
- // issuers. We should match according to RFC 5280 sec. 7.1. We should find |
- // an appropriate NSS function or add one if needbe. |
- if (ca_names->nnames && |
- NSS_CmpCertChainWCANames(node->cert, ca_names) != SECSuccess) |
- continue; |
- X509Certificate* x509_cert = X509Certificate::CreateFromHandle( |
- node->cert, X509Certificate::SOURCE_LONE_CERT_IMPORT, |
- net::X509Certificate::OSCertHandles()); |
- that->client_certs_.push_back(x509_cert); |
- } |
- CERT_DestroyCertList(client_certs); |
+ 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 SingleRequestCertVerifier(cert_verifier_)); |
+ server_cert_verify_result_ = &local_server_cert_verify_result_; |
+ return verifier_->Verify(server_cert_, host_and_port_.host(), flags, |
+ &local_server_cert_verify_result_, |
+ &handshake_io_callback_); |
+} |
+ |
+// Derived from AuthCertificateCallback() in |
+// mozilla/source/security/manager/ssl/src/nsNSSCallbacks.cpp. |
+int SSLClientSocketNSS::DoVerifyCertComplete(int result) { |
+ verifier_.reset(); |
+ |
+ if (!start_cert_verification_time_.is_null()) { |
+ base::TimeDelta verify_time = |
+ base::TimeTicks::Now() - start_cert_verification_time_; |
+ if (result == OK) |
+ UMA_HISTOGRAM_TIMES("Net.SSLCertVerificationTime", verify_time); |
+ else |
+ UMA_HISTOGRAM_TIMES("Net.SSLCertVerificationTimeError", verify_time); |
} |
- // Tell NSS to suspend the client authentication. We will then abort the |
- // handshake by returning ERR_SSL_CLIENT_AUTH_CERT_NEEDED. |
- return SECWouldBlock; |
-} |
-#endif // NSS_PLATFORM_CLIENT_AUTH |
+ if (ssl_host_info_.get()) |
+ ssl_host_info_->set_cert_verification_finished_time(); |
-// static |
-// NSS calls this when handshake is completed. |
-// After the SSL handshake is finished, use CertVerifier to verify |
-// the saved server certificate. |
-void SSLClientSocketNSS::HandshakeCallback(PRFileDesc* socket, |
- void* arg) { |
- SSLClientSocketNSS* that = reinterpret_cast<SSLClientSocketNSS*>(arg); |
+ // 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. |
- that->handshake_callback_called_ = true; |
+ // If we have been explicitly told to accept this certificate, override the |
+ // result of verifier_.Verify. |
+ // Eventually, we should cache the cert verification results so that we don't |
+ // need to call verifier_.Verify repeatedly. But for now we need to do this. |
+ // Alternatively, we could use the cert's status that we stored along with |
+ // the cert in the allowed_bad_certs vector. |
+ if (IsCertificateError(result) && |
+ ssl_config_.IsAllowedBadCert(server_cert_)) { |
+ VLOG(1) << "accepting bad SSL certificate, as user told us to"; |
+ result = OK; |
+ } |
- that->UpdateServerCert(); |
- that->UpdateConnectionStatus(); |
-} |
+ if (result == OK) |
+ LogConnectionTypeMetrics(); |
-int SSLClientSocketNSS::DoSnapStartLoadInfo() { |
- EnterFunction(""); |
- int rv = ssl_host_info_->WaitForDataReady(&handshake_io_callback_); |
- GotoState(STATE_HANDSHAKE); |
+ completed_handshake_ = true; |
- if (rv == OK) { |
- 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); |
+ // If we merged a Write call into the handshake we need to make the |
+ // callback now. |
+ if (user_write_callback_) { |
+ corked_ = false; |
+ 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_); |
} |
- } 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); |
- GotoState(STATE_SNAP_START_LOAD_INFO); |
} |
+ 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. |
+ DCHECK(next_handshake_state_ == STATE_NONE); |
+ return result; |
+} |
+ |
+int SSLClientSocketNSS::DoPayloadRead() { |
+ EnterFunction(user_read_buf_len_); |
+ DCHECK(user_read_buf_); |
+ DCHECK_GT(user_read_buf_len_, 0); |
+ int rv = PR_Read(nss_fd_, user_read_buf_->data(), user_read_buf_len_); |
+ if (client_auth_cert_needed_) { |
+ // We don't need to invalidate the non-client-authenticated SSL session |
+ // because the server will renegotiate anyway. |
+ LeaveFunction(""); |
+ rv = ERR_SSL_CLIENT_AUTH_CERT_NEEDED; |
+ net_log_.AddEvent(NetLog::TYPE_SSL_READ_ERROR, |
+ make_scoped_refptr(new SSLErrorParams(rv, 0))); |
+ return rv; |
+ } |
+ if (rv >= 0) { |
+ LogData(user_read_buf_->data(), rv); |
+ LeaveFunction(""); |
+ return rv; |
+ } |
+ PRErrorCode prerr = PR_GetError(); |
+ if (prerr == PR_WOULD_BLOCK_ERROR) { |
+ LeaveFunction(""); |
+ return ERR_IO_PENDING; |
+ } |
LeaveFunction(""); |
+ rv = MapNSSError(prerr); |
+ net_log_.AddEvent(NetLog::TYPE_SSL_READ_ERROR, |
+ make_scoped_refptr(new SSLErrorParams(rv, prerr))); |
return rv; |
} |
-int SSLClientSocketNSS::DoSnapStartWaitForWrite() { |
- EnterFunction(""); |
- // In this state, we're waiting for the first Write call so that we can merge |
- // it into the Snap Start handshake. |
- if (!user_write_buf_) { |
- // We'll lie and say that we're connected in order that someone will call |
- // Write. |
- GotoState(STATE_SNAP_START_WAIT_FOR_WRITE); |
- DCHECK(!user_connect_callback_); |
+int SSLClientSocketNSS::DoPayloadWrite() { |
+ EnterFunction(user_write_buf_len_); |
+ DCHECK(user_write_buf_); |
+ int rv = PR_Write(nss_fd_, user_write_buf_->data(), user_write_buf_len_); |
+ if (rv >= 0) { |
+ LogData(user_write_buf_->data(), rv); |
+ LeaveFunction(""); |
+ return rv; |
+ } |
+ PRErrorCode prerr = PR_GetError(); |
+ if (prerr == PR_WOULD_BLOCK_ERROR) { |
LeaveFunction(""); |
return ERR_IO_PENDING; |
} |
+ LeaveFunction(""); |
+ rv = MapNSSError(prerr); |
+ net_log_.AddEvent(NetLog::TYPE_SSL_WRITE_ERROR, |
+ make_scoped_refptr(new SSLErrorParams(rv, prerr))); |
+ return rv; |
+} |
- // This is the largest Snap Start application data payload that we'll try to |
- // use. A TCP client can only send three frames of data without an ACK and, |
- // at 2048 bytes, this leaves some space for the rest of the ClientHello |
- // (including possible session ticket). |
- static const int kMaxSnapStartPayloadSize = 2048; |
+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; |
+ }; |
+} |
- if (user_write_buf_len_ > kMaxSnapStartPayloadSize) { |
- user_write_buf_len_ = kMaxSnapStartPayloadSize; |
- // When we complete the handshake and call user_write_callback_ we'll say |
- // that we only wrote |kMaxSnapStartPayloadSize| bytes. That way the rest |
- // of the payload will be presented to |Write| again and transmitted as |
- // normal application data. |
+// SaveSnapStartInfo extracts the information needed to perform a Snap Start |
+// with this server in the future (if any) and tells |ssl_host_info_| to |
+// preserve it. |
+void SSLClientSocketNSS::SaveSnapStartInfo() { |
+ if (!ssl_host_info_.get()) |
+ return; |
+ |
+ // If the SSLHostInfo hasn't managed to load from disk yet then we can't save |
+ // anything. |
+ if (ssl_host_info_->WaitForDataReady(NULL) != OK) |
+ return; |
+ |
+ SECStatus rv; |
+ SSLSnapStartResult snap_start_type; |
+ rv = SSL_GetSnapStartResult(nss_fd_, &snap_start_type); |
+ if (rv != SECSuccess) { |
+ NOTREACHED(); |
+ return; |
+ } |
+ net_log_.AddEvent(NetLog::TYPE_SSL_SNAP_START, |
+ new NetLogIntegerParameter("type", snap_start_type)); |
+ if (snap_start_type == SSL_SNAP_START_FULL || |
+ snap_start_type == SSL_SNAP_START_RESUME) { |
+ // If we did a successful Snap Start then our information was correct and |
+ // there's no point saving it again. |
+ return; |
+ } |
+ |
+ const unsigned char* hello_data; |
+ unsigned hello_data_len; |
+ rv = SSL_GetPredictedServerHelloData(nss_fd_, &hello_data, &hello_data_len); |
+ if (rv != SECSuccess) { |
+ NOTREACHED(); |
+ return; |
+ } |
+ if (hello_data_len > std::numeric_limits<uint16>::max()) |
+ return; |
+ SSLHostInfo::State* state = ssl_host_info_->mutable_state(); |
+ |
+ if (hello_data_len > 0) { |
+ 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); |
+ } else { |
+ state->server_hello.clear(); |
+ state->npn_valid = false; |
} |
- SECStatus rv = SSL_SetSnapStartApplicationData( |
- nss_fd_, reinterpret_cast<const unsigned char*>(user_write_buf_->data()), |
- user_write_buf_len_); |
- DCHECK_EQ(SECSuccess, rv); |
- |
- GotoState(STATE_HANDSHAKE); |
- LeaveFunction(""); |
- return OK; |
-} |
+ 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; |
-int SSLClientSocketNSS::DoHandshake() { |
- EnterFunction(""); |
- int net_error = net::OK; |
- SECStatus rv = SSL_ForceHandshake(nss_fd_); |
+ state->certs.push_back(std::string( |
+ reinterpret_cast<char*>(certs[i]->derCert.data), |
+ certs[i]->derCert.len)); |
+ } |
- if (client_auth_cert_needed_) { |
- net_error = ERR_SSL_CLIENT_AUTH_CERT_NEEDED; |
- net_log_.AddEvent(NetLog::TYPE_SSL_HANDSHAKE_ERROR, |
- 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 |
- // the next handshake. This will cause the server to ask for a client |
- // cert again. |
- if (rv == SECSuccess && SSL_InvalidateSession(nss_fd_) != SECSuccess) { |
- LOG(WARNING) << "Couldn't invalidate SSL session: " << PR_GetError(); |
- } |
- } else if (rv == SECSuccess) { |
- if (handshake_callback_called_) { |
- if (eset_mitm_detected_) { |
- net_error = ERR_ESET_ANTI_VIRUS_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; |
- } |
- } |
- } |
- } |
+ ssl_host_info_->Persist(); |
+} |
-#if defined(SSL_ENABLE_OCSP_STAPLING) |
- const CacheOCSPResponseFromSideChannelFunction cache_ocsp_response = |
- GetCacheOCSPResponseFromSideChannelFunction(); |
- // TODO: we need to be able to plumb an OCSP response into the system |
- // libraries. When we do, GetOCSPResponseFromSideChannelFunction |
- // needs to be updated for those platforms. |
- if (!predicted_cert_chain_correct_ && cache_ocsp_response) { |
- unsigned int len = 0; |
- SSL_GetStapledOCSPResponse(nss_fd_, NULL, &len); |
- if (len) { |
- const unsigned int orig_len = len; |
- scoped_array<uint8> ocsp_response(new uint8[orig_len]); |
- SSL_GetStapledOCSPResponse(nss_fd_, ocsp_response.get(), &len); |
- DCHECK_EQ(orig_len, len); |
+// LoadSnapStartInfo parses |info|, which contains data previously serialised |
+// by |SaveSnapStartInfo|, and sets the predicted certificates and ServerHello |
+// data on the NSS socket. Returns true on success. If this function returns |
+// false, the caller should try a normal TLS handshake. |
+bool SSLClientSocketNSS::LoadSnapStartInfo() { |
+ const SSLHostInfo::State& state(ssl_host_info_->state()); |
- SECItem ocsp_response_item; |
- ocsp_response_item.type = siBuffer; |
- ocsp_response_item.data = ocsp_response.get(); |
- ocsp_response_item.len = len; |
+ if (state.server_hello.empty() || |
+ state.certs.empty() || |
+ !state.npn_valid) { |
+ return false; |
+ } |
- cache_ocsp_response( |
- CERT_GetDefaultCertDB(), server_cert_nss_, PR_Now(), |
- &ocsp_response_item, NULL); |
- } |
- } |
-#endif |
+ SECStatus rv; |
+ rv = SSL_SetPredictedServerHelloData( |
+ nss_fd_, |
+ reinterpret_cast<const uint8*>(state.server_hello.data()), |
+ state.server_hello.size()); |
+ DCHECK_EQ(SECSuccess, rv); |
- 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 |
- // wrong protocol! The higher levels of this network stack aren't |
- // prepared for switching the protocol like that so we make up an error |
- // and rely on the fact that the request will be retried. |
- if (IsNPNProtocolMispredicted()) { |
- LOG(WARNING) << "Mispredicted NPN protocol for " |
- << host_and_port_.ToString(); |
- net_error = ERR_SSL_SNAP_START_NPN_MISPREDICTION; |
- } else { |
- // Let's verify the certificate. |
- GotoState(STATE_VERIFY_DNSSEC); |
- } |
- } |
- // Done! |
- } else { |
- // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=562434 - |
- // SSL_ForceHandshake returned SECSuccess prematurely. |
- rv = SECFailure; |
- net_error = ERR_SSL_PROTOCOL_ERROR; |
- net_log_.AddEvent(NetLog::TYPE_SSL_HANDSHAKE_ERROR, |
- make_scoped_refptr(new SSLErrorParams(net_error, 0))); |
+ const std::vector<std::string>& certs_in = state.certs; |
+ scoped_array<CERTCertificate*> certs(new CERTCertificate*[certs_in.size()]); |
+ for (size_t i = 0; i < certs_in.size(); i++) { |
+ SECItem derCert; |
+ derCert.data = |
+ const_cast<uint8*>(reinterpret_cast<const uint8*>(certs_in[i].data())); |
+ derCert.len = certs_in[i].size(); |
+ certs[i] = CERT_NewTempCertificate( |
+ CERT_GetDefaultCertDB(), &derCert, NULL /* no nickname given */, |
+ PR_FALSE /* not permanent */, PR_TRUE /* copy DER data */); |
+ if (!certs[i]) { |
+ DestroyCertificates(&certs[0], i); |
+ NOTREACHED(); |
+ return false; |
} |
- } else { |
- PRErrorCode prerr = PR_GetError(); |
- net_error = MapNSSHandshakeError(prerr); |
+ } |
- // If not done, stay in this state |
- if (net_error == ERR_IO_PENDING) { |
- GotoState(STATE_HANDSHAKE); |
- } else { |
- LOG(ERROR) << "handshake failed; NSS error code " << prerr |
- << ", net_error " << net_error; |
- net_log_.AddEvent( |
- NetLog::TYPE_SSL_HANDSHAKE_ERROR, |
- make_scoped_refptr(new SSLErrorParams(net_error, prerr))); |
- } |
+ rv = SSL_SetPredictedPeerCertificates(nss_fd_, certs.get(), certs_in.size()); |
+ DestroyCertificates(&certs[0], certs_in.size()); |
+ DCHECK_EQ(SECSuccess, rv); |
+ |
+ if (state.npn_valid) { |
+ predicted_npn_status_ = state.npn_status; |
+ predicted_npn_proto_ = state.npn_protocol; |
} |
- LeaveFunction(""); |
- return net_error; |
+ return true; |
} |
-// DNSValidationResult enumerates the possible outcomes from processing a |
-// set of DNS records. |
-enum DNSValidationResult { |
- DNSVR_SUCCESS, // the cert is immediately acceptable. |
- DNSVR_FAILURE, // the cert is unconditionally rejected. |
- DNSVR_CONTINUE, // perform CA validation as usual. |
-}; |
+bool SSLClientSocketNSS::IsNPNProtocolMispredicted() { |
+ DCHECK(handshake_callback_called_); |
+ if (!predicted_npn_proto_used_) |
+ return false; |
+ std::string npn_proto; |
+ GetNextProto(&npn_proto); |
+ return predicted_npn_proto_ != npn_proto; |
+} |
-// VerifyTXTRecords processes the RRDATA for a number of DNS TXT records and |
-// checks them against the given certificate. |
-// dnssec: if true then the TXT records are DNSSEC validated. In this case, |
-// DNSVR_SUCCESS may be returned. |
-// server_cert_nss: the certificate to validate |
-// rrdatas: the TXT records for the current domain. |
-static DNSValidationResult VerifyTXTRecords( |
- bool dnssec, |
- CERTCertificate* server_cert_nss, |
- const std::vector<base::StringPiece>& rrdatas) { |
- bool found_well_formed_record = false; |
- bool matched_record = false; |
+void SSLClientSocketNSS::UncorkAfterTimeout() { |
+ corked_ = false; |
+ int nsent; |
+ do { |
+ nsent = BufferSend(); |
+ } while (nsent > 0); |
+} |
- for (std::vector<base::StringPiece>::const_iterator |
- i = rrdatas.begin(); i != rrdatas.end(); ++i) { |
- std::map<std::string, std::string> m( |
- DNSSECChainVerifier::ParseTLSTXTRecord(*i)); |
- if (m.empty()) |
- continue; |
+// Do network I/O between the given buffer and the given socket. |
+// Return true if some I/O performed, false otherwise (error or ERR_IO_PENDING) |
+bool SSLClientSocketNSS::DoTransportIO() { |
+ EnterFunction(""); |
+ bool network_moved = false; |
+ if (nss_bufs_ != NULL) { |
+ int nsent = BufferSend(); |
+ int nreceived = BufferRecv(); |
+ network_moved = (nsent > 0 || nreceived >= 0); |
+ } |
+ LeaveFunction(network_moved); |
+ return network_moved; |
+} |
- std::map<std::string, std::string>::const_iterator j; |
- j = m.find("v"); |
- if (j == m.end() || j->second != "tls1") |
- continue; |
+// Return 0 for EOF, |
+// > 0 for bytes transferred immediately, |
+// < 0 for error (or the non-error ERR_IO_PENDING). |
+int SSLClientSocketNSS::BufferSend(void) { |
+ if (transport_send_busy_) |
+ return ERR_IO_PENDING; |
- j = m.find("ha"); |
+ EnterFunction(""); |
+ const char* buf1; |
+ const char* buf2; |
+ unsigned int len1, len2; |
+ memio_GetWriteParams(nss_bufs_, &buf1, &len1, &buf2, &len2); |
+ const unsigned int len = len1 + len2; |
- HASH_HashType hash_algorithm; |
- unsigned hash_length; |
- if (j == m.end() || j->second == "sha1") { |
- hash_algorithm = HASH_AlgSHA1; |
- hash_length = SHA1_LENGTH; |
- } else if (j->second == "sha256") { |
- hash_algorithm = HASH_AlgSHA256; |
- hash_length = SHA256_LENGTH; |
+ if (corked_ && len < kRecvBufferSize / 2) |
+ return 0; |
+ |
+ int rv = 0; |
+ if (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, |
+ &buffer_send_callback_); |
+ if (rv == ERR_IO_PENDING) { |
+ transport_send_busy_ = true; |
} else { |
- continue; |
+ memio_PutWriteResult(nss_bufs_, MapErrorToNSS(rv)); |
} |
+ } |
- j = m.find("h"); |
- if (j == m.end()) |
- continue; |
+ LeaveFunction(rv); |
+ return rv; |
+} |
- std::vector<uint8> given_hash; |
- if (!base::HexStringToBytes(j->second, &given_hash)) |
- continue; |
+void SSLClientSocketNSS::BufferSendComplete(int result) { |
+ EnterFunction(result); |
- if (given_hash.size() != hash_length) |
- continue; |
+ // In the case of TCP FastOpen, connect is now finished. |
+ if (!peername_initialized_ && UsingTCPFastOpen()) |
+ InitializeSSLPeerName(); |
- uint8 calculated_hash[SHA256_LENGTH]; // SHA256 is the largest. |
- SECStatus rv; |
+ memio_PutWriteResult(nss_bufs_, MapErrorToNSS(result)); |
+ transport_send_busy_ = false; |
+ OnSendComplete(result); |
+ LeaveFunction(""); |
+} |
- j = m.find("hr"); |
- if (j == m.end() || j->second == "pubkey") { |
- rv = HASH_HashBuf(hash_algorithm, calculated_hash, |
- server_cert_nss->derPublicKey.data, |
- server_cert_nss->derPublicKey.len); |
- } else if (j->second == "cert") { |
- rv = HASH_HashBuf(hash_algorithm, calculated_hash, |
- server_cert_nss->derCert.data, |
- server_cert_nss->derCert.len); |
+int SSLClientSocketNSS::BufferRecv(void) { |
+ if (transport_recv_busy_) return ERR_IO_PENDING; |
+ |
+ char *buf; |
+ int nb = memio_GetReadParams(nss_bufs_, &buf); |
+ EnterFunction(nb); |
+ int rv; |
+ if (!nb) { |
+ // buffer too full to read into, so no I/O possible at moment |
+ rv = ERR_IO_PENDING; |
+ } else { |
+ recv_buffer_ = new IOBuffer(nb); |
+ rv = transport_->socket()->Read(recv_buffer_, nb, &buffer_recv_callback_); |
+ if (rv == ERR_IO_PENDING) { |
+ transport_recv_busy_ = true; |
} else { |
- continue; |
+ if (rv > 0) |
+ memcpy(buf, recv_buffer_->data(), rv); |
+ memio_PutReadResult(nss_bufs_, MapErrorToNSS(rv)); |
+ recv_buffer_ = NULL; |
} |
+ } |
+ LeaveFunction(rv); |
+ return rv; |
+} |
- if (rv != SECSuccess) |
- NOTREACHED(); |
+void SSLClientSocketNSS::BufferRecvComplete(int result) { |
+ EnterFunction(result); |
+ if (result > 0) { |
+ char *buf; |
+ memio_GetReadParams(nss_bufs_, &buf); |
+ memcpy(buf, recv_buffer_->data(), result); |
+ } |
+ recv_buffer_ = NULL; |
+ memio_PutReadResult(nss_bufs_, MapErrorToNSS(result)); |
+ transport_recv_busy_ = false; |
+ OnRecvComplete(result); |
+ LeaveFunction(""); |
+} |
- found_well_formed_record = true; |
+// static |
+// NSS calls this if an incoming certificate needs to be verified. |
+// Do nothing but return SECSuccess. |
+// This is called only in full handshake mode. |
+// Peer certificate is retrieved in HandshakeCallback() later, which is called |
+// in full handshake mode or in resumption handshake mode. |
+SECStatus SSLClientSocketNSS::OwnAuthCertHandler(void* arg, |
+ PRFileDesc* socket, |
+ PRBool checksig, |
+ PRBool is_server) { |
+#ifdef SSL_ENABLE_FALSE_START |
+ // In the event that we are False Starting this connection, we wish to send |
+ // out the Finished message and first application data record in the same |
+ // packet. This prevents non-determinism when talking to False Start |
+ // intolerant servers which, otherwise, might see the two messages in |
+ // different reads or not, depending on network conditions. |
+ PRBool false_start = 0; |
+ SECStatus rv = SSL_OptionGet(socket, SSL_ENABLE_FALSE_START, &false_start); |
+ DCHECK_EQ(SECSuccess, rv); |
- if (memcmp(calculated_hash, &given_hash[0], hash_length) == 0) { |
- matched_record = true; |
- if (dnssec) |
- return DNSVR_SUCCESS; |
+ if (false_start) { |
+ SSLClientSocketNSS* that = reinterpret_cast<SSLClientSocketNSS*>(arg); |
+ |
+ // ESET anti-virus is capable of intercepting HTTPS connections on Windows. |
+ // However, it is False Start intolerant and causes the connections to hang |
+ // forever. We detect ESET by the issuer of the leaf certificate and set a |
+ // flag to return a specific error, giving the user instructions for |
+ // reconfiguring ESET. |
+ CERTCertificate* cert = SSL_PeerCertificate(that->nss_fd_); |
+ if (cert) { |
+ char* common_name = CERT_GetCommonName(&cert->issuer); |
+ if (common_name) { |
+ if (strcmp(common_name, "ESET_RootSslCert") == 0) |
+ that->eset_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 (found_well_formed_record && !matched_record) |
- return DNSVR_FAILURE; |
+ if (false_start && !that->handshake_callback_called_) { |
+ that->corked_ = true; |
+ that->uncork_timer_.Start( |
+ base::TimeDelta::FromMilliseconds(kCorkTimeoutMs), |
+ that, &SSLClientSocketNSS::UncorkAfterTimeout); |
+ } |
+ } |
+#endif |
- return DNSVR_CONTINUE; |
+ // Tell NSS to not verify the certificate. |
+ return SECSuccess; |
} |
+#if defined(NSS_PLATFORM_CLIENT_AUTH) |
+// static |
+// NSS calls this if a client certificate is needed. |
+SECStatus SSLClientSocketNSS::PlatformClientAuthHandler( |
+ void* arg, |
+ PRFileDesc* socket, |
+ CERTDistNames* ca_names, |
+ CERTCertList** result_certs, |
+ void** result_private_key) { |
+ SSLClientSocketNSS* that = reinterpret_cast<SSLClientSocketNSS*>(arg); |
-// CheckDNSSECChain tries to validate a DNSSEC chain embedded in |
-// |server_cert_nss_|. It returns true iff a chain is found that proves the |
-// value of a TXT record that contains a valid public key fingerprint. |
-static DNSValidationResult CheckDNSSECChain( |
- const std::string& hostname, |
- CERTCertificate* server_cert_nss) { |
- if (!server_cert_nss) |
- return DNSVR_CONTINUE; |
+ that->client_auth_cert_needed_ = !that->ssl_config_.send_client_cert; |
+#if defined(OS_WIN) |
+ if (that->ssl_config_.send_client_cert) { |
+ if (that->ssl_config_.client_cert) { |
+ PCCERT_CONTEXT cert_context = |
+ that->ssl_config_.client_cert->os_cert_handle(); |
+ if (VLOG_IS_ON(1)) { |
+ do { |
+ DWORD size_needed = 0; |
+ BOOL got_info = CertGetCertificateContextProperty( |
+ cert_context, CERT_KEY_PROV_INFO_PROP_ID, NULL, &size_needed); |
+ if (!got_info) { |
+ VLOG(1) << "Failed to get key prov info size " << GetLastError(); |
+ break; |
+ } |
+ std::vector<BYTE> raw_info(size_needed); |
+ got_info = CertGetCertificateContextProperty( |
+ cert_context, CERT_KEY_PROV_INFO_PROP_ID, &raw_info[0], |
+ &size_needed); |
+ if (!got_info) { |
+ VLOG(1) << "Failed to get key prov info " << GetLastError(); |
+ break; |
+ } |
+ PCRYPT_KEY_PROV_INFO info = |
+ reinterpret_cast<PCRYPT_KEY_PROV_INFO>(&raw_info[0]); |
+ VLOG(1) << "Container Name: " << info->pwszContainerName |
+ << "\nProvider Name: " << info->pwszProvName |
+ << "\nProvider Type: " << info->dwProvType |
+ << "\nFlags: " << info->dwFlags |
+ << "\nProvider Param Count: " << info->cProvParam |
+ << "\nKey Specifier: " << info->dwKeySpec; |
+ } while (false); |
- // CERT_FindCertExtensionByOID isn't exported so we have to install an OID, |
- // get a tag for it and find the extension by using that tag. |
- static SECOidTag dnssec_chain_tag; |
- static bool dnssec_chain_tag_valid; |
- if (!dnssec_chain_tag_valid) { |
- // It's harmless if multiple threads enter this block concurrently. |
- static const uint8 kDNSSECChainOID[] = |
- // 1.3.6.1.4.1.11129.2.1.4 |
- // (iso.org.dod.internet.private.enterprises.google.googleSecurity. |
- // certificateExtensions.dnssecEmbeddedChain) |
- {0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x01, 0x04}; |
- SECOidData oid_data; |
- memset(&oid_data, 0, sizeof(oid_data)); |
- oid_data.oid.data = const_cast<uint8*>(kDNSSECChainOID); |
- oid_data.oid.len = sizeof(kDNSSECChainOID); |
- oid_data.desc = "DNSSEC chain"; |
- oid_data.supportedExtension = SUPPORTED_CERT_EXTENSION; |
- dnssec_chain_tag = SECOID_AddEntry(&oid_data); |
- DCHECK_NE(SEC_OID_UNKNOWN, dnssec_chain_tag); |
- dnssec_chain_tag_valid = true; |
- } |
+ do { |
+ DWORD size_needed = 0; |
+ BOOL got_identifier = CertGetCertificateContextProperty( |
+ cert_context, CERT_KEY_IDENTIFIER_PROP_ID, NULL, &size_needed); |
+ if (!got_identifier) { |
+ VLOG(1) << "Failed to get key identifier size " |
+ << GetLastError(); |
+ break; |
+ } |
+ std::vector<BYTE> raw_id(size_needed); |
+ got_identifier = CertGetCertificateContextProperty( |
+ cert_context, CERT_KEY_IDENTIFIER_PROP_ID, &raw_id[0], |
+ &size_needed); |
+ if (!got_identifier) { |
+ VLOG(1) << "Failed to get key identifier " << GetLastError(); |
+ break; |
+ } |
+ VLOG(1) << "Key Identifier: " << base::HexEncode(&raw_id[0], |
+ size_needed); |
+ } while (false); |
+ } |
+ 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); |
- SECItem dnssec_embedded_chain; |
- SECStatus rv = CERT_FindCertExtension(server_cert_nss, |
- dnssec_chain_tag, &dnssec_embedded_chain); |
- if (rv != SECSuccess) |
- return DNSVR_CONTINUE; |
+ // 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; |
- base::StringPiece chain( |
- reinterpret_cast<char*>(dnssec_embedded_chain.data), |
- dnssec_embedded_chain.len); |
- std::string dns_hostname; |
- if (!DNSDomainFromDot(hostname, &dns_hostname)) |
- return DNSVR_CONTINUE; |
- DNSSECChainVerifier verifier(dns_hostname, chain); |
- DNSSECChainVerifier::Error err = verifier.Verify(); |
- if (err != DNSSECChainVerifier::OK) { |
- LOG(ERROR) << "DNSSEC chain verification failed: " << err; |
- return DNSVR_CONTINUE; |
+ 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; |
} |
- if (verifier.rrtype() != kDNS_TXT) |
- return DNSVR_CONTINUE; |
- |
- DNSValidationResult r = VerifyTXTRecords( |
- true /* DNSSEC verified */, server_cert_nss, verifier.rrdatas()); |
- SECITEM_FreeItem(&dnssec_embedded_chain, PR_FALSE); |
- return r; |
-} |
+ that->client_certs_.clear(); |
-int SSLClientSocketNSS::DoVerifyDNSSEC(int result) { |
- if (ssl_config_.dns_cert_provenance_checking_enabled && |
- dns_cert_checker_) { |
- PeerCertificateChain certs(nss_fd_); |
- dns_cert_checker_->DoAsyncVerification( |
- host_and_port_.host(), certs.AsStringPieceVector()); |
+ std::vector<CERT_NAME_BLOB> issuer_list(ca_names->nnames); |
+ for (int i = 0; i < ca_names->nnames; ++i) { |
+ issuer_list[i].cbData = ca_names->names[i].len; |
+ issuer_list[i].pbData = ca_names->names[i].data; |
} |
- if (ssl_config_.dnssec_enabled) { |
- DNSValidationResult r = CheckDNSSECChain(host_and_port_.host(), |
- server_cert_nss_); |
- if (r == DNSVR_SUCCESS) { |
- 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; |
- } |
+ // Client certificates of the user are in the "MY" system certificate store. |
+ HCERTSTORE my_cert_store = CertOpenSystemStore(NULL, L"MY"); |
+ if (!my_cert_store) { |
+ LOG(ERROR) << "Could not open the \"MY\" system certificate store: " |
+ << GetLastError(); |
+ return SECFailure; |
} |
- if (dnssec_provider_ == NULL) { |
- GotoState(STATE_VERIFY_CERT); |
- return OK; |
- } |
+ // Enumerate the client certificates. |
+ CERT_CHAIN_FIND_BY_ISSUER_PARA find_by_issuer_para; |
+ memset(&find_by_issuer_para, 0, sizeof(find_by_issuer_para)); |
+ find_by_issuer_para.cbSize = sizeof(find_by_issuer_para); |
+ find_by_issuer_para.pszUsageIdentifier = szOID_PKIX_KP_CLIENT_AUTH; |
+ find_by_issuer_para.cIssuer = ca_names->nnames; |
+ find_by_issuer_para.rgIssuer = ca_names->nnames ? &issuer_list[0] : NULL; |
+ find_by_issuer_para.pfnFindCallback = ClientCertFindCallback; |
- GotoState(STATE_VERIFY_DNSSEC_COMPLETE); |
- RRResponse* response; |
- dnssec_wait_start_time_ = base::Time::Now(); |
- return dnssec_provider_->GetDNSSECRecords(&response, &handshake_io_callback_); |
-} |
+ PCCERT_CHAIN_CONTEXT chain_context = NULL; |
-int SSLClientSocketNSS::DoVerifyDNSSECComplete(int result) { |
- RRResponse* response; |
- int err = dnssec_provider_->GetDNSSECRecords(&response, NULL); |
- DCHECK_EQ(err, OK); |
+ for (;;) { |
+ // Find a certificate chain. |
+ chain_context = CertFindChainInStore(my_cert_store, |
+ X509_ASN_ENCODING, |
+ 0, |
+ CERT_CHAIN_FIND_BY_ISSUER, |
+ &find_by_issuer_para, |
+ chain_context); |
+ if (!chain_context) { |
+ DWORD err = GetLastError(); |
+ if (err != CRYPT_E_NOT_FOUND) |
+ DLOG(ERROR) << "CertFindChainInStore failed: " << err; |
+ break; |
+ } |
- const base::TimeDelta elapsed = base::Time::Now() - dnssec_wait_start_time_; |
- HISTOGRAM_TIMES("Net.DNSSECWaitTime", elapsed); |
+ // Get the leaf certificate. |
+ PCCERT_CONTEXT cert_context = |
+ chain_context->rgpChain[0]->rgpElement[0]->pCertContext; |
+ // 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(X509Certificate::cert_store(), |
+ cert_context, |
+ CERT_STORE_ADD_USE_EXISTING, |
+ &cert_context2); |
+ if (!ok) { |
+ NOTREACHED(); |
+ continue; |
+ } |
- GotoState(STATE_VERIFY_CERT); |
- if (!response || response->rrdatas.empty()) |
- return OK; |
+ // 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); |
+ } |
- std::vector<base::StringPiece> records; |
- records.resize(response->rrdatas.size()); |
- for (unsigned i = 0; i < response->rrdatas.size(); i++) |
- records[i] = base::StringPiece(response->rrdatas[i]); |
- DNSValidationResult r = |
- VerifyTXTRecords(response->dnssec, server_cert_nss_, records); |
+ scoped_refptr<X509Certificate> cert = X509Certificate::CreateFromHandle( |
+ cert_context2, X509Certificate::SOURCE_LONE_CERT_IMPORT, |
+ intermediates); |
+ that->client_certs_.push_back(cert); |
- if (!ssl_config_.dnssec_enabled) { |
- // If DNSSEC is not enabled we don't take any action based on the result, |
- // except to record the latency, above. |
- return OK; |
+ X509Certificate::FreeOSCertHandle(cert_context2); |
+ for (net::X509Certificate::OSCertHandles::iterator it = |
+ intermediates.begin(); it != intermediates.end(); ++it) { |
+ net::X509Certificate::FreeOSCertHandle(*it); |
+ } |
} |
- switch (r) { |
- case DNSVR_FAILURE: |
- GotoState(STATE_VERIFY_CERT_COMPLETE); |
- 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: |
- 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: |
- NOTREACHED(); |
- GotoState(STATE_VERIFY_CERT); |
- } |
+ BOOL ok = CertCloseStore(my_cert_store, CERT_CLOSE_STORE_CHECK_FLAG); |
+ DCHECK(ok); |
- return OK; |
-} |
+ // Tell NSS to suspend the client authentication. We will then abort the |
+ // handshake by returning ERR_SSL_CLIENT_AUTH_CERT_NEEDED. |
+ return SECWouldBlock; |
+#elif defined(OS_MACOSX) |
+ if (that->ssl_config_.send_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); |
-int SSLClientSocketNSS::DoVerifyCert(int result) { |
- DCHECK(server_cert_); |
+ 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); |
- GotoState(STATE_VERIFY_CERT_COMPLETE); |
- start_cert_verification_time_ = base::TimeTicks::Now(); |
+ 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; |
- 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 end_time = ssl_host_info_->verification_end_time(); |
- if (end_time.is_null()) |
- end_time = base::TimeTicks::Now(); |
- UMA_HISTOGRAM_TIMES("Net.SSLVerificationMergedMsSaved", |
- end_time - 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); |
+ 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; |
} |
- 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 SingleRequestCertVerifier(cert_verifier_)); |
- server_cert_verify_result_ = &local_server_cert_verify_result_; |
- return verifier_->Verify(server_cert_, host_and_port_.host(), flags, |
- &local_server_cert_verify_result_, |
- &handshake_io_callback_); |
-} |
- |
-// Derived from AuthCertificateCallback() in |
-// mozilla/source/security/manager/ssl/src/nsNSSCallbacks.cpp. |
-int SSLClientSocketNSS::DoVerifyCertComplete(int result) { |
- verifier_.reset(); |
+ that->client_certs_.clear(); |
- if (!start_cert_verification_time_.is_null()) { |
- base::TimeDelta verify_time = |
- base::TimeTicks::Now() - start_cert_verification_time_; |
- if (result == OK) |
- UMA_HISTOGRAM_TIMES("Net.SSLCertVerificationTime", verify_time); |
- else |
- UMA_HISTOGRAM_TIMES("Net.SSLCertVerificationTimeError", verify_time); |
+ // First, get the cert issuer names allowed by the server. |
+ std::vector<CertPrincipal> valid_issuers; |
+ int n = ca_names->nnames; |
+ for (int i = 0; i < n; i++) { |
+ // Parse each name into a CertPrincipal object. |
+ CertPrincipal p; |
+ if (p.ParseDistinguishedName(ca_names->names[i].data, |
+ ca_names->names[i].len)) { |
+ valid_issuers.push_back(p); |
+ } |
} |
- if (ssl_host_info_.get()) |
- ssl_host_info_->set_cert_verification_finished_time(); |
+ // Now get the available client certs whose issuers are allowed by the server. |
+ X509Certificate::GetSSLClientCertificates(that->host_and_port_.host(), |
+ valid_issuers, |
+ &that->client_certs_); |
- // 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. |
+ // Tell NSS to suspend the client authentication. We will then abort the |
+ // handshake by returning ERR_SSL_CLIENT_AUTH_CERT_NEEDED. |
+ return SECWouldBlock; |
+#else |
+ return SECFailure; |
+#endif |
+} |
- // If we have been explicitly told to accept this certificate, override the |
- // result of verifier_.Verify. |
- // Eventually, we should cache the cert verification results so that we don't |
- // need to call verifier_.Verify repeatedly. But for now we need to do this. |
- // Alternatively, we could use the cert's status that we stored along with |
- // the cert in the allowed_bad_certs vector. |
- if (IsCertificateError(result) && |
- ssl_config_.IsAllowedBadCert(server_cert_)) { |
- VLOG(1) << "accepting bad SSL certificate, as user told us to"; |
- result = OK; |
- } |
+#else // NSS_PLATFORM_CLIENT_AUTH |
- if (result == OK) |
- LogConnectionTypeMetrics(); |
+// 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); |
- completed_handshake_ = true; |
+ that->client_auth_cert_needed_ = !that->ssl_config_.send_client_cert; |
+ void* wincx = SSL_RevealPinArg(socket); |
- // If we merged a Write call into the handshake we need to make the |
- // callback now. |
- if (user_write_callback_) { |
- corked_ = false; |
- 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_); |
+ // Second pass: a client certificate should have been selected. |
+ if (that->ssl_config_.send_client_cert) { |
+ if (that->ssl_config_.client_cert) { |
+ CERTCertificate* cert = CERT_DupCertificate( |
+ that->ssl_config_.client_cert->os_cert_handle()); |
+ SECKEYPrivateKey* privkey = PK11_FindKeyByAnyCert(cert, wincx); |
+ if (privkey) { |
+ // TODO(jsorianopastor): We should wait for server certificate |
+ // verification before sending our credentials. See |
+ // http://crbug.com/13934. |
+ *result_certificate = cert; |
+ *result_private_key = privkey; |
+ return SECSuccess; |
} |
+ LOG(WARNING) << "Client cert found without private key"; |
} |
+ // Send no client certificate. |
+ return SECFailure; |
} |
- if (user_read_callback_) { |
- int rv = DoReadLoop(OK); |
- if (rv != ERR_IO_PENDING) |
- DoReadCallback(rv); |
+ // Iterate over all client certificates. |
+ CERTCertList* client_certs = CERT_FindUserCertsByUsage( |
+ CERT_GetDefaultCertDB(), certUsageSSLClient, |
+ PR_FALSE, PR_FALSE, wincx); |
+ if (client_certs) { |
+ for (CERTCertListNode* node = CERT_LIST_HEAD(client_certs); |
+ !CERT_LIST_END(node, client_certs); |
+ node = CERT_LIST_NEXT(node)) { |
+ // Only offer unexpired certificates. |
+ if (CERT_CheckCertValidTimes(node->cert, PR_Now(), PR_TRUE) != |
+ secCertTimeValid) |
+ continue; |
+ // Filter by issuer. |
+ // |
+ // TODO(davidben): This does a binary comparison of the DER-encoded |
+ // issuers. We should match according to RFC 5280 sec. 7.1. We should find |
+ // an appropriate NSS function or add one if needbe. |
+ if (ca_names->nnames && |
+ NSS_CmpCertChainWCANames(node->cert, ca_names) != SECSuccess) |
+ continue; |
+ X509Certificate* x509_cert = X509Certificate::CreateFromHandle( |
+ node->cert, X509Certificate::SOURCE_LONE_CERT_IMPORT, |
+ net::X509Certificate::OSCertHandles()); |
+ that->client_certs_.push_back(x509_cert); |
+ } |
+ CERT_DestroyCertList(client_certs); |
} |
- // Exit DoHandshakeLoop and return the result to the caller to Connect. |
- DCHECK(next_handshake_state_ == STATE_NONE); |
- return result; |
+ // Tell NSS to suspend the client authentication. We will then abort the |
+ // handshake by returning ERR_SSL_CLIENT_AUTH_CERT_NEEDED. |
+ return SECWouldBlock; |
} |
+#endif // NSS_PLATFORM_CLIENT_AUTH |
-int SSLClientSocketNSS::DoPayloadRead() { |
- EnterFunction(user_read_buf_len_); |
- DCHECK(user_read_buf_); |
- DCHECK_GT(user_read_buf_len_, 0); |
- int rv = PR_Read(nss_fd_, user_read_buf_->data(), user_read_buf_len_); |
- if (client_auth_cert_needed_) { |
- // We don't need to invalidate the non-client-authenticated SSL session |
- // because the server will renegotiate anyway. |
- LeaveFunction(""); |
- rv = ERR_SSL_CLIENT_AUTH_CERT_NEEDED; |
- net_log_.AddEvent(NetLog::TYPE_SSL_READ_ERROR, |
- make_scoped_refptr(new SSLErrorParams(rv, 0))); |
- return rv; |
- } |
- if (rv >= 0) { |
- LogData(user_read_buf_->data(), rv); |
- LeaveFunction(""); |
- return rv; |
- } |
- PRErrorCode prerr = PR_GetError(); |
- if (prerr == PR_WOULD_BLOCK_ERROR) { |
- LeaveFunction(""); |
- return ERR_IO_PENDING; |
- } |
- LeaveFunction(""); |
- rv = MapNSSError(prerr); |
- net_log_.AddEvent(NetLog::TYPE_SSL_READ_ERROR, |
- make_scoped_refptr(new SSLErrorParams(rv, prerr))); |
- return rv; |
-} |
+// static |
+// NSS calls this when handshake is completed. |
+// After the SSL handshake is finished, use CertVerifier to verify |
+// the saved server certificate. |
+void SSLClientSocketNSS::HandshakeCallback(PRFileDesc* socket, |
+ void* arg) { |
+ SSLClientSocketNSS* that = reinterpret_cast<SSLClientSocketNSS*>(arg); |
-int SSLClientSocketNSS::DoPayloadWrite() { |
- EnterFunction(user_write_buf_len_); |
- DCHECK(user_write_buf_); |
- int rv = PR_Write(nss_fd_, user_write_buf_->data(), user_write_buf_len_); |
- if (rv >= 0) { |
- LogData(user_write_buf_->data(), rv); |
- LeaveFunction(""); |
- return rv; |
- } |
- PRErrorCode prerr = PR_GetError(); |
- if (prerr == PR_WOULD_BLOCK_ERROR) { |
- LeaveFunction(""); |
- return ERR_IO_PENDING; |
- } |
- LeaveFunction(""); |
- rv = MapNSSError(prerr); |
- net_log_.AddEvent(NetLog::TYPE_SSL_WRITE_ERROR, |
- make_scoped_refptr(new SSLErrorParams(rv, prerr))); |
- return rv; |
-} |
+ that->handshake_callback_called_ = true; |
-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; |
- }; |
+ that->UpdateServerCert(); |
+ that->UpdateConnectionStatus(); |
} |
} // namespace net |