Chromium Code Reviews| 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 b46ebbdfd3e03497c5733d16bd510e16121da252..2c85e2928ecf7622c2331aa65cdae5a732b1e9cc 100644 |
| --- a/net/socket/ssl_client_socket_nss.cc |
| +++ b/net/socket/ssl_client_socket_nss.cc |
| @@ -106,7 +106,9 @@ |
| #include "net/socket/client_socket_handle.h" |
| #include "net/socket/nss_ssl_util.h" |
| #include "net/socket/ssl_error_params.h" |
| +#include "net/socket/ssl_host_info.h" |
| #include "net/ssl/ssl_cert_request_info.h" |
| +#include "net/ssl/ssl_config_service.h" |
|
wtc
2014/01/15 19:08:59
I assume it is necessary to add this header? It's
ramant (doing other things)
2014/01/18 00:21:56
Done.
|
| #include "net/ssl/ssl_connection_status_flags.h" |
| #include "net/ssl/ssl_info.h" |
| @@ -413,6 +415,7 @@ struct HandshakeState { |
| channel_id_sent = false; |
| server_cert_chain.Reset(NULL); |
| server_cert = NULL; |
| + predicted_cert_chain_correct = false; |
| sct_list_from_tls_extension.clear(); |
| stapled_ocsp_response.clear(); |
| resumed_handshake = false; |
| @@ -449,6 +452,11 @@ struct HandshakeState { |
| // Stapled OCSP response received. |
| std::string stapled_ocsp_response; |
| + // True if we predicted a certificate chain (via |
| + // Core::SetPredictedCertificates) and that prediction matched what the |
| + // server sent. |
| + bool predicted_cert_chain_correct; |
|
wtc
2014/01/15 19:08:59
List this right after server_cert.
ramant (doing other things)
2014/01/18 00:21:56
Done.
|
| + |
| // True if the current handshake was the result of TLS session resumption. |
| bool resumed_handshake; |
| @@ -1653,6 +1661,26 @@ void SSLClientSocketNSS::Core::HandshakeSucceeded() { |
| UpdateConnectionStatus(); |
| UpdateNextProto(); |
| + // We need to see if the predicted certificate chain (from |
| + // SetPredictedCertificates) matches the actual certificate chain. |
| + nss_handshake_state_.predicted_cert_chain_correct = false; |
| + if (!predicted_certs_.empty()) { |
| + PeerCertificateChain& certs = nss_handshake_state_.server_cert_chain; |
| + nss_handshake_state_.predicted_cert_chain_correct = |
| + certs.size() == predicted_certs_.size(); |
| + |
| + if (nss_handshake_state_.predicted_cert_chain_correct) { |
| + for (unsigned i = 0; i < certs.size(); i++) { |
| + if (certs[i]->derCert.len != predicted_certs_[i].size() || |
| + memcmp(certs[i]->derCert.data, predicted_certs_[i].data(), |
| + certs[i]->derCert.len) != 0) { |
| + nss_handshake_state_.predicted_cert_chain_correct = false; |
| + break; |
| + } |
| + } |
| + } |
| + } |
| + |
| // Update the network task runners view of the handshake state whenever |
| // a handshake has completed. |
| PostOrRunCallback( |
| @@ -2398,7 +2426,8 @@ void SSLClientSocketNSS::Core::UpdateStapledOCSPResponse() { |
| // TODO(agl): figure out how to plumb an OCSP response into the Mac |
| // system library and update IsOCSPStaplingSupported for Mac. |
| - if (IsOCSPStaplingSupported()) { |
| + if (!nss_handshake_state_.predicted_cert_chain_correct && |
| + IsOCSPStaplingSupported()) { |
| #if defined(OS_WIN) |
| if (nss_handshake_state_.server_cert) { |
| CRYPT_DATA_BLOB ocsp_response_blob; |
| @@ -2769,11 +2798,13 @@ SSLClientSocketNSS::SSLClientSocketNSS( |
| scoped_ptr<ClientSocketHandle> transport_socket, |
| const HostPortPair& host_and_port, |
| const SSLConfig& ssl_config, |
| + SSLHostInfo* ssl_host_info, |
| const SSLClientSocketContext& context) |
| : nss_task_runner_(nss_task_runner), |
| transport_(transport_socket.Pass()), |
| host_and_port_(host_and_port), |
| ssl_config_(ssl_config), |
| + server_cert_verify_result_(NULL), |
| cert_verifier_(context.cert_verifier), |
| cert_transparency_verifier_(context.cert_transparency_verifier), |
| server_bound_cert_service_(context.server_bound_cert_service), |
| @@ -2782,6 +2813,7 @@ SSLClientSocketNSS::SSLClientSocketNSS( |
| next_handshake_state_(STATE_NONE), |
| nss_fd_(NULL), |
| net_log_(transport_->socket()->NetLog()), |
| + ssl_host_info_(ssl_host_info), |
| transport_security_state_(context.transport_security_state), |
| valid_thread_id_(base::kInvalidThreadId) { |
| EnterFunction(""); |
| @@ -2813,16 +2845,16 @@ bool SSLClientSocketNSS::GetSSLInfo(SSLInfo* ssl_info) { |
| return false; |
| } |
| - ssl_info->cert_status = server_cert_verify_result_.cert_status; |
| - ssl_info->cert = server_cert_verify_result_.verified_cert; |
| + ssl_info->cert_status = server_cert_verify_result_->cert_status; |
| + ssl_info->cert = server_cert_verify_result_->verified_cert; |
| AddSCTInfoToSSLInfo(ssl_info); |
| ssl_info->connection_status = |
| core_->state().ssl_connection_status; |
| - ssl_info->public_key_hashes = server_cert_verify_result_.public_key_hashes; |
| + ssl_info->public_key_hashes = server_cert_verify_result_->public_key_hashes; |
| ssl_info->is_issued_by_known_root = |
| - server_cert_verify_result_.is_issued_by_known_root; |
| + server_cert_verify_result_->is_issued_by_known_root; |
| ssl_info->client_cert_sent = |
| ssl_config_.send_client_cert && ssl_config_.client_cert.get(); |
| ssl_info->channel_id_sent = WasChannelIDSent(); |
| @@ -2932,7 +2964,11 @@ int SSLClientSocketNSS::Connect(const CompletionCallback& callback) { |
| return rv; |
| } |
| - GotoState(STATE_HANDSHAKE); |
| + if (ssl_host_info_.get()) { |
| + GotoState(STATE_LOAD_SSL_HOST_INFO); |
| + } else { |
| + GotoState(STATE_HANDSHAKE); |
| + } |
| rv = DoHandshakeLoop(OK); |
| if (rv == ERR_IO_PENDING) { |
| @@ -2957,7 +2993,8 @@ void SSLClientSocketNSS::Disconnect() { |
| // Reset object state. |
| user_connect_callback_.Reset(); |
| - server_cert_verify_result_.Reset(); |
| + local_server_cert_verify_result_.Reset(); |
| + server_cert_verify_result_ = NULL; |
| completed_handshake_ = false; |
| start_cert_verification_time_ = base::TimeTicks(); |
| InitCore(); |
| @@ -3286,6 +3323,35 @@ void SSLClientSocketNSS::OnHandshakeIOComplete(int result) { |
| LeaveFunction(""); |
| } |
| +void SSLClientSocketNSS::LoadSSLHostInfo() { |
| + const SSLHostInfo::State& state(ssl_host_info_->state()); |
| + |
| + if (state.certs.empty()) |
| + return; |
| + |
| + /* TODO(rtenneti): Implement the following */ |
| + // const std::vector<std::string>& certs_in = state.certs; |
| + // core_->SetPredictedCertificates(certs_in); |
| +} |
| + |
| +int SSLClientSocketNSS::DoLoadSSLHostInfo() { |
| + EnterFunction(""); |
| + int rv = ssl_host_info_->WaitForDataReady( |
| + base::Bind(&SSLClientSocketNSS::OnHandshakeIOComplete, |
| + base::Unretained(this))); |
| + GotoState(STATE_HANDSHAKE); |
| + |
| + if (rv == OK) { |
| + LoadSSLHostInfo(); |
| + } else { |
| + DCHECK_EQ(ERR_IO_PENDING, rv); |
| + GotoState(STATE_LOAD_SSL_HOST_INFO); |
| + } |
| + |
| + LeaveFunction(""); |
| + return rv; |
| +} |
| + |
| int SSLClientSocketNSS::DoHandshakeLoop(int last_io_result) { |
| EnterFunction(last_io_result); |
| int rv = last_io_result; |
| @@ -3298,6 +3364,10 @@ int SSLClientSocketNSS::DoHandshakeLoop(int last_io_result) { |
| State state = next_handshake_state_; |
| GotoState(STATE_NONE); |
| switch (state) { |
| + case STATE_LOAD_SSL_HOST_INFO: |
| + DCHECK(rv == OK || rv == ERR_IO_PENDING); |
| + rv = DoLoadSSLHostInfo(); |
| + break; |
| case STATE_HANDSHAKE: |
| rv = DoHandshake(); |
| break; |
| @@ -3337,6 +3407,7 @@ int SSLClientSocketNSS::DoHandshakeComplete(int result) { |
| EnterFunction(result); |
| if (result == OK) { |
| + SaveSSLHostInfo(); |
| // SSL handshake is completed. Let's verify the certificate. |
| GotoState(STATE_VERIFY_CERT); |
| // Done! |
| @@ -3367,22 +3438,46 @@ int SSLClientSocketNSS::DoVerifyCert(int result) { |
| if (ssl_config_.IsAllowedBadCert(der_cert, &cert_status)) { |
| DCHECK(start_cert_verification_time_.is_null()); |
| VLOG(1) << "Received an expected bad cert with status: " << cert_status; |
| - server_cert_verify_result_.Reset(); |
| - server_cert_verify_result_.cert_status = cert_status; |
| - server_cert_verify_result_.verified_cert = core_->state().server_cert; |
| + server_cert_verify_result_ = &local_server_cert_verify_result_; |
| + local_server_cert_verify_result_.Reset(); |
| + local_server_cert_verify_result_.cert_status = cert_status; |
| + local_server_cert_verify_result_.verified_cert = |
| + core_->state().server_cert; |
| return OK; |
| } |
| // We may have failed to create X509Certificate object if we are |
| // running inside sandbox. |
| if (!core_->state().server_cert.get()) { |
| - server_cert_verify_result_.Reset(); |
| - server_cert_verify_result_.cert_status = CERT_STATUS_INVALID; |
| + server_cert_verify_result_ = &local_server_cert_verify_result_; |
| + local_server_cert_verify_result_.Reset(); |
| + local_server_cert_verify_result_.cert_status = CERT_STATUS_INVALID; |
| return ERR_CERT_INVALID; |
| } |
| start_cert_verification_time_ = base::TimeTicks::Now(); |
| + if (ssl_host_info_.get() && !ssl_host_info_->state().certs.empty() && |
| + core_->state().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); |
| + 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( |
| + base::Bind(&SSLClientSocketNSS::OnHandshakeIOComplete, |
| + base::Unretained(this))); |
| + } else { |
| + UMA_HISTOGRAM_ENUMERATION("Net.SSLVerificationMerged", 0 /* false */, 2); |
| + } |
| + |
| int flags = 0; |
| if (ssl_config_.rev_checking_enabled) |
| flags |= CertVerifier::VERIFY_REV_CHECKING_ENABLED; |
| @@ -3393,12 +3488,13 @@ int SSLClientSocketNSS::DoVerifyCert(int result) { |
| if (ssl_config_.rev_checking_required_local_anchors) |
| flags |= CertVerifier::VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS; |
| verifier_.reset(new SingleRequestCertVerifier(cert_verifier_)); |
| + server_cert_verify_result_ = &local_server_cert_verify_result_; |
| return verifier_->Verify( |
| core_->state().server_cert.get(), |
| host_and_port_.host(), |
| flags, |
| SSLConfigService::GetCRLSet().get(), |
| - &server_cert_verify_result_, |
| + &local_server_cert_verify_result_, |
| base::Bind(&SSLClientSocketNSS::OnHandshakeIOComplete, |
| base::Unretained(this)), |
| net_log_); |
| @@ -3454,11 +3550,11 @@ int SSLClientSocketNSS::DoVerifyCertComplete(int result) { |
| // * the build is recent (very old builds should fail open so that users |
| // have some chance to recover). |
| // |
| - const CertStatus cert_status = server_cert_verify_result_.cert_status; |
| + const CertStatus cert_status = server_cert_verify_result_->cert_status; |
| if (transport_security_state_ && |
| (result == OK || |
| (IsCertificateError(result) && IsCertStatusMinorError(cert_status))) && |
| - server_cert_verify_result_.is_issued_by_known_root && |
| + server_cert_verify_result_->is_issued_by_known_root && |
| TransportSecurityState::IsBuildTimely()) { |
| bool sni_available = |
| ssl_config_.version_max >= SSL_PROTOCOL_VERSION_TLS1 || |
| @@ -3470,7 +3566,7 @@ int SSLClientSocketNSS::DoVerifyCertComplete(int result) { |
| &domain_state) && |
| domain_state.HasPublicKeyPins()) { |
| if (!domain_state.CheckPublicKeyPins( |
| - server_cert_verify_result_.public_key_hashes)) { |
| + server_cert_verify_result_->public_key_hashes)) { |
| result = ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN; |
| UMA_HISTOGRAM_BOOLEAN("Net.PublicKeyPinSuccess", false); |
| TransportSecurityState::ReportUMAOnPinFailure(host); |
| @@ -3505,7 +3601,7 @@ void SSLClientSocketNSS::VerifyCT() { |
| // gets all the data it needs for SCT verification and does not do any |
| // external communication. |
| int result = cert_transparency_verifier_->Verify( |
| - server_cert_verify_result_.verified_cert, |
| + server_cert_verify_result_->verified_cert, |
| core_->state().stapled_ocsp_response, |
| core_->state().sct_list_from_tls_extension, |
| &ct_verify_result_, |
| @@ -3543,6 +3639,35 @@ void SSLClientSocketNSS::LogConnectionTypeMetrics() const { |
| }; |
| } |
| +// SaveSSLHostInfo saves the certificate chain of the connection so that we can |
| +// start verification faster in the future. |
| +void SSLClientSocketNSS::SaveSSLHostInfo() { |
| + 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(net::CompletionCallback()) != OK) |
| + return; |
| + |
| + SSLHostInfo::State* state = ssl_host_info_->mutable_state(); |
| + |
| + state->certs.clear(); |
| + const PeerCertificateChain& certs = core_->state().server_cert_chain; |
| + for (unsigned i = 0; i < certs.size(); i++) { |
| + if (certs[i] == NULL || |
| + 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_host_info_->Persist(); |
| +} |
| + |
| void SSLClientSocketNSS::EnsureThreadIdAssigned() const { |
| base::AutoLock auto_lock(lock_); |
| if (valid_thread_id_ != base::kInvalidThreadId) |