|  | // Copyright 2014 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 "chrome/browser/ssl/ssl_error_handler.h" | 
|  |  | 
|  | #include <stdint.h> | 
|  | #include <memory> | 
|  | #include <unordered_set> | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/callback_helpers.h" | 
|  | #include "base/feature_list.h" | 
|  | #include "base/files/file_util.h" | 
|  | #include "base/lazy_instance.h" | 
|  | #include "base/macros.h" | 
|  | #include "base/memory/ptr_util.h" | 
|  | #include "base/metrics/histogram_macros.h" | 
|  | #include "base/strings/stringprintf.h" | 
|  | #include "base/time/clock.h" | 
|  | #include "base/time/time.h" | 
|  | #include "build/build_config.h" | 
|  | #include "chrome/browser/browser_process.h" | 
|  | #include "chrome/browser/profiles/profile.h" | 
|  | #include "chrome/browser/ssl/bad_clock_blocking_page.h" | 
|  | #include "chrome/browser/ssl/captive_portal_blocking_page.h" | 
|  | #include "chrome/browser/ssl/captive_portal_helper.h" | 
|  | #include "chrome/browser/ssl/mitm_software_blocking_page.h" | 
|  | #include "chrome/browser/ssl/ssl_blocking_page.h" | 
|  | #include "chrome/browser/ssl/ssl_cert_reporter.h" | 
|  | #include "chrome/browser/ssl/ssl_error_assistant.h" | 
|  | #include "chrome/common/buildflags.h" | 
|  | #include "chrome/common/pref_names.h" | 
|  | #include "components/network_time/network_time_tracker.h" | 
|  | #include "components/prefs/pref_service.h" | 
|  | #include "components/security_interstitials/core/ssl_error_ui.h" | 
|  | #include "components/ssl_errors/error_classification.h" | 
|  | #include "components/ssl_errors/error_info.h" | 
|  | #include "content/public/browser/browser_context.h" | 
|  | #include "content/public/browser/browser_thread.h" | 
|  | #include "content/public/browser/navigation_handle.h" | 
|  | #include "content/public/browser/notification_service.h" | 
|  | #include "content/public/browser/notification_source.h" | 
|  | #include "content/public/browser/render_frame_host.h" | 
|  | #include "content/public/browser/storage_partition.h" | 
|  | #include "content/public/browser/web_contents.h" | 
|  | #include "net/base/net_errors.h" | 
|  | #include "services/network/public/cpp/shared_url_loader_factory.h" | 
|  | #include "third_party/protobuf/src/google/protobuf/io/zero_copy_stream_impl_lite.h" | 
|  |  | 
|  | #if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION) | 
|  | #include "chrome/browser/captive_portal/captive_portal_service.h" | 
|  | #include "chrome/browser/captive_portal/captive_portal_service_factory.h" | 
|  | #include "chrome/browser/captive_portal/captive_portal_tab_helper.h" | 
|  | #endif | 
|  |  | 
|  | #if defined(OS_WIN) | 
|  | #include "base/win/win_util.h" | 
|  | #elif defined(OS_CHROMEOS) | 
|  | #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h" | 
|  | #endif  // #if defined(OS_WIN) | 
|  |  | 
|  | #if defined(OS_ANDROID) | 
|  | #include "base/android/jni_android.h" | 
|  | #include "chrome/browser/ssl/captive_portal_helper_android.h" | 
|  | #endif | 
|  |  | 
|  | const base::Feature kMITMSoftwareInterstitial{"MITMSoftwareInterstitial", | 
|  | base::FEATURE_ENABLED_BY_DEFAULT}; | 
|  |  | 
|  | const base::Feature kCaptivePortalInterstitial{ | 
|  | "CaptivePortalInterstitial", base::FEATURE_ENABLED_BY_DEFAULT}; | 
|  |  | 
|  | const base::Feature kCaptivePortalCertificateList{ | 
|  | "CaptivePortalCertificateList", base::FEATURE_ENABLED_BY_DEFAULT}; | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | const base::Feature kSSLCommonNameMismatchHandling{ | 
|  | "SSLCommonNameMismatchHandling", base::FEATURE_ENABLED_BY_DEFAULT}; | 
|  |  | 
|  | // Default delay in milliseconds before displaying the SSL interstitial. | 
|  | // This can be changed in tests. | 
|  | // - If there is a name mismatch and a suggested URL available result arrives | 
|  | //   during this time, the user is redirected to the suggester URL. | 
|  | // - If a "captive portal detected" result arrives during this time, | 
|  | //   a captive portal interstitial is displayed. | 
|  | // - Otherwise, an SSL interstitial is displayed. | 
|  | const int64_t kInterstitialDelayInMilliseconds = 3000; | 
|  |  | 
|  | const char kHistogram[] = "interstitial.ssl_error_handler"; | 
|  |  | 
|  | // Adds a message to console after navigation commits and then, deletes itself. | 
|  | // Also deletes itself if the navigation is stopped. | 
|  | class CommonNameMismatchRedirectObserver | 
|  | : public content::WebContentsObserver, | 
|  | public content::WebContentsUserData<CommonNameMismatchRedirectObserver> { | 
|  | public: | 
|  | ~CommonNameMismatchRedirectObserver() override {} | 
|  |  | 
|  | static void AddToConsoleAfterNavigation( | 
|  | content::WebContents* web_contents, | 
|  | const std::string& request_url_hostname, | 
|  | const std::string& suggested_url_hostname) { | 
|  | web_contents->SetUserData( | 
|  | UserDataKey(), | 
|  | base::WrapUnique(new CommonNameMismatchRedirectObserver( | 
|  | web_contents, request_url_hostname, suggested_url_hostname))); | 
|  | } | 
|  |  | 
|  | private: | 
|  | friend class content::WebContentsUserData<CommonNameMismatchRedirectObserver>; | 
|  | CommonNameMismatchRedirectObserver(content::WebContents* web_contents, | 
|  | const std::string& request_url_hostname, | 
|  | const std::string& suggested_url_hostname) | 
|  | : WebContentsObserver(web_contents), | 
|  | web_contents_(web_contents), | 
|  | request_url_hostname_(request_url_hostname), | 
|  | suggested_url_hostname_(suggested_url_hostname) {} | 
|  |  | 
|  | // WebContentsObserver: | 
|  | void NavigationStopped() override { | 
|  | // Deletes |this|. | 
|  | web_contents_->RemoveUserData(UserDataKey()); | 
|  | } | 
|  |  | 
|  | void NavigationEntryCommitted( | 
|  | const content::LoadCommittedDetails& /* load_details */) override { | 
|  | web_contents_->GetMainFrame()->AddMessageToConsole( | 
|  | content::CONSOLE_MESSAGE_LEVEL_INFO, | 
|  | base::StringPrintf( | 
|  | "Redirecting navigation %s -> %s because the server presented a " | 
|  | "certificate valid for %s but not for %s. To disable such " | 
|  | "redirects launch Chrome with the following flag: " | 
|  | "--disable-features=SSLCommonNameMismatchHandling", | 
|  | request_url_hostname_.c_str(), suggested_url_hostname_.c_str(), | 
|  | suggested_url_hostname_.c_str(), request_url_hostname_.c_str())); | 
|  | web_contents_->RemoveUserData(UserDataKey()); | 
|  | } | 
|  |  | 
|  | void WebContentsDestroyed() override { | 
|  | web_contents_->RemoveUserData(UserDataKey()); | 
|  | } | 
|  |  | 
|  | content::WebContents* web_contents_; | 
|  | const std::string request_url_hostname_; | 
|  | const std::string suggested_url_hostname_; | 
|  |  | 
|  | WEB_CONTENTS_USER_DATA_KEY_DECL(); | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(CommonNameMismatchRedirectObserver); | 
|  | }; | 
|  |  | 
|  | WEB_CONTENTS_USER_DATA_KEY_IMPL(CommonNameMismatchRedirectObserver) | 
|  |  | 
|  | void RecordUMA(SSLErrorHandler::UMAEvent event) { | 
|  | UMA_HISTOGRAM_ENUMERATION(kHistogram, event, | 
|  | SSLErrorHandler::SSL_ERROR_HANDLER_EVENT_COUNT); | 
|  | } | 
|  |  | 
|  | bool IsCaptivePortalInterstitialEnabled() { | 
|  | return base::FeatureList::IsEnabled(kCaptivePortalInterstitial); | 
|  | } | 
|  |  | 
|  | bool IsMITMSoftwareInterstitialEnabled() { | 
|  | return base::FeatureList::IsEnabled(kMITMSoftwareInterstitial); | 
|  | } | 
|  |  | 
|  | bool IsSSLCommonNameMismatchHandlingEnabled() { | 
|  | return base::FeatureList::IsEnabled(kSSLCommonNameMismatchHandling); | 
|  | } | 
|  |  | 
|  | // Configuration for SSLErrorHandler. | 
|  | class ConfigSingleton { | 
|  | public: | 
|  | ConfigSingleton(); | 
|  |  | 
|  | base::TimeDelta interstitial_delay() const; | 
|  | SSLErrorHandler::TimerStartedCallback* timer_started_callback() const; | 
|  | base::Clock* clock() const; | 
|  | network_time::NetworkTimeTracker* network_time_tracker() const; | 
|  |  | 
|  | bool IsKnownCaptivePortalCertificate(const net::SSLInfo& ssl_info); | 
|  |  | 
|  | // Returns the name of a known MITM software provider that matches the | 
|  | // certificate passed in as the |cert| parameter. Returns empty string if | 
|  | // there is no match. | 
|  | const std::string MatchKnownMITMSoftware( | 
|  | const scoped_refptr<net::X509Certificate> cert); | 
|  |  | 
|  | // Returns a DynamicInterstitialInfo that matches with |ssl_info|. If is no | 
|  | // match, return null. | 
|  | base::Optional<DynamicInterstitialInfo> MatchDynamicInterstitial( | 
|  | const net::SSLInfo& ssl_info, | 
|  | bool is_overridable); | 
|  |  | 
|  | // Testing methods: | 
|  | void ResetForTesting(); | 
|  | void SetInterstitialDelayForTesting(const base::TimeDelta& delay); | 
|  | void SetTimerStartedCallbackForTesting( | 
|  | SSLErrorHandler::TimerStartedCallback* callback); | 
|  | void SetClockForTesting(base::Clock* clock); | 
|  | void SetNetworkTimeTrackerForTesting( | 
|  | network_time::NetworkTimeTracker* tracker); | 
|  | void SetReportNetworkConnectivityCallbackForTesting( | 
|  | base::OnceClosure callback); | 
|  |  | 
|  | void SetErrorAssistantProto( | 
|  | std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig> | 
|  | error_assistant_proto); | 
|  |  | 
|  | void SetEnterpriseManagedForTesting(bool enterprise_managed); | 
|  | bool IsEnterpriseManagedFlagSetForTesting() const; | 
|  | int GetErrorAssistantProtoVersionIdForTesting() const; | 
|  |  | 
|  | bool IsEnterpriseManaged() const; | 
|  |  | 
|  | void SetOSReportsCaptivePortalForTesting(bool os_reports_captive_portal); | 
|  | bool DoesOSReportCaptivePortalForTesting() const; | 
|  |  | 
|  | base::OnceClosure report_network_connectivity_callback() { | 
|  | return std::move(report_network_connectivity_callback_); | 
|  | } | 
|  |  | 
|  | private: | 
|  | base::TimeDelta interstitial_delay_; | 
|  |  | 
|  | // Callback to call when the interstitial timer is started. Used for | 
|  | // testing. | 
|  | SSLErrorHandler::TimerStartedCallback* timer_started_callback_ = nullptr; | 
|  |  | 
|  | // The clock to use when deciding which error type to display. Used for | 
|  | // testing. | 
|  | base::Clock* testing_clock_ = nullptr; | 
|  |  | 
|  | network_time::NetworkTimeTracker* network_time_tracker_ = nullptr; | 
|  |  | 
|  | base::OnceClosure report_network_connectivity_callback_; | 
|  |  | 
|  | enum EnterpriseManaged { | 
|  | ENTERPRISE_MANAGED_STATUS_NOT_SET, | 
|  | ENTERPRISE_MANAGED_STATUS_TRUE, | 
|  | ENTERPRISE_MANAGED_STATUS_FALSE | 
|  | }; | 
|  | EnterpriseManaged is_enterprise_managed_for_testing_; | 
|  |  | 
|  | enum OSCaptivePortalStatus { | 
|  | OS_CAPTIVE_PORTAL_STATUS_NOT_SET, | 
|  | OS_CAPTIVE_PORTAL_STATUS_BEHIND_PORTAL, | 
|  | OS_CAPTIVE_PORTAL_STATUS_NOT_BEHIND_PORTAL, | 
|  | }; | 
|  | OSCaptivePortalStatus os_captive_portal_status_for_testing_; | 
|  |  | 
|  | std::unique_ptr<SSLErrorAssistant> ssl_error_assistant_; | 
|  | }; | 
|  |  | 
|  | ConfigSingleton::ConfigSingleton() | 
|  | : interstitial_delay_( | 
|  | base::TimeDelta::FromMilliseconds(kInterstitialDelayInMilliseconds)), | 
|  | is_enterprise_managed_for_testing_(ENTERPRISE_MANAGED_STATUS_NOT_SET), | 
|  | os_captive_portal_status_for_testing_(OS_CAPTIVE_PORTAL_STATUS_NOT_SET), | 
|  | ssl_error_assistant_(std::make_unique<SSLErrorAssistant>()) {} | 
|  |  | 
|  | base::TimeDelta ConfigSingleton::interstitial_delay() const { | 
|  | return interstitial_delay_; | 
|  | } | 
|  |  | 
|  | SSLErrorHandler::TimerStartedCallback* ConfigSingleton::timer_started_callback() | 
|  | const { | 
|  | return timer_started_callback_; | 
|  | } | 
|  |  | 
|  | network_time::NetworkTimeTracker* ConfigSingleton::network_time_tracker() | 
|  | const { | 
|  | return network_time_tracker_ ? network_time_tracker_ | 
|  | : g_browser_process->network_time_tracker(); | 
|  | } | 
|  |  | 
|  | base::Clock* ConfigSingleton::clock() const { | 
|  | return testing_clock_; | 
|  | } | 
|  |  | 
|  | void ConfigSingleton::ResetForTesting() { | 
|  | interstitial_delay_ = | 
|  | base::TimeDelta::FromMilliseconds(kInterstitialDelayInMilliseconds); | 
|  | timer_started_callback_ = nullptr; | 
|  | network_time_tracker_ = nullptr; | 
|  | testing_clock_ = nullptr; | 
|  | ssl_error_assistant_->ResetForTesting(); | 
|  | is_enterprise_managed_for_testing_ = ENTERPRISE_MANAGED_STATUS_NOT_SET; | 
|  | os_captive_portal_status_for_testing_ = OS_CAPTIVE_PORTAL_STATUS_NOT_SET; | 
|  | } | 
|  |  | 
|  | void ConfigSingleton::SetInterstitialDelayForTesting( | 
|  | const base::TimeDelta& delay) { | 
|  | interstitial_delay_ = delay; | 
|  | } | 
|  |  | 
|  | void ConfigSingleton::SetTimerStartedCallbackForTesting( | 
|  | SSLErrorHandler::TimerStartedCallback* callback) { | 
|  | DCHECK(!callback || !callback->is_null()); | 
|  | timer_started_callback_ = callback; | 
|  | } | 
|  |  | 
|  | void ConfigSingleton::SetClockForTesting(base::Clock* clock) { | 
|  | testing_clock_ = clock; | 
|  | } | 
|  |  | 
|  | void ConfigSingleton::SetNetworkTimeTrackerForTesting( | 
|  | network_time::NetworkTimeTracker* tracker) { | 
|  | network_time_tracker_ = tracker; | 
|  | } | 
|  |  | 
|  | void ConfigSingleton::SetReportNetworkConnectivityCallbackForTesting( | 
|  | base::OnceClosure closure) { | 
|  | report_network_connectivity_callback_ = std::move(closure); | 
|  | } | 
|  |  | 
|  | void ConfigSingleton::SetEnterpriseManagedForTesting(bool enterprise_managed) { | 
|  | if (enterprise_managed) { | 
|  | is_enterprise_managed_for_testing_ = ENTERPRISE_MANAGED_STATUS_TRUE; | 
|  | } else { | 
|  | is_enterprise_managed_for_testing_ = ENTERPRISE_MANAGED_STATUS_FALSE; | 
|  | } | 
|  | } | 
|  |  | 
|  | bool ConfigSingleton::IsEnterpriseManagedFlagSetForTesting() const { | 
|  | if (is_enterprise_managed_for_testing_ == ENTERPRISE_MANAGED_STATUS_NOT_SET) { | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | int ConfigSingleton::GetErrorAssistantProtoVersionIdForTesting() const { | 
|  | return ssl_error_assistant_->GetErrorAssistantProtoVersionIdForTesting(); | 
|  | } | 
|  |  | 
|  | bool ConfigSingleton::IsEnterpriseManaged() const { | 
|  | // Return the value of the testing flag if it's set. | 
|  | if (is_enterprise_managed_for_testing_ == ENTERPRISE_MANAGED_STATUS_TRUE) { | 
|  | return true; | 
|  | } | 
|  | if (is_enterprise_managed_for_testing_ == ENTERPRISE_MANAGED_STATUS_FALSE) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | #if defined(OS_WIN) | 
|  | if (base::win::IsEnterpriseManaged()) { | 
|  | return true; | 
|  | } | 
|  | #elif defined(OS_CHROMEOS) | 
|  | if (g_browser_process->platform_part()->browser_policy_connector_chromeos()) { | 
|  | return true; | 
|  | } | 
|  | #endif  // #if defined(OS_WIN) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | void ConfigSingleton::SetOSReportsCaptivePortalForTesting( | 
|  | bool os_reports_captive_portal) { | 
|  | os_captive_portal_status_for_testing_ = | 
|  | os_reports_captive_portal ? OS_CAPTIVE_PORTAL_STATUS_BEHIND_PORTAL | 
|  | : OS_CAPTIVE_PORTAL_STATUS_NOT_BEHIND_PORTAL; | 
|  | } | 
|  |  | 
|  | bool ConfigSingleton::DoesOSReportCaptivePortalForTesting() const { | 
|  | return os_captive_portal_status_for_testing_ == | 
|  | OS_CAPTIVE_PORTAL_STATUS_BEHIND_PORTAL; | 
|  | } | 
|  |  | 
|  | void ConfigSingleton::SetErrorAssistantProto( | 
|  | std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig> proto) { | 
|  | ssl_error_assistant_->SetErrorAssistantProto(std::move(proto)); | 
|  | } | 
|  |  | 
|  | bool ConfigSingleton::IsKnownCaptivePortalCertificate( | 
|  | const net::SSLInfo& ssl_info) { | 
|  | return ssl_error_assistant_->IsKnownCaptivePortalCertificate(ssl_info); | 
|  | } | 
|  |  | 
|  | const std::string ConfigSingleton::MatchKnownMITMSoftware( | 
|  | const scoped_refptr<net::X509Certificate> cert) { | 
|  | return ssl_error_assistant_->MatchKnownMITMSoftware(cert); | 
|  | } | 
|  |  | 
|  | base::Optional<DynamicInterstitialInfo> | 
|  | ConfigSingleton::MatchDynamicInterstitial(const net::SSLInfo& ssl_info, | 
|  | bool is_overridable) { | 
|  | return ssl_error_assistant_->MatchDynamicInterstitial(ssl_info, | 
|  | is_overridable); | 
|  | } | 
|  |  | 
|  | class SSLErrorHandlerDelegateImpl : public SSLErrorHandler::Delegate { | 
|  | public: | 
|  | SSLErrorHandlerDelegateImpl( | 
|  | content::WebContents* web_contents, | 
|  | const net::SSLInfo& ssl_info, | 
|  | Profile* const profile, | 
|  | int cert_error, | 
|  | int options_mask, | 
|  | const GURL& request_url, | 
|  | std::unique_ptr<SSLCertReporter> ssl_cert_reporter, | 
|  | const base::Callback<void(content::CertificateRequestResultType)>& | 
|  | decision_callback, | 
|  | SSLErrorHandler::BlockingPageReadyCallback blocking_page_ready_callback) | 
|  | : web_contents_(web_contents), | 
|  | ssl_info_(ssl_info), | 
|  | profile_(profile), | 
|  | cert_error_(cert_error), | 
|  | options_mask_(options_mask), | 
|  | request_url_(request_url), | 
|  | ssl_cert_reporter_(std::move(ssl_cert_reporter)), | 
|  | decision_callback_(decision_callback), | 
|  | blocking_page_ready_callback_(std::move(blocking_page_ready_callback)) { | 
|  | } | 
|  | ~SSLErrorHandlerDelegateImpl() override; | 
|  |  | 
|  | // SSLErrorHandler::Delegate methods: | 
|  | void CheckForCaptivePortal() override; | 
|  | bool DoesOSReportCaptivePortal() override; | 
|  | bool GetSuggestedUrl(const std::vector<std::string>& dns_names, | 
|  | GURL* suggested_url) const override; | 
|  | void CheckSuggestedUrl( | 
|  | const GURL& suggested_url, | 
|  | const CommonNameMismatchHandler::CheckUrlCallback& callback) override; | 
|  | void NavigateToSuggestedURL(const GURL& suggested_url) override; | 
|  | bool IsErrorOverridable() const override; | 
|  | void ShowCaptivePortalInterstitial(const GURL& landing_url) override; | 
|  | void ShowMITMSoftwareInterstitial(const std::string& mitm_software_name, | 
|  | bool is_enterprise_managed) override; | 
|  | void ShowSSLInterstitial(const GURL& support_url) override; | 
|  | void ShowBadClockInterstitial(const base::Time& now, | 
|  | ssl_errors::ClockState clock_state) override; | 
|  | void ReportNetworkConnectivity(base::OnceClosure callback) override; | 
|  |  | 
|  | private: | 
|  | // Calls the |blocking_page_ready_callback_| if it's not null, else calls | 
|  | // Show() on the given interstitial. | 
|  | void OnBlockingPageReady( | 
|  | security_interstitials::SecurityInterstitialPage* interstitial_page); | 
|  |  | 
|  | content::WebContents* web_contents_; | 
|  | const net::SSLInfo ssl_info_; | 
|  | Profile* const profile_; | 
|  | const int cert_error_; | 
|  | const int options_mask_; | 
|  | const GURL request_url_; | 
|  | std::unique_ptr<CommonNameMismatchHandler> common_name_mismatch_handler_; | 
|  | std::unique_ptr<SSLCertReporter> ssl_cert_reporter_; | 
|  | const base::Callback<void(content::CertificateRequestResultType)> | 
|  | decision_callback_; | 
|  | SSLErrorHandler::BlockingPageReadyCallback blocking_page_ready_callback_; | 
|  | }; | 
|  |  | 
|  | SSLErrorHandlerDelegateImpl::~SSLErrorHandlerDelegateImpl() { | 
|  | if (common_name_mismatch_handler_) { | 
|  | common_name_mismatch_handler_->Cancel(); | 
|  | common_name_mismatch_handler_.reset(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void SSLErrorHandlerDelegateImpl::CheckForCaptivePortal() { | 
|  | #if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION) | 
|  | CaptivePortalService* captive_portal_service = | 
|  | CaptivePortalServiceFactory::GetForProfile(profile_); | 
|  | captive_portal_service->DetectCaptivePortal(); | 
|  | #else | 
|  | NOTREACHED(); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | bool SSLErrorHandlerDelegateImpl::DoesOSReportCaptivePortal() { | 
|  | #if defined(OS_ANDROID) || defined(OS_WIN) | 
|  | return chrome::IsBehindCaptivePortal(); | 
|  | #else | 
|  | return false; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | bool SSLErrorHandlerDelegateImpl::GetSuggestedUrl( | 
|  | const std::vector<std::string>& dns_names, | 
|  | GURL* suggested_url) const { | 
|  | return CommonNameMismatchHandler::GetSuggestedUrl(request_url_, dns_names, | 
|  | suggested_url); | 
|  | } | 
|  |  | 
|  | void SSLErrorHandlerDelegateImpl::CheckSuggestedUrl( | 
|  | const GURL& suggested_url, | 
|  | const CommonNameMismatchHandler::CheckUrlCallback& callback) { | 
|  | scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory( | 
|  | content::BrowserContext::GetDefaultStoragePartition(profile_) | 
|  | ->GetURLLoaderFactoryForBrowserProcess()); | 
|  | common_name_mismatch_handler_.reset( | 
|  | new CommonNameMismatchHandler(request_url_, url_loader_factory)); | 
|  |  | 
|  | common_name_mismatch_handler_->CheckSuggestedUrl(suggested_url, callback); | 
|  | } | 
|  |  | 
|  | void SSLErrorHandlerDelegateImpl::NavigateToSuggestedURL( | 
|  | const GURL& suggested_url) { | 
|  | content::NavigationController::LoadURLParams load_params(suggested_url); | 
|  | load_params.transition_type = ui::PAGE_TRANSITION_TYPED; | 
|  | web_contents_->GetController().LoadURLWithParams(load_params); | 
|  | } | 
|  |  | 
|  | bool SSLErrorHandlerDelegateImpl::IsErrorOverridable() const { | 
|  | return SSLBlockingPage::IsOverridable(options_mask_); | 
|  | } | 
|  |  | 
|  | void SSLErrorHandlerDelegateImpl::ShowCaptivePortalInterstitial( | 
|  | const GURL& landing_url) { | 
|  | // Show captive portal blocking page. The interstitial owns the blocking page. | 
|  | OnBlockingPageReady(new CaptivePortalBlockingPage( | 
|  | web_contents_, request_url_, landing_url, std::move(ssl_cert_reporter_), | 
|  | ssl_info_, cert_error_, decision_callback_)); | 
|  | } | 
|  |  | 
|  | void SSLErrorHandlerDelegateImpl::ShowMITMSoftwareInterstitial( | 
|  | const std::string& mitm_software_name, | 
|  | bool is_enterprise_managed) { | 
|  | // Show MITM software blocking page. The interstitial owns the blocking page. | 
|  | OnBlockingPageReady(new MITMSoftwareBlockingPage( | 
|  | web_contents_, cert_error_, request_url_, std::move(ssl_cert_reporter_), | 
|  | ssl_info_, mitm_software_name, is_enterprise_managed, | 
|  | decision_callback_)); | 
|  | } | 
|  |  | 
|  | void SSLErrorHandlerDelegateImpl::ShowSSLInterstitial(const GURL& support_url) { | 
|  | // Show SSL blocking page. The interstitial owns the blocking page. | 
|  | OnBlockingPageReady(SSLBlockingPage::Create( | 
|  | web_contents_, cert_error_, ssl_info_, request_url_, options_mask_, | 
|  | base::Time::NowFromSystemTime(), support_url, | 
|  | std::move(ssl_cert_reporter_), decision_callback_)); | 
|  | } | 
|  |  | 
|  | void SSLErrorHandlerDelegateImpl::ShowBadClockInterstitial( | 
|  | const base::Time& now, | 
|  | ssl_errors::ClockState clock_state) { | 
|  | // Show bad clock page. The interstitial owns the blocking page. | 
|  | OnBlockingPageReady(new BadClockBlockingPage( | 
|  | web_contents_, cert_error_, ssl_info_, request_url_, now, clock_state, | 
|  | std::move(ssl_cert_reporter_), decision_callback_)); | 
|  | } | 
|  |  | 
|  | void SSLErrorHandlerDelegateImpl::ReportNetworkConnectivity( | 
|  | base::OnceClosure callback) { | 
|  | #if defined(OS_ANDROID) | 
|  | chrome::android::ReportNetworkConnectivity( | 
|  | base::android::AttachCurrentThread()); | 
|  | #else | 
|  | // Nothing to do on other platforms. | 
|  | #endif | 
|  | if (callback) | 
|  | std::move(callback).Run(); | 
|  | } | 
|  |  | 
|  | void SSLErrorHandlerDelegateImpl::OnBlockingPageReady( | 
|  | security_interstitials::SecurityInterstitialPage* interstitial_page) { | 
|  | if (blocking_page_ready_callback_.is_null()) { | 
|  | interstitial_page->Show(); | 
|  | } else { | 
|  | std::move(blocking_page_ready_callback_) | 
|  | .Run(std::unique_ptr<security_interstitials::SecurityInterstitialPage>( | 
|  | interstitial_page)); | 
|  | } | 
|  | } | 
|  |  | 
|  | int IsCertErrorFatal(int cert_error) { | 
|  | switch (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: | 
|  | case net::ERR_CERT_SYMANTEC_LEGACY: | 
|  | return false; | 
|  | 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: | 
|  | return true; | 
|  | default: | 
|  | NOTREACHED(); | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | static base::LazyInstance<ConfigSingleton>::Leaky g_config = | 
|  | LAZY_INSTANCE_INITIALIZER; | 
|  |  | 
|  | void SSLErrorHandler::HandleSSLError( | 
|  | content::WebContents* web_contents, | 
|  | int cert_error, | 
|  | const net::SSLInfo& ssl_info, | 
|  | const GURL& request_url, | 
|  | bool expired_previous_decision, | 
|  | std::unique_ptr<SSLCertReporter> ssl_cert_reporter, | 
|  | const base::Callback<void(content::CertificateRequestResultType)>& | 
|  | decision_callback, | 
|  | base::OnceCallback< | 
|  | void(std::unique_ptr<security_interstitials::SecurityInterstitialPage>)> | 
|  | blocking_page_ready_callback) { | 
|  | DCHECK(!FromWebContents(web_contents)); | 
|  |  | 
|  | Profile* profile = | 
|  | Profile::FromBrowserContext(web_contents->GetBrowserContext()); | 
|  |  | 
|  | // This can happen if GetBrowserContext no longer exist by the time this gets | 
|  | // called (e.g. the SSL error was in a webview that has since been destroyed), | 
|  | // if that's the case we don't need to handle the error (and will crash if we | 
|  | // attempt to). | 
|  | if (!profile) | 
|  | return; | 
|  |  | 
|  | bool hard_override_disabled = | 
|  | !profile->GetPrefs()->GetBoolean(prefs::kSSLErrorOverrideAllowed); | 
|  | int options_mask = CalculateOptionsMask(cert_error, hard_override_disabled, | 
|  | ssl_info.is_fatal_cert_error, | 
|  | expired_previous_decision); | 
|  |  | 
|  | SSLErrorHandler* error_handler = new SSLErrorHandler( | 
|  | std::unique_ptr<SSLErrorHandler::Delegate>( | 
|  | new SSLErrorHandlerDelegateImpl( | 
|  | web_contents, ssl_info, profile, cert_error, options_mask, | 
|  | request_url, std::move(ssl_cert_reporter), decision_callback, | 
|  | std::move(blocking_page_ready_callback))), | 
|  | web_contents, profile, cert_error, ssl_info, request_url, | 
|  | decision_callback); | 
|  | web_contents->SetUserData(UserDataKey(), base::WrapUnique(error_handler)); | 
|  | error_handler->StartHandlingError(); | 
|  | } | 
|  |  | 
|  | // static | 
|  | void SSLErrorHandler::ResetConfigForTesting() { | 
|  | g_config.Pointer()->ResetForTesting(); | 
|  | } | 
|  |  | 
|  | // static | 
|  | void SSLErrorHandler::SetInterstitialDelayForTesting( | 
|  | const base::TimeDelta& delay) { | 
|  | g_config.Pointer()->SetInterstitialDelayForTesting(delay); | 
|  | } | 
|  |  | 
|  | // static | 
|  | void SSLErrorHandler::SetInterstitialTimerStartedCallbackForTesting( | 
|  | TimerStartedCallback* callback) { | 
|  | g_config.Pointer()->SetTimerStartedCallbackForTesting(callback); | 
|  | } | 
|  |  | 
|  | // static | 
|  | void SSLErrorHandler::SetClockForTesting(base::Clock* testing_clock) { | 
|  | g_config.Pointer()->SetClockForTesting(testing_clock); | 
|  | } | 
|  |  | 
|  | // static | 
|  | void SSLErrorHandler::SetNetworkTimeTrackerForTesting( | 
|  | network_time::NetworkTimeTracker* tracker) { | 
|  | g_config.Pointer()->SetNetworkTimeTrackerForTesting(tracker); | 
|  | } | 
|  |  | 
|  | // static | 
|  | void SSLErrorHandler::SetReportNetworkConnectivityCallbackForTesting( | 
|  | base::OnceClosure closure) { | 
|  | g_config.Pointer()->SetReportNetworkConnectivityCallbackForTesting( | 
|  | std::move(closure)); | 
|  | } | 
|  |  | 
|  | // static | 
|  | void SSLErrorHandler::SetEnterpriseManagedForTesting(bool enterprise_managed) { | 
|  | g_config.Pointer()->SetEnterpriseManagedForTesting(enterprise_managed); | 
|  | } | 
|  |  | 
|  | // static | 
|  | bool SSLErrorHandler::IsEnterpriseManagedFlagSetForTesting() { | 
|  | return g_config.Pointer()->IsEnterpriseManagedFlagSetForTesting(); | 
|  | } | 
|  |  | 
|  | // static | 
|  | std::string SSLErrorHandler::GetHistogramNameForTesting() { | 
|  | return kHistogram; | 
|  | } | 
|  |  | 
|  | // static | 
|  | int SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting() { | 
|  | return g_config.Pointer()->GetErrorAssistantProtoVersionIdForTesting(); | 
|  | } | 
|  |  | 
|  | // static | 
|  | void SSLErrorHandler::SetOSReportsCaptivePortalForTesting( | 
|  | bool os_reports_captive_portal) { | 
|  | g_config.Pointer()->SetOSReportsCaptivePortalForTesting( | 
|  | os_reports_captive_portal); | 
|  | } | 
|  |  | 
|  | bool SSLErrorHandler::IsTimerRunningForTesting() const { | 
|  | return timer_.IsRunning(); | 
|  | } | 
|  |  | 
|  | // static | 
|  | void SSLErrorHandler::SetErrorAssistantProto( | 
|  | std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig> config_proto) { | 
|  | g_config.Pointer()->SetErrorAssistantProto(std::move(config_proto)); | 
|  | } | 
|  |  | 
|  | SSLErrorHandler::SSLErrorHandler( | 
|  | std::unique_ptr<Delegate> delegate, | 
|  | content::WebContents* web_contents, | 
|  | Profile* profile, | 
|  | int cert_error, | 
|  | const net::SSLInfo& ssl_info, | 
|  | const GURL& request_url, | 
|  | const base::Callback<void(content::CertificateRequestResultType)>& | 
|  | decision_callback) | 
|  | : content::WebContentsObserver(web_contents), | 
|  | delegate_(std::move(delegate)), | 
|  | web_contents_(web_contents), | 
|  | profile_(profile), | 
|  | cert_error_(cert_error), | 
|  | ssl_info_(ssl_info), | 
|  | request_url_(request_url), | 
|  | decision_callback_(decision_callback), | 
|  | weak_ptr_factory_(this) {} | 
|  |  | 
|  | SSLErrorHandler::~SSLErrorHandler() { | 
|  | } | 
|  |  | 
|  | void SSLErrorHandler::StartHandlingError() { | 
|  | RecordUMA(HANDLE_ALL); | 
|  |  | 
|  | if (ssl_errors::ErrorInfo::NetErrorToErrorType(cert_error_) == | 
|  | ssl_errors::ErrorInfo::CERT_DATE_INVALID) { | 
|  | HandleCertDateInvalidError(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | base::Optional<DynamicInterstitialInfo> dynamic_interstitial = | 
|  | g_config.Pointer()->MatchDynamicInterstitial( | 
|  | ssl_info_, delegate_->IsErrorOverridable()); | 
|  | if (dynamic_interstitial) { | 
|  | ShowDynamicInterstitial(dynamic_interstitial.value()); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Ideally, a captive portal interstitial should only be displayed if the only | 
|  | // SSL error is a name mismatch error. However, captive portal detector always | 
|  | // opens a new tab if it detects a portal ignoring the types of SSL errors. To | 
|  | // be consistent with captive portal detector, use the result of OS detection | 
|  | // without checking only_error_is_name_mismatch. | 
|  | if (IsCaptivePortalInterstitialEnabled() && | 
|  | (g_config.Pointer()->DoesOSReportCaptivePortalForTesting() || | 
|  | delegate_->DoesOSReportCaptivePortal())) { | 
|  | delegate_->ReportNetworkConnectivity( | 
|  | g_config.Pointer()->report_network_connectivity_callback()); | 
|  | RecordUMA(OS_REPORTS_CAPTIVE_PORTAL); | 
|  | ShowCaptivePortalInterstitial(GURL()); | 
|  | return; | 
|  | } | 
|  |  | 
|  | const bool only_error_is_name_mismatch = | 
|  | IsOnlyCertError(net::CERT_STATUS_COMMON_NAME_INVALID); | 
|  |  | 
|  | // Check known captive portal certificate list if the only error is | 
|  | // name-mismatch. If there are multiple errors, it indicates that the captive | 
|  | // portal landing page itself will have SSL errors, and so it's not a very | 
|  | // helpful place to direct the user to go. | 
|  | if (only_error_is_name_mismatch) { | 
|  | delegate_->ReportNetworkConnectivity( | 
|  | g_config.Pointer()->report_network_connectivity_callback()); | 
|  |  | 
|  | if (base::FeatureList::IsEnabled(kCaptivePortalCertificateList) && | 
|  | g_config.Pointer()->IsKnownCaptivePortalCertificate(ssl_info_)) { | 
|  | RecordUMA(CAPTIVE_PORTAL_CERT_FOUND); | 
|  | ShowCaptivePortalInterstitial(GURL()); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | // The MITM software interstitial is displayed if and only if: | 
|  | // - the error thrown is not overridable | 
|  | // - the only certificate error is CERT_STATUS_AUTHORITY_INVALID | 
|  | // - the certificate contains a string that indicates it was issued by a | 
|  | //   MITM software | 
|  | if (IsMITMSoftwareInterstitialEnabled() && !delegate_->IsErrorOverridable() && | 
|  | IsOnlyCertError(net::CERT_STATUS_AUTHORITY_INVALID)) { | 
|  | const std::string found_mitm_software = | 
|  | g_config.Pointer()->MatchKnownMITMSoftware(ssl_info_.cert); | 
|  | if (!found_mitm_software.empty()) { | 
|  | ShowMITMSoftwareInterstitial(found_mitm_software, | 
|  | g_config.Pointer()->IsEnterpriseManaged()); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (IsSSLCommonNameMismatchHandlingEnabled() && | 
|  | cert_error_ == net::ERR_CERT_COMMON_NAME_INVALID && | 
|  | delegate_->IsErrorOverridable()) { | 
|  | std::vector<std::string> dns_names; | 
|  | ssl_info_.cert->GetSubjectAltName(&dns_names, nullptr); | 
|  | GURL suggested_url; | 
|  | if (!dns_names.empty() && | 
|  | delegate_->GetSuggestedUrl(dns_names, &suggested_url)) { | 
|  | RecordUMA(WWW_MISMATCH_FOUND_IN_SAN); | 
|  |  | 
|  | // Show the SSL interstitial if |CERT_STATUS_COMMON_NAME_INVALID| is not | 
|  | // the only error. Need not check for captive portal in this case. | 
|  | // (See the comment below). | 
|  | if (!only_error_is_name_mismatch) { | 
|  | ShowSSLInterstitial(); | 
|  | return; | 
|  | } | 
|  | delegate_->CheckSuggestedUrl( | 
|  | suggested_url, | 
|  | base::Bind(&SSLErrorHandler::CommonNameMismatchHandlerCallback, | 
|  | weak_ptr_factory_.GetWeakPtr())); | 
|  | timer_.Start(FROM_HERE, g_config.Pointer()->interstitial_delay(), this, | 
|  | &SSLErrorHandler::ShowSSLInterstitial); | 
|  |  | 
|  | if (g_config.Pointer()->timer_started_callback()) | 
|  | g_config.Pointer()->timer_started_callback()->Run(web_contents_); | 
|  |  | 
|  | // Do not check for a captive portal in this case, because a captive | 
|  | // portal most likely cannot serve a valid certificate which passes the | 
|  | // similarity check. | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Always listen to captive portal notifications, otherwise build fails | 
|  | // because profile_ isn't used. This is a no-op on platforms where | 
|  | // captive portal detection is disabled. | 
|  | registrar_.Add(this, chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT, | 
|  | content::Source<Profile>(profile_)); | 
|  |  | 
|  | #if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION) | 
|  | CaptivePortalTabHelper* captive_portal_tab_helper = | 
|  | CaptivePortalTabHelper::FromWebContents(web_contents_); | 
|  | if (captive_portal_tab_helper) { | 
|  | captive_portal_tab_helper->OnSSLCertError(ssl_info_); | 
|  | } | 
|  |  | 
|  | if (IsCaptivePortalInterstitialEnabled()) { | 
|  | delegate_->CheckForCaptivePortal(); | 
|  | timer_.Start(FROM_HERE, g_config.Pointer()->interstitial_delay(), this, | 
|  | &SSLErrorHandler::ShowSSLInterstitial); | 
|  | if (g_config.Pointer()->timer_started_callback()) | 
|  | g_config.Pointer()->timer_started_callback()->Run(web_contents_); | 
|  | return; | 
|  | } | 
|  | #endif | 
|  | // Display an SSL interstitial. | 
|  | ShowSSLInterstitial(); | 
|  | } | 
|  |  | 
|  | void SSLErrorHandler::ShowCaptivePortalInterstitial(const GURL& landing_url) { | 
|  | // Show captive portal blocking page. The interstitial owns the blocking page. | 
|  | RecordUMA(delegate_->IsErrorOverridable() | 
|  | ? SHOW_CAPTIVE_PORTAL_INTERSTITIAL_OVERRIDABLE | 
|  | : SHOW_CAPTIVE_PORTAL_INTERSTITIAL_NONOVERRIDABLE); | 
|  | delegate_->ShowCaptivePortalInterstitial(landing_url); | 
|  |  | 
|  | // Once an interstitial is displayed, no need to keep the handler around. | 
|  | // This is the equivalent of "delete this". It also destroys the timer. | 
|  | web_contents_->RemoveUserData(UserDataKey()); | 
|  | } | 
|  |  | 
|  | void SSLErrorHandler::ShowMITMSoftwareInterstitial( | 
|  | const std::string& mitm_software_name, | 
|  | bool is_enterprise_managed) { | 
|  | // Show SSL blocking page. The interstitial owns the blocking page. | 
|  | RecordUMA(SHOW_MITM_SOFTWARE_INTERSTITIAL); | 
|  | delegate_->ShowMITMSoftwareInterstitial(mitm_software_name, | 
|  | is_enterprise_managed); | 
|  | // Once an interstitial is displayed, no need to keep the handler around. | 
|  | // This is the equivalent of "delete this". | 
|  | web_contents_->RemoveUserData(UserDataKey()); | 
|  | } | 
|  |  | 
|  | void SSLErrorHandler::ShowSSLInterstitial() { | 
|  | // Show SSL blocking page. The interstitial owns the blocking page. | 
|  | RecordUMA(delegate_->IsErrorOverridable() | 
|  | ? SHOW_SSL_INTERSTITIAL_OVERRIDABLE | 
|  | : SHOW_SSL_INTERSTITIAL_NONOVERRIDABLE); | 
|  | delegate_->ShowSSLInterstitial(GURL()); | 
|  | // Once an interstitial is displayed, no need to keep the handler around. | 
|  | // This is the equivalent of "delete this". | 
|  | web_contents_->RemoveUserData(UserDataKey()); | 
|  | } | 
|  |  | 
|  | void SSLErrorHandler::ShowBadClockInterstitial( | 
|  | const base::Time& now, | 
|  | ssl_errors::ClockState clock_state) { | 
|  | RecordUMA(SHOW_BAD_CLOCK); | 
|  | delegate_->ShowBadClockInterstitial(now, clock_state); | 
|  | // Once an interstitial is displayed, no need to keep the handler around. | 
|  | // This is the equivalent of "delete this". | 
|  | web_contents_->RemoveUserData(UserDataKey()); | 
|  | } | 
|  |  | 
|  | void SSLErrorHandler::ShowDynamicInterstitial( | 
|  | const DynamicInterstitialInfo dynamic_interstitial) { | 
|  | switch (dynamic_interstitial.interstitial_type) { | 
|  | case chrome_browser_ssl::DynamicInterstitial::INTERSTITIAL_PAGE_NONE: | 
|  | NOTREACHED(); | 
|  | return; | 
|  | case chrome_browser_ssl::DynamicInterstitial::INTERSTITIAL_PAGE_SSL: | 
|  | delegate_->ShowSSLInterstitial(dynamic_interstitial.support_url); | 
|  | return; | 
|  | case chrome_browser_ssl::DynamicInterstitial:: | 
|  | INTERSTITIAL_PAGE_CAPTIVE_PORTAL: | 
|  | delegate_->ShowCaptivePortalInterstitial(GURL()); | 
|  | return; | 
|  | case chrome_browser_ssl::DynamicInterstitial:: | 
|  | INTERSTITIAL_PAGE_MITM_SOFTWARE: | 
|  | DCHECK(!dynamic_interstitial.mitm_software_name.empty()); | 
|  | delegate_->ShowMITMSoftwareInterstitial( | 
|  | dynamic_interstitial.mitm_software_name, | 
|  | g_config.Pointer()->IsEnterpriseManaged()); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | void SSLErrorHandler::CommonNameMismatchHandlerCallback( | 
|  | CommonNameMismatchHandler::SuggestedUrlCheckResult result, | 
|  | const GURL& suggested_url) { | 
|  | timer_.Stop(); | 
|  | if (result == CommonNameMismatchHandler::SuggestedUrlCheckResult:: | 
|  | SUGGESTED_URL_AVAILABLE) { | 
|  | RecordUMA(WWW_MISMATCH_URL_AVAILABLE); | 
|  | CommonNameMismatchRedirectObserver::AddToConsoleAfterNavigation( | 
|  | web_contents(), request_url_.host(), suggested_url.host()); | 
|  | delegate_->NavigateToSuggestedURL(suggested_url); | 
|  | } else { | 
|  | RecordUMA(WWW_MISMATCH_URL_NOT_AVAILABLE); | 
|  | ShowSSLInterstitial(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void SSLErrorHandler::Observe( | 
|  | int type, | 
|  | const content::NotificationSource& source, | 
|  | const content::NotificationDetails& details) { | 
|  | #if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION) | 
|  | DCHECK_EQ(chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT, type); | 
|  |  | 
|  | timer_.Stop(); | 
|  | CaptivePortalService::Results* results = | 
|  | content::Details<CaptivePortalService::Results>(details).ptr(); | 
|  | if (results->result == captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL) | 
|  | ShowCaptivePortalInterstitial(results->landing_url); | 
|  | else | 
|  | ShowSSLInterstitial(); | 
|  | #else | 
|  | NOTREACHED(); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | void SSLErrorHandler::DidStartNavigation( | 
|  | content::NavigationHandle* navigation_handle) { | 
|  | if (!navigation_handle->IsInMainFrame() || | 
|  | navigation_handle->IsSameDocument()) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Destroy the error handler on all new navigations. This ensures that the | 
|  | // handler is properly recreated when a hanging page is navigated to an SSL | 
|  | // error, even when the tab's WebContents doesn't change. | 
|  | DeleteSSLErrorHandler(); | 
|  | } | 
|  |  | 
|  | void SSLErrorHandler::NavigationStopped() { | 
|  | // Destroy the error handler when the page load is stopped. | 
|  | DeleteSSLErrorHandler(); | 
|  | } | 
|  |  | 
|  | void SSLErrorHandler::DeleteSSLErrorHandler() { | 
|  | // Need to explicity deny the certificate via the callback, otherwise memory | 
|  | // is leaked. | 
|  | if (!decision_callback_.is_null()) { | 
|  | base::ResetAndReturn(&decision_callback_) | 
|  | .Run(content::CERTIFICATE_REQUEST_RESULT_TYPE_DENY); | 
|  | } | 
|  | delegate_.reset(); | 
|  | // Deletes |this| and also destroys the timer. | 
|  | web_contents_->RemoveUserData(UserDataKey()); | 
|  | } | 
|  |  | 
|  | void SSLErrorHandler::HandleCertDateInvalidError() { | 
|  | const base::TimeTicks now = base::TimeTicks::Now(); | 
|  | timer_.Start(FROM_HERE, g_config.Pointer()->interstitial_delay(), | 
|  | base::Bind(&SSLErrorHandler::HandleCertDateInvalidErrorImpl, | 
|  | base::Unretained(this), now)); | 
|  | // Try kicking off a time fetch to get an up-to-date estimate of the | 
|  | // true time. This will only have an effect if network time is | 
|  | // unavailable or if there is not already a query in progress. | 
|  | // | 
|  | // Pass a weak pointer as the callback; if the timer fires before the | 
|  | // fetch completes and shows an interstitial, this SSLErrorHandler | 
|  | // will be deleted. | 
|  | network_time::NetworkTimeTracker* tracker = | 
|  | g_config.Pointer()->network_time_tracker(); | 
|  | if (!tracker->StartTimeFetch( | 
|  | base::Bind(&SSLErrorHandler::HandleCertDateInvalidErrorImpl, | 
|  | weak_ptr_factory_.GetWeakPtr(), now))) { | 
|  | HandleCertDateInvalidErrorImpl(now); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (g_config.Pointer()->timer_started_callback()) | 
|  | g_config.Pointer()->timer_started_callback()->Run(web_contents_); | 
|  | } | 
|  |  | 
|  | void SSLErrorHandler::HandleCertDateInvalidErrorImpl( | 
|  | base::TimeTicks started_handling_error) { | 
|  | UMA_HISTOGRAM_CUSTOM_TIMES( | 
|  | "interstitial.ssl_error_handler.cert_date_error_delay", | 
|  | base::TimeTicks::Now() - started_handling_error, | 
|  | base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromSeconds(4), | 
|  | 50); | 
|  |  | 
|  | timer_.Stop(); | 
|  | base::Clock* testing_clock = g_config.Pointer()->clock(); | 
|  | const base::Time now = | 
|  | testing_clock ? testing_clock->Now() : base::Time::NowFromSystemTime(); | 
|  |  | 
|  | network_time::NetworkTimeTracker* tracker = | 
|  | g_config.Pointer()->network_time_tracker(); | 
|  | ssl_errors::ClockState clock_state = ssl_errors::GetClockState(now, tracker); | 
|  | if (clock_state == ssl_errors::CLOCK_STATE_FUTURE || | 
|  | clock_state == ssl_errors::CLOCK_STATE_PAST) { | 
|  | ShowBadClockInterstitial(now, clock_state); | 
|  | return;  // |this| is deleted after showing the interstitial. | 
|  | } | 
|  | ShowSSLInterstitial(); | 
|  | } | 
|  |  | 
|  | // Returns true if |only_cert_error_expected| is the only error code present in | 
|  | // the certificate. The parameter |only_cert_error_expected| is a | 
|  | // net::CertStatus code representing the most serious error identified on the | 
|  | // certificate. For example, this could be net::CERT_STATUS_COMMON_NAME_INVALID. | 
|  | // This function is useful for rendering interstitials that are triggered by one | 
|  | // specific error code only. | 
|  | bool SSLErrorHandler::IsOnlyCertError( | 
|  | net::CertStatus only_cert_error_expected) const { | 
|  | const net::CertStatus other_errors = | 
|  | ssl_info_.cert_status ^ only_cert_error_expected; | 
|  |  | 
|  | return cert_error_ == | 
|  | net::MapCertStatusToNetError(only_cert_error_expected) && | 
|  | (!net::IsCertStatusError(other_errors) || | 
|  | net::IsCertStatusMinorError(ssl_info_.cert_status)); | 
|  | } | 
|  |  | 
|  | // static | 
|  | int SSLErrorHandler::CalculateOptionsMask(int cert_error, | 
|  | bool hard_override_disabled, | 
|  | bool should_ssl_errors_be_fatal, | 
|  | bool expired_previous_decision) { | 
|  | int options_mask = 0; | 
|  | if (!IsCertErrorFatal(cert_error) && !hard_override_disabled && | 
|  | !should_ssl_errors_be_fatal) { | 
|  | options_mask |= security_interstitials::SSLErrorUI::SOFT_OVERRIDE_ENABLED; | 
|  | } | 
|  | if (hard_override_disabled) { | 
|  | options_mask |= security_interstitials::SSLErrorUI::HARD_OVERRIDE_DISABLED; | 
|  | } | 
|  | if (should_ssl_errors_be_fatal) { | 
|  | options_mask |= security_interstitials::SSLErrorUI::STRICT_ENFORCEMENT; | 
|  | } | 
|  | if (expired_previous_decision) { | 
|  | options_mask |= | 
|  | security_interstitials::SSLErrorUI::EXPIRED_BUT_PREVIOUSLY_ALLOWED; | 
|  | } | 
|  | return options_mask; | 
|  | } | 
|  |  | 
|  | WEB_CONTENTS_USER_DATA_KEY_IMPL(SSLErrorHandler) |