// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "content/browser/ssl/ssl_manager.h" #include #include #include "base/bind.h" #include "base/macros.h" #include "base/memory/ptr_util.h" #include "base/metrics/histogram_macros.h" #include "base/strings/utf_string_conversions.h" #include "base/supports_user_data.h" #include "content/browser/devtools/devtools_agent_host_impl.h" #include "content/browser/devtools/protocol/security_handler.h" #include "content/browser/frame_host/navigation_entry_impl.h" #include "content/browser/loader/resource_dispatcher_host_impl.h" #include "content/browser/loader/resource_request_info_impl.h" #include "content/browser/ssl/ssl_error_handler.h" #include "content/browser/web_contents/web_contents_impl.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/certificate_request_result_type.h" #include "content/public/browser/content_browser_client.h" #include "content/public/browser/devtools_agent_host.h" #include "content/public/browser/navigation_details.h" #include "content/public/browser/ssl_host_state_delegate.h" #include "net/url_request/url_request.h" namespace content { namespace { const char kSSLManagerKeyName[] = "content_ssl_manager"; // Events for UMA. Do not reorder or change! enum SSLGoodCertSeenEvent { NO_PREVIOUS_EXCEPTION = 0, HAD_PREVIOUS_EXCEPTION = 1, SSL_GOOD_CERT_SEEN_EVENT_MAX = 2 }; void OnAllowCertificateWithRecordDecision( bool record_decision, const base::Callback& callback, CertificateRequestResultType decision) { callback.Run(record_decision, decision); } void OnAllowCertificate(SSLErrorHandler* handler, SSLHostStateDelegate* state_delegate, bool record_decision, CertificateRequestResultType decision) { DCHECK(handler->ssl_info().is_valid()); switch (decision) { case CERTIFICATE_REQUEST_RESULT_TYPE_CONTINUE: // Note that we should not call SetMaxSecurityStyle here, because // the active NavigationEntry has just been deleted (in // HideInterstitialPage) and the new NavigationEntry will not be // set until DidNavigate. This is ok, because the new // NavigationEntry will have its max security style set within // DidNavigate. // // While AllowCert() executes synchronously on this thread, // ContinueRequest() gets posted to a different thread. Calling // AllowCert() first ensures deterministic ordering. if (record_decision && state_delegate) { state_delegate->AllowCert(handler->request_url().host(), *handler->ssl_info().cert.get(), handler->cert_error()); } handler->ContinueRequest(); return; case CERTIFICATE_REQUEST_RESULT_TYPE_DENY: handler->DenyRequest(); return; case CERTIFICATE_REQUEST_RESULT_TYPE_CANCEL: handler->CancelRequest(); return; } } class SSLManagerSet : public base::SupportsUserData::Data { public: SSLManagerSet() { } std::set& get() { return set_; } private: std::set set_; DISALLOW_COPY_AND_ASSIGN(SSLManagerSet); }; void HandleSSLErrorOnUI( const base::Callback& web_contents_getter, const base::WeakPtr& delegate, const ResourceType resource_type, const GURL& url, const net::SSLInfo& ssl_info, bool fatal) { content::WebContents* web_contents = web_contents_getter.Run(); std::unique_ptr handler(new SSLErrorHandler( web_contents, delegate, resource_type, url, ssl_info, fatal)); if (!web_contents) { // Requests can fail to dispatch because they don't have a WebContents. See // https://crbug.com/86537. In this case we have to make a decision in this // function, so we ignore revocation check failures. if (net::IsCertStatusMinorError(ssl_info.cert_status)) { handler->ContinueRequest(); } else { handler->CancelRequest(); } return; } NavigationControllerImpl* controller = static_cast(&web_contents->GetController()); controller->SetPendingNavigationSSLError(true); SSLManager* manager = controller->ssl_manager(); manager->OnCertError(std::move(handler)); } } // namespace // static void SSLManager::OnSSLCertificateError( const base::WeakPtr& delegate, const ResourceType resource_type, const GURL& url, const base::Callback& web_contents_getter, const net::SSLInfo& ssl_info, bool fatal) { DCHECK(delegate.get()); DVLOG(1) << "OnSSLCertificateError() cert_error: " << net::MapCertStatusToNetError(ssl_info.cert_status) << " resource_type: " << resource_type << " url: " << url.spec() << " cert_status: " << std::hex << ssl_info.cert_status; // A certificate error occurred. Construct a SSLErrorHandler object // on the UI thread for processing. BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&HandleSSLErrorOnUI, web_contents_getter, delegate, resource_type, url, ssl_info, fatal)); } // static void SSLManager::OnSSLCertificateSubresourceError( const base::WeakPtr& delegate, const GURL& url, int render_process_id, int render_frame_id, const net::SSLInfo& ssl_info, bool fatal) { OnSSLCertificateError(delegate, RESOURCE_TYPE_SUB_RESOURCE, url, base::Bind(&WebContentsImpl::FromRenderFrameHostID, render_process_id, render_frame_id), ssl_info, fatal); } SSLManager::SSLManager(NavigationControllerImpl* controller) : controller_(controller), ssl_host_state_delegate_( controller->GetBrowserContext()->GetSSLHostStateDelegate()) { DCHECK(controller_); SSLManagerSet* managers = static_cast( controller_->GetBrowserContext()->GetUserData(kSSLManagerKeyName)); if (!managers) { auto managers_owned = base::MakeUnique(); managers = managers_owned.get(); controller_->GetBrowserContext()->SetUserData(kSSLManagerKeyName, std::move(managers_owned)); } managers->get().insert(this); } SSLManager::~SSLManager() { SSLManagerSet* managers = static_cast( controller_->GetBrowserContext()->GetUserData(kSSLManagerKeyName)); managers->get().erase(this); } void SSLManager::DidCommitProvisionalLoad(const LoadCommittedDetails& details) { NavigationEntryImpl* entry = controller_->GetLastCommittedEntry(); int content_status_flags = 0; if (!details.is_main_frame) { // If it wasn't a main-frame navigation, then carry over content // status flags. (For example, the mixed content flag shouldn't // clear because of a frame navigation.) NavigationEntryImpl* previous_entry = controller_->GetEntryAtIndex(details.previous_entry_index); if (previous_entry) { content_status_flags = previous_entry->GetSSL().content_status; } } UpdateEntry(entry, content_status_flags, 0); // Always notify the WebContents that the SSL state changed when a // load is committed, in case the active navigation entry has changed. NotifyDidChangeVisibleSSLState(); } void SSLManager::DidDisplayMixedContent() { UpdateLastCommittedEntry(SSLStatus::DISPLAYED_INSECURE_CONTENT, 0); } void SSLManager::DidContainInsecureFormAction() { UpdateLastCommittedEntry(SSLStatus::DISPLAYED_FORM_WITH_INSECURE_ACTION, 0); } void SSLManager::DidDisplayContentWithCertErrors() { NavigationEntryImpl* entry = controller_->GetLastCommittedEntry(); if (!entry) return; // Only record information about subresources with cert errors if the // main page is HTTPS with a certificate. if (entry->GetURL().SchemeIsCryptographic() && entry->GetSSL().certificate) { UpdateLastCommittedEntry(SSLStatus::DISPLAYED_CONTENT_WITH_CERT_ERRORS, 0); } } void SSLManager::DidShowPasswordInputOnHttp() { UpdateLastCommittedEntry(SSLStatus::DISPLAYED_PASSWORD_FIELD_ON_HTTP, 0); } void SSLManager::DidHideAllPasswordInputsOnHttp() { UpdateLastCommittedEntry(0, SSLStatus::DISPLAYED_PASSWORD_FIELD_ON_HTTP); } void SSLManager::DidShowCreditCardInputOnHttp() { UpdateLastCommittedEntry(SSLStatus::DISPLAYED_CREDIT_CARD_FIELD_ON_HTTP, 0); } void SSLManager::DidRunMixedContent(const GURL& security_origin) { NavigationEntryImpl* entry = controller_->GetLastCommittedEntry(); if (!entry) return; SiteInstance* site_instance = entry->site_instance(); if (!site_instance) return; if (ssl_host_state_delegate_) { ssl_host_state_delegate_->HostRanInsecureContent( security_origin.host(), site_instance->GetProcess()->GetID(), SSLHostStateDelegate::MIXED_CONTENT); } UpdateEntry(entry, 0, 0); NotifySSLInternalStateChanged(controller_->GetBrowserContext()); } void SSLManager::DidRunContentWithCertErrors(const GURL& security_origin) { NavigationEntryImpl* entry = controller_->GetLastCommittedEntry(); if (!entry) return; SiteInstance* site_instance = entry->site_instance(); if (!site_instance) return; if (ssl_host_state_delegate_) { ssl_host_state_delegate_->HostRanInsecureContent( security_origin.host(), site_instance->GetProcess()->GetID(), SSLHostStateDelegate::CERT_ERRORS_CONTENT); } UpdateEntry(entry, 0, 0); NotifySSLInternalStateChanged(controller_->GetBrowserContext()); } void SSLManager::OnCertError(std::unique_ptr handler) { bool expired_previous_decision = false; // First we check if we know the policy for this error. DCHECK(handler->ssl_info().is_valid()); SSLHostStateDelegate::CertJudgment judgment = ssl_host_state_delegate_ ? ssl_host_state_delegate_->QueryPolicy( handler->request_url().host(), *handler->ssl_info().cert.get(), handler->cert_error(), &expired_previous_decision) : SSLHostStateDelegate::DENIED; if (judgment == SSLHostStateDelegate::ALLOWED) { handler->ContinueRequest(); return; } // For all other hosts, which must be DENIED, a blocking page is shown to the // user every time they come back to the page. int options_mask = 0; switch (handler->cert_error()) { case net::ERR_CERT_COMMON_NAME_INVALID: case net::ERR_CERT_DATE_INVALID: case net::ERR_CERT_AUTHORITY_INVALID: case net::ERR_CERT_WEAK_SIGNATURE_ALGORITHM: case net::ERR_CERT_WEAK_KEY: case net::ERR_CERT_NAME_CONSTRAINT_VIOLATION: case net::ERR_CERT_VALIDITY_TOO_LONG: case net::ERR_CERTIFICATE_TRANSPARENCY_REQUIRED: if (!handler->fatal()) options_mask |= OVERRIDABLE; else options_mask |= STRICT_ENFORCEMENT; if (expired_previous_decision) options_mask |= EXPIRED_PREVIOUS_DECISION; OnCertErrorInternal(std::move(handler), options_mask); break; case net::ERR_CERT_NO_REVOCATION_MECHANISM: // Ignore this error. handler->ContinueRequest(); break; case net::ERR_CERT_UNABLE_TO_CHECK_REVOCATION: // We ignore this error but will show a warning status in the location // bar. handler->ContinueRequest(); break; case net::ERR_CERT_CONTAINS_ERRORS: case net::ERR_CERT_REVOKED: case net::ERR_CERT_INVALID: case net::ERR_SSL_WEAK_SERVER_EPHEMERAL_DH_KEY: case net::ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN: if (handler->fatal()) options_mask |= STRICT_ENFORCEMENT; if (expired_previous_decision) options_mask |= EXPIRED_PREVIOUS_DECISION; OnCertErrorInternal(std::move(handler), options_mask); break; default: NOTREACHED(); handler->CancelRequest(); break; } } void SSLManager::DidStartResourceResponse(const GURL& url, bool has_certificate, net::CertStatus ssl_cert_status) { if (has_certificate && url.SchemeIsCryptographic() && !net::IsCertStatusError(ssl_cert_status)) { // If the scheme is https: or wss: *and* the security info for the // cert has been set (i.e. the cert id is not 0) and the cert did // not have any errors, revoke any previous decisions that // have occurred. If the cert info has not been set, do nothing since it // isn't known if the connection was actually a valid connection or if it // had a cert error. SSLGoodCertSeenEvent event = NO_PREVIOUS_EXCEPTION; if (ssl_host_state_delegate_ && ssl_host_state_delegate_->HasAllowException(url.host())) { // If there's no certificate error, a good certificate has been seen, so // clear out any exceptions that were made by the user for bad // certificates. This intentionally does not apply to cached resources // (see https://crbug.com/634553 for an explanation). ssl_host_state_delegate_->RevokeUserAllowExceptions(url.host()); event = HAD_PREVIOUS_EXCEPTION; } UMA_HISTOGRAM_ENUMERATION("interstitial.ssl.good_cert_seen", event, SSL_GOOD_CERT_SEEN_EVENT_MAX); } } void SSLManager::OnCertErrorInternal(std::unique_ptr handler, int options_mask) { bool overridable = (options_mask & OVERRIDABLE) != 0; bool strict_enforcement = (options_mask & STRICT_ENFORCEMENT) != 0; bool expired_previous_decision = (options_mask & EXPIRED_PREVIOUS_DECISION) != 0; WebContents* web_contents = handler->web_contents(); int cert_error = handler->cert_error(); const net::SSLInfo& ssl_info = handler->ssl_info(); const GURL& request_url = handler->request_url(); ResourceType resource_type = handler->resource_type(); base::Callback callback = base::Bind(&OnAllowCertificate, base::Owned(handler.release()), ssl_host_state_delegate_); DevToolsAgentHostImpl* agent_host = static_cast( DevToolsAgentHost::GetOrCreateFor(web_contents).get()); if (agent_host) { for (auto* security_handler : protocol::SecurityHandler::ForAgentHost(agent_host)) { if (security_handler->NotifyCertificateError( cert_error, request_url, base::Bind(&OnAllowCertificateWithRecordDecision, false, callback))) { return; } } } GetContentClient()->browser()->AllowCertificateError( web_contents, cert_error, ssl_info, request_url, resource_type, overridable, strict_enforcement, expired_previous_decision, base::Bind(&OnAllowCertificateWithRecordDecision, true, callback)); } void SSLManager::UpdateEntry(NavigationEntryImpl* entry, int add_content_status_flags, int remove_content_status_flags) { // We don't always have a navigation entry to update, for example in the // case of the Web Inspector. if (!entry) return; SSLStatus original_ssl_status = entry->GetSSL(); // Copy! entry->GetSSL().initialized = true; entry->GetSSL().content_status |= add_content_status_flags; entry->GetSSL().content_status &= ~remove_content_status_flags; SiteInstance* site_instance = entry->site_instance(); // Note that |site_instance| can be NULL here because NavigationEntries don't // necessarily have site instances. Without a process, the entry can't // possibly have insecure content. See bug https://crbug.com/12423. if (site_instance && ssl_host_state_delegate_) { std::string host = entry->GetURL().host(); int process_id = site_instance->GetProcess()->GetID(); if (ssl_host_state_delegate_->DidHostRunInsecureContent( host, process_id, SSLHostStateDelegate::MIXED_CONTENT)) { entry->GetSSL().content_status |= SSLStatus::RAN_INSECURE_CONTENT; } // Only record information about subresources with cert errors if the // main page is HTTPS with a certificate. if (entry->GetURL().SchemeIsCryptographic() && entry->GetSSL().certificate && ssl_host_state_delegate_->DidHostRunInsecureContent( host, process_id, SSLHostStateDelegate::CERT_ERRORS_CONTENT)) { entry->GetSSL().content_status |= SSLStatus::RAN_CONTENT_WITH_CERT_ERRORS; } } if (!entry->GetSSL().Equals(original_ssl_status)) NotifyDidChangeVisibleSSLState(); } void SSLManager::UpdateLastCommittedEntry(int add_content_status_flags, int remove_content_status_flags) { NavigationEntryImpl* entry = controller_->GetLastCommittedEntry(); if (!entry) return; UpdateEntry(entry, add_content_status_flags, remove_content_status_flags); } void SSLManager::NotifyDidChangeVisibleSSLState() { WebContentsImpl* contents = static_cast(controller_->delegate()->GetWebContents()); contents->DidChangeVisibleSecurityState(); } // static void SSLManager::NotifySSLInternalStateChanged(BrowserContext* context) { SSLManagerSet* managers = static_cast(context->GetUserData(kSSLManagerKeyName)); for (std::set::iterator i = managers->get().begin(); i != managers->get().end(); ++i) { (*i)->UpdateEntry((*i)->controller()->GetLastCommittedEntry(), 0, 0); } } } // namespace content