meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 1 | // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "chrome/browser/ssl/ssl_error_handler.h" |
| 6 | |
avi | 664c07b | 2015-12-26 02:18:31 | [diff] [blame] | 7 | #include <stdint.h> |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 8 | #include <unordered_set> |
dcheng | e73d8520c | 2015-12-27 01:19:09 | [diff] [blame] | 9 | #include <utility> |
avi | 664c07b | 2015-12-26 02:18:31 | [diff] [blame] | 10 | |
meacer | d2c91b4 | 2015-03-20 18:34:40 | [diff] [blame] | 11 | #include "base/callback_helpers.h" |
meacer | 8016685 | 2016-12-08 01:51:36 | [diff] [blame] | 12 | #include "base/feature_list.h" |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 13 | #include "base/files/file_util.h" |
meacer | 352dc2e | 2017-01-10 01:27:41 | [diff] [blame] | 14 | #include "base/lazy_instance.h" |
avi | 664c07b | 2015-12-26 02:18:31 | [diff] [blame] | 15 | #include "base/macros.h" |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 16 | #include "base/memory/ptr_util.h" |
asvitkine | aa06031 | 2016-09-01 22:44:13 | [diff] [blame] | 17 | #include "base/metrics/histogram_macros.h" |
bhanudev | d6bd778 | 2015-08-21 00:01:54 | [diff] [blame] | 18 | #include "base/strings/stringprintf.h" |
felt | 534cc9e7 | 2015-09-01 20:38:59 | [diff] [blame] | 19 | #include "base/time/clock.h" |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 20 | #include "base/time/time.h" |
Emily Stark | f896a37 | 2017-07-26 00:08:24 | [diff] [blame] | 21 | #include "build/build_config.h" |
mab | 740b02b | 2016-03-17 23:44:21 | [diff] [blame] | 22 | #include "chrome/browser/browser_process.h" |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 23 | #include "chrome/browser/profiles/profile.h" |
felt | 534cc9e7 | 2015-09-01 20:38:59 | [diff] [blame] | 24 | #include "chrome/browser/ssl/bad_clock_blocking_page.h" |
Mustafa Emre Acer | 12b5effe | 2017-09-26 19:26:35 | [diff] [blame^] | 25 | #include "chrome/browser/ssl/captive_portal_blocking_page.h" |
Mustafa Acer | ac3e881 | 2017-09-11 20:31:01 | [diff] [blame] | 26 | #include "chrome/browser/ssl/mitm_software_blocking_page.h" |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 27 | #include "chrome/browser/ssl/ssl_blocking_page.h" |
estark | 3ba1113 | 2015-04-10 01:38:33 | [diff] [blame] | 28 | #include "chrome/browser/ssl/ssl_cert_reporter.h" |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 29 | #include "chrome/browser/ssl/ssl_error_assistant.pb.h" |
brettw | ab78fef | 2016-10-12 02:56:05 | [diff] [blame] | 30 | #include "chrome/common/features.h" |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 31 | #include "chrome/grit/browser_resources.h" |
estark | 5057f82 | 2016-11-08 21:34:54 | [diff] [blame] | 32 | #include "components/network_time/network_time_tracker.h" |
felt | 70127b4 | 2015-11-10 21:57:23 | [diff] [blame] | 33 | #include "components/ssl_errors/error_classification.h" |
felt | 2493b445 | 2015-09-17 20:33:59 | [diff] [blame] | 34 | #include "components/ssl_errors/error_info.h" |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 35 | #include "content/public/browser/browser_thread.h" |
jam | 6625f12 | 2017-01-20 06:37:33 | [diff] [blame] | 36 | #include "content/public/browser/navigation_handle.h" |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 37 | #include "content/public/browser/notification_service.h" |
| 38 | #include "content/public/browser/notification_source.h" |
bhanudev | d6bd778 | 2015-08-21 00:01:54 | [diff] [blame] | 39 | #include "content/public/browser/render_frame_host.h" |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 40 | #include "content/public/browser/web_contents.h" |
bhanudev | 3999e37 | 2015-08-21 07:23:10 | [diff] [blame] | 41 | #include "net/base/net_errors.h" |
Sasha Perigo | 2bfbbe90 | 2017-08-10 21:11:03 | [diff] [blame] | 42 | #include "third_party/protobuf/src/google/protobuf/io/zero_copy_stream_impl_lite.h" |
Mustafa Acer | ac3e881 | 2017-09-11 20:31:01 | [diff] [blame] | 43 | #include "third_party/re2/src/re2/re2.h" |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 44 | #include "ui/base/resource/resource_bundle.h" |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 45 | |
brettw | ab78fef | 2016-10-12 02:56:05 | [diff] [blame] | 46 | #if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION) |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 47 | #include "chrome/browser/captive_portal/captive_portal_service.h" |
| 48 | #include "chrome/browser/captive_portal/captive_portal_service_factory.h" |
| 49 | #include "chrome/browser/captive_portal/captive_portal_tab_helper.h" |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 50 | #endif |
| 51 | |
Sasha Perigo | 232e21aa | 2017-08-19 01:45:40 | [diff] [blame] | 52 | #if defined(OS_WIN) |
| 53 | #include "base/win/win_util.h" |
| 54 | #elif defined(OS_CHROMEOS) |
| 55 | #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h" |
| 56 | #endif // #if defined(OS_WIN) |
| 57 | |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 58 | namespace { |
| 59 | |
Sasha Perigo | 2bfbbe90 | 2017-08-10 21:11:03 | [diff] [blame] | 60 | const base::Feature kMITMSoftwareInterstitial{ |
| 61 | "MITMSoftwareInterstitial", base::FEATURE_DISABLED_BY_DEFAULT}; |
Sasha Perigo | 2bfbbe90 | 2017-08-10 21:11:03 | [diff] [blame] | 62 | |
meacer | 8016685 | 2016-12-08 01:51:36 | [diff] [blame] | 63 | #if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION) |
| 64 | const base::Feature kCaptivePortalInterstitial{ |
| 65 | "CaptivePortalInterstitial", base::FEATURE_ENABLED_BY_DEFAULT}; |
Mustafa Emre Acer | 12b5effe | 2017-09-26 19:26:35 | [diff] [blame^] | 66 | #endif |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 67 | |
| 68 | const base::Feature kCaptivePortalCertificateList{ |
| 69 | "CaptivePortalCertificateList", base::FEATURE_DISABLED_BY_DEFAULT}; |
meacer | 8016685 | 2016-12-08 01:51:36 | [diff] [blame] | 70 | |
meacer | 93c0f80 | 2016-12-08 19:55:13 | [diff] [blame] | 71 | const base::Feature kSSLCommonNameMismatchHandling{ |
| 72 | "SSLCommonNameMismatchHandling", base::FEATURE_ENABLED_BY_DEFAULT}; |
| 73 | |
Emily Stark | f896a37 | 2017-07-26 00:08:24 | [diff] [blame] | 74 | #if defined(OS_WIN) |
| 75 | const base::Feature kSuperfishInterstitial{"SuperfishInterstitial", |
| 76 | base::FEATURE_ENABLED_BY_DEFAULT}; |
| 77 | #else |
estark | 5cbbc09 | 2017-06-23 02:00:56 | [diff] [blame] | 78 | const base::Feature kSuperfishInterstitial{"SuperfishInterstitial", |
| 79 | base::FEATURE_DISABLED_BY_DEFAULT}; |
Emily Stark | f896a37 | 2017-07-26 00:08:24 | [diff] [blame] | 80 | #endif |
estark | 5cbbc09 | 2017-06-23 02:00:56 | [diff] [blame] | 81 | |
meacer | 352dc2e | 2017-01-10 01:27:41 | [diff] [blame] | 82 | // Default delay in milliseconds before displaying the SSL interstitial. |
meacer | 8b4cea2 | 2015-08-28 02:02:55 | [diff] [blame] | 83 | // This can be changed in tests. |
| 84 | // - If there is a name mismatch and a suggested URL available result arrives |
| 85 | // during this time, the user is redirected to the suggester URL. |
| 86 | // - If a "captive portal detected" result arrives during this time, |
| 87 | // a captive portal interstitial is displayed. |
| 88 | // - Otherwise, an SSL interstitial is displayed. |
meacer | 352dc2e | 2017-01-10 01:27:41 | [diff] [blame] | 89 | const int64_t kInterstitialDelayInMilliseconds = 3000; |
estark | 5057f82 | 2016-11-08 21:34:54 | [diff] [blame] | 90 | |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 91 | const char kHistogram[] = "interstitial.ssl_error_handler"; |
| 92 | |
estark | 5cbbc09 | 2017-06-23 02:00:56 | [diff] [blame] | 93 | bool IsSuperfish(const scoped_refptr<net::X509Certificate>& cert) { |
| 94 | // This is the fingerprint of the well-known Superfish certificate at |
| 95 | // https://pastebin.com/WcXv8QcG. Superfish is identified by certificate |
| 96 | // fingerprint rather than SPKI because net::SSLInfo does not guarantee |
| 97 | // |public_key_hashes| (the SPKIs) to be populated if the certificate doesn't |
| 98 | // verify successfully. It so happens that Superfish uses the same certificate |
| 99 | // universally (not just the same public key), and calculating the fingerprint |
| 100 | // is more convenient here than calculating the SPKI. |
| 101 | const net::SHA256HashValue kSuperfishFingerprint{ |
| 102 | {0xB6, 0xFE, 0x91, 0x51, 0x40, 0x2B, 0xAD, 0x1C, 0x06, 0xD7, 0xE6, |
| 103 | 0x6D, 0xB6, 0x7A, 0x26, 0xAA, 0x73, 0x56, 0xF2, 0xE6, 0xC6, 0x44, |
| 104 | 0xDB, 0xCF, 0x9F, 0x98, 0x96, 0x8F, 0xF6, 0x32, 0xE1, 0xB7}}; |
| 105 | for (const net::X509Certificate::OSCertHandle& intermediate : |
| 106 | cert->GetIntermediateCertificates()) { |
| 107 | net::SHA256HashValue hash = |
| 108 | net::X509Certificate::CalculateFingerprint256(intermediate); |
| 109 | if (hash == kSuperfishFingerprint) { |
| 110 | return true; |
estark | 6767d1b | 2017-06-03 17:37:20 | [diff] [blame] | 111 | } |
| 112 | } |
estark | 5cbbc09 | 2017-06-23 02:00:56 | [diff] [blame] | 113 | return false; |
| 114 | } |
| 115 | |
| 116 | // Records an UMA histogram for whether the Superfish certificate was present in |
| 117 | // the certificate chain. |
| 118 | void RecordSuperfishUMA(const scoped_refptr<net::X509Certificate>& cert) { |
estark | 6767d1b | 2017-06-03 17:37:20 | [diff] [blame] | 119 | UMA_HISTOGRAM_BOOLEAN("interstitial.ssl_error_handler.superfish", |
estark | 5cbbc09 | 2017-06-23 02:00:56 | [diff] [blame] | 120 | IsSuperfish(cert)); |
estark | 6767d1b | 2017-06-03 17:37:20 | [diff] [blame] | 121 | } |
| 122 | |
meacer | 8b4cea2 | 2015-08-28 02:02:55 | [diff] [blame] | 123 | // Adds a message to console after navigation commits and then, deletes itself. |
| 124 | // Also deletes itself if the navigation is stopped. |
| 125 | class CommonNameMismatchRedirectObserver |
| 126 | : public content::WebContentsObserver, |
| 127 | public content::WebContentsUserData<CommonNameMismatchRedirectObserver> { |
| 128 | public: |
avi | 284ec61 | 2017-05-03 01:52:23 | [diff] [blame] | 129 | ~CommonNameMismatchRedirectObserver() override {} |
| 130 | |
meacer | 8b4cea2 | 2015-08-28 02:02:55 | [diff] [blame] | 131 | static void AddToConsoleAfterNavigation( |
| 132 | content::WebContents* web_contents, |
| 133 | const std::string& request_url_hostname, |
| 134 | const std::string& suggested_url_hostname) { |
| 135 | web_contents->SetUserData( |
| 136 | UserDataKey(), |
avi | 284ec61 | 2017-05-03 01:52:23 | [diff] [blame] | 137 | base::WrapUnique(new CommonNameMismatchRedirectObserver( |
| 138 | web_contents, request_url_hostname, suggested_url_hostname))); |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 139 | } |
meacer | 8b4cea2 | 2015-08-28 02:02:55 | [diff] [blame] | 140 | |
| 141 | private: |
| 142 | CommonNameMismatchRedirectObserver(content::WebContents* web_contents, |
| 143 | const std::string& request_url_hostname, |
| 144 | const std::string& suggested_url_hostname) |
| 145 | : WebContentsObserver(web_contents), |
| 146 | web_contents_(web_contents), |
| 147 | request_url_hostname_(request_url_hostname), |
| 148 | suggested_url_hostname_(suggested_url_hostname) {} |
meacer | 8b4cea2 | 2015-08-28 02:02:55 | [diff] [blame] | 149 | |
| 150 | // WebContentsObserver: |
| 151 | void NavigationStopped() override { |
| 152 | // Deletes |this|. |
| 153 | web_contents_->RemoveUserData(UserDataKey()); |
| 154 | } |
| 155 | |
| 156 | void NavigationEntryCommitted( |
| 157 | const content::LoadCommittedDetails& /* load_details */) override { |
| 158 | web_contents_->GetMainFrame()->AddMessageToConsole( |
pfeldman | 2bcbc122 | 2017-01-21 06:08:54 | [diff] [blame] | 159 | content::CONSOLE_MESSAGE_LEVEL_INFO, |
meacer | 8b4cea2 | 2015-08-28 02:02:55 | [diff] [blame] | 160 | base::StringPrintf( |
| 161 | "Redirecting navigation %s -> %s because the server presented a " |
meacer | ee63a4e | 2016-03-30 23:03:19 | [diff] [blame] | 162 | "certificate valid for %s but not for %s. To disable such " |
| 163 | "redirects launch Chrome with the following flag: " |
meacer | 93c0f80 | 2016-12-08 19:55:13 | [diff] [blame] | 164 | "--disable-features=SSLCommonNameMismatchHandling", |
meacer | 8b4cea2 | 2015-08-28 02:02:55 | [diff] [blame] | 165 | request_url_hostname_.c_str(), suggested_url_hostname_.c_str(), |
| 166 | suggested_url_hostname_.c_str(), request_url_hostname_.c_str())); |
| 167 | web_contents_->RemoveUserData(UserDataKey()); |
| 168 | } |
| 169 | |
| 170 | void WebContentsDestroyed() override { |
| 171 | web_contents_->RemoveUserData(UserDataKey()); |
| 172 | } |
| 173 | |
| 174 | content::WebContents* web_contents_; |
| 175 | const std::string request_url_hostname_; |
| 176 | const std::string suggested_url_hostname_; |
| 177 | |
| 178 | DISALLOW_COPY_AND_ASSIGN(CommonNameMismatchRedirectObserver); |
| 179 | }; |
| 180 | |
meacer | c7693c79 | 2017-02-01 01:42:58 | [diff] [blame] | 181 | void RecordUMA(SSLErrorHandler::UMAEvent event) { |
| 182 | UMA_HISTOGRAM_ENUMERATION(kHistogram, event, |
| 183 | SSLErrorHandler::SSL_ERROR_HANDLER_EVENT_COUNT); |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 184 | } |
meacer | 5c77768 | 2015-01-09 06:54:53 | [diff] [blame] | 185 | |
brettw | ab78fef | 2016-10-12 02:56:05 | [diff] [blame] | 186 | #if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION) |
meacer | 5c77768 | 2015-01-09 06:54:53 | [diff] [blame] | 187 | bool IsCaptivePortalInterstitialEnabled() { |
meacer | 8016685 | 2016-12-08 01:51:36 | [diff] [blame] | 188 | return base::FeatureList::IsEnabled(kCaptivePortalInterstitial); |
meacer | 5c77768 | 2015-01-09 06:54:53 | [diff] [blame] | 189 | } |
Mustafa Emre Acer | 12b5effe | 2017-09-26 19:26:35 | [diff] [blame^] | 190 | #endif |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 191 | |
Sasha Perigo | 2bfbbe90 | 2017-08-10 21:11:03 | [diff] [blame] | 192 | std::unique_ptr<std::unordered_set<std::string>> LoadCaptivePortalCertHashes( |
| 193 | const chrome_browser_ssl::SSLErrorAssistantConfig& proto) { |
| 194 | auto hashes = base::MakeUnique<std::unordered_set<std::string>>(); |
| 195 | for (const chrome_browser_ssl::CaptivePortalCert& cert : |
| 196 | proto.captive_portal_cert()) { |
| 197 | hashes.get()->insert(cert.sha256_hash()); |
| 198 | } |
| 199 | return hashes; |
| 200 | } |
Sasha Perigo | 2bfbbe90 | 2017-08-10 21:11:03 | [diff] [blame] | 201 | |
Sasha Perigo | 2bfbbe90 | 2017-08-10 21:11:03 | [diff] [blame] | 202 | bool IsMITMSoftwareInterstitialEnabled() { |
| 203 | return base::FeatureList::IsEnabled(kMITMSoftwareInterstitial); |
| 204 | } |
| 205 | |
Sasha Perigo | bc595cf | 2017-08-30 19:13:48 | [diff] [blame] | 206 | // Struct which stores data about a known MITM software pulled from the |
| 207 | // SSLErrorAssistant proto. |
| 208 | struct MITMSoftwareType { |
| 209 | MITMSoftwareType(const std::string& name, |
| 210 | const std::string& issuer_common_name_regex, |
| 211 | const std::string& issuer_organization_regex) |
| 212 | : name(name), |
| 213 | issuer_common_name_regex(issuer_common_name_regex), |
| 214 | issuer_organization_regex(issuer_organization_regex) {} |
| 215 | |
| 216 | const std::string name; |
| 217 | const std::string issuer_common_name_regex; |
| 218 | const std::string issuer_organization_regex; |
| 219 | }; |
| 220 | |
| 221 | std::unique_ptr<std::vector<MITMSoftwareType>> LoadMITMSoftwareList( |
| 222 | const chrome_browser_ssl::SSLErrorAssistantConfig& proto) { |
| 223 | auto mitm_software_list = base::MakeUnique<std::vector<MITMSoftwareType>>(); |
| 224 | |
| 225 | for (const chrome_browser_ssl::MITMSoftware& proto_entry : |
| 226 | proto.mitm_software()) { |
| 227 | // The |name| field and at least one of the |issuer_common_name_regex| and |
| 228 | // |issuer_organization_regex| fields must be set. |
| 229 | DCHECK(!proto_entry.name().empty()); |
| 230 | DCHECK(!(proto_entry.issuer_common_name_regex().empty() && |
| 231 | proto_entry.issuer_organization_regex().empty())); |
| 232 | if (proto_entry.name().empty() || |
| 233 | (proto_entry.issuer_common_name_regex().empty() && |
| 234 | proto_entry.issuer_organization_regex().empty())) { |
| 235 | continue; |
| 236 | } |
| 237 | |
| 238 | mitm_software_list.get()->push_back(MITMSoftwareType( |
| 239 | proto_entry.name(), proto_entry.issuer_common_name_regex(), |
| 240 | proto_entry.issuer_organization_regex())); |
Sasha Perigo | 2bfbbe90 | 2017-08-10 21:11:03 | [diff] [blame] | 241 | } |
Sasha Perigo | bc595cf | 2017-08-30 19:13:48 | [diff] [blame] | 242 | return mitm_software_list; |
Sasha Perigo | 2bfbbe90 | 2017-08-10 21:11:03 | [diff] [blame] | 243 | } |
Sasha Perigo | 2bfbbe90 | 2017-08-10 21:11:03 | [diff] [blame] | 244 | |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 245 | // Reads the SSL error assistant configuration from the resource bundle. |
meacer | 4404d45 | 2017-02-10 02:30:54 | [diff] [blame] | 246 | std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig> |
| 247 | ReadErrorAssistantProtoFromResourceBundle() { |
| 248 | auto proto = base::MakeUnique<chrome_browser_ssl::SSLErrorAssistantConfig>(); |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 249 | DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| 250 | DCHECK(proto); |
| 251 | ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); |
| 252 | base::StringPiece data = |
| 253 | bundle.GetRawDataResource(IDR_SSL_ERROR_ASSISTANT_PB); |
| 254 | google::protobuf::io::ArrayInputStream stream(data.data(), data.size()); |
meacer | 4404d45 | 2017-02-10 02:30:54 | [diff] [blame] | 255 | return proto->ParseFromZeroCopyStream(&stream) ? std::move(proto) : nullptr; |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 256 | } |
| 257 | |
bhanudev | 4b280dc | 2015-08-20 20:43:00 | [diff] [blame] | 258 | bool IsSSLCommonNameMismatchHandlingEnabled() { |
meacer | 93c0f80 | 2016-12-08 19:55:13 | [diff] [blame] | 259 | return base::FeatureList::IsEnabled(kSSLCommonNameMismatchHandling); |
bhanudev | 4b280dc | 2015-08-20 20:43:00 | [diff] [blame] | 260 | } |
| 261 | |
meacer | 352dc2e | 2017-01-10 01:27:41 | [diff] [blame] | 262 | // Configuration for SSLErrorHandler. |
gab | 2a48181 | 2017-05-30 15:08:50 | [diff] [blame] | 263 | class ConfigSingleton { |
meacer | 352dc2e | 2017-01-10 01:27:41 | [diff] [blame] | 264 | public: |
| 265 | ConfigSingleton(); |
| 266 | |
| 267 | base::TimeDelta interstitial_delay() const; |
| 268 | SSLErrorHandler::TimerStartedCallback* timer_started_callback() const; |
| 269 | base::Clock* clock() const; |
| 270 | network_time::NetworkTimeTracker* network_time_tracker() const; |
| 271 | |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 272 | // Returns true if any of the SHA256 hashes in |ssl_info| is of a captive |
| 273 | // portal certificate. The set of captive portal hashes is loaded on first |
| 274 | // use. |
| 275 | bool IsKnownCaptivePortalCert(const net::SSLInfo& ssl_info); |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 276 | |
Sasha Perigo | 232e21aa | 2017-08-19 01:45:40 | [diff] [blame] | 277 | // Returns the name of a known MITM software provider that matches the |
| 278 | // certificate passed in as the |cert| parameter. Returns empty string if |
| 279 | // there is no match. |
| 280 | const std::string MatchKnownMITMSoftware( |
| 281 | const scoped_refptr<net::X509Certificate> cert); |
Sasha Perigo | 2bfbbe90 | 2017-08-10 21:11:03 | [diff] [blame] | 282 | |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 283 | // Testing methods: |
| 284 | void ResetForTesting(); |
meacer | 352dc2e | 2017-01-10 01:27:41 | [diff] [blame] | 285 | void SetInterstitialDelayForTesting(const base::TimeDelta& delay); |
| 286 | void SetTimerStartedCallbackForTesting( |
| 287 | SSLErrorHandler::TimerStartedCallback* callback); |
| 288 | void SetClockForTesting(base::Clock* clock); |
| 289 | void SetNetworkTimeTrackerForTesting( |
| 290 | network_time::NetworkTimeTracker* tracker); |
Mustafa Emre Acer | 12b5effe | 2017-09-26 19:26:35 | [diff] [blame^] | 291 | |
meacer | 4404d45 | 2017-02-10 02:30:54 | [diff] [blame] | 292 | void SetErrorAssistantProto( |
| 293 | std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig> |
| 294 | error_assistant_proto); |
Sasha Perigo | 232e21aa | 2017-08-19 01:45:40 | [diff] [blame] | 295 | void SetEnterpriseManagedForTesting(bool enterprise_managed); |
| 296 | bool IsEnterpriseManagedFlagSetForTesting() const; |
Mustafa Acer | 3bc5efb | 2017-09-19 23:05:46 | [diff] [blame] | 297 | int GetErrorAssistantProtoVersionIdForTesting() const; |
| 298 | |
Sasha Perigo | 232e21aa | 2017-08-19 01:45:40 | [diff] [blame] | 299 | bool IsEnterpriseManaged() const; |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 300 | |
meacer | 352dc2e | 2017-01-10 01:27:41 | [diff] [blame] | 301 | private: |
| 302 | base::TimeDelta interstitial_delay_; |
| 303 | |
| 304 | // Callback to call when the interstitial timer is started. Used for |
| 305 | // testing. |
| 306 | SSLErrorHandler::TimerStartedCallback* timer_started_callback_ = nullptr; |
| 307 | |
| 308 | // The clock to use when deciding which error type to display. Used for |
| 309 | // testing. |
| 310 | base::Clock* testing_clock_ = nullptr; |
| 311 | |
| 312 | network_time::NetworkTimeTracker* network_time_tracker_ = nullptr; |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 313 | |
meacer | 4404d45 | 2017-02-10 02:30:54 | [diff] [blame] | 314 | // Error assistant configuration. |
| 315 | std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig> |
| 316 | error_assistant_proto_; |
| 317 | |
Sasha Perigo | bc595cf | 2017-08-30 19:13:48 | [diff] [blame] | 318 | std::unique_ptr<std::vector<MITMSoftwareType>> mitm_software_list_; |
Sasha Perigo | 232e21aa | 2017-08-19 01:45:40 | [diff] [blame] | 319 | |
| 320 | enum EnterpriseManaged { |
| 321 | ENTERPRISE_MANAGED_STATUS_NOT_SET, |
| 322 | ENTERPRISE_MANAGED_STATUS_TRUE, |
| 323 | ENTERPRISE_MANAGED_STATUS_FALSE |
| 324 | }; |
| 325 | EnterpriseManaged is_enterprise_managed_for_testing_; |
Sasha Perigo | 2bfbbe90 | 2017-08-10 21:11:03 | [diff] [blame] | 326 | |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 327 | // SPKI hashes belonging to certs treated as captive portals. Null until the |
meacer | 4404d45 | 2017-02-10 02:30:54 | [diff] [blame] | 328 | // first time IsKnownCaptivePortalCert() or SetErrorAssistantProto() |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 329 | // is called. |
| 330 | std::unique_ptr<std::unordered_set<std::string>> captive_portal_spki_hashes_; |
meacer | 352dc2e | 2017-01-10 01:27:41 | [diff] [blame] | 331 | }; |
| 332 | |
| 333 | ConfigSingleton::ConfigSingleton() |
| 334 | : interstitial_delay_( |
Sasha Perigo | 232e21aa | 2017-08-19 01:45:40 | [diff] [blame] | 335 | base::TimeDelta::FromMilliseconds(kInterstitialDelayInMilliseconds)), |
| 336 | is_enterprise_managed_for_testing_(ENTERPRISE_MANAGED_STATUS_NOT_SET) {} |
meacer | 352dc2e | 2017-01-10 01:27:41 | [diff] [blame] | 337 | |
| 338 | base::TimeDelta ConfigSingleton::interstitial_delay() const { |
| 339 | return interstitial_delay_; |
| 340 | } |
| 341 | |
| 342 | SSLErrorHandler::TimerStartedCallback* ConfigSingleton::timer_started_callback() |
| 343 | const { |
| 344 | return timer_started_callback_; |
| 345 | } |
| 346 | |
| 347 | network_time::NetworkTimeTracker* ConfigSingleton::network_time_tracker() |
| 348 | const { |
| 349 | return network_time_tracker_ ? network_time_tracker_ |
| 350 | : g_browser_process->network_time_tracker(); |
| 351 | } |
| 352 | |
| 353 | base::Clock* ConfigSingleton::clock() const { |
| 354 | return testing_clock_; |
| 355 | } |
| 356 | |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 357 | void ConfigSingleton::ResetForTesting() { |
| 358 | interstitial_delay_ = |
| 359 | base::TimeDelta::FromMilliseconds(kInterstitialDelayInMilliseconds); |
| 360 | timer_started_callback_ = nullptr; |
| 361 | network_time_tracker_ = nullptr; |
| 362 | testing_clock_ = nullptr; |
meacer | 4404d45 | 2017-02-10 02:30:54 | [diff] [blame] | 363 | error_assistant_proto_.reset(); |
Sasha Perigo | 232e21aa | 2017-08-19 01:45:40 | [diff] [blame] | 364 | mitm_software_list_.reset(); |
| 365 | is_enterprise_managed_for_testing_ = ENTERPRISE_MANAGED_STATUS_NOT_SET; |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 366 | captive_portal_spki_hashes_.reset(); |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 367 | } |
| 368 | |
meacer | 352dc2e | 2017-01-10 01:27:41 | [diff] [blame] | 369 | void ConfigSingleton::SetInterstitialDelayForTesting( |
| 370 | const base::TimeDelta& delay) { |
| 371 | interstitial_delay_ = delay; |
| 372 | } |
| 373 | |
| 374 | void ConfigSingleton::SetTimerStartedCallbackForTesting( |
| 375 | SSLErrorHandler::TimerStartedCallback* callback) { |
| 376 | DCHECK(!callback || !callback->is_null()); |
| 377 | timer_started_callback_ = callback; |
| 378 | } |
| 379 | |
| 380 | void ConfigSingleton::SetClockForTesting(base::Clock* clock) { |
| 381 | testing_clock_ = clock; |
| 382 | } |
| 383 | |
| 384 | void ConfigSingleton::SetNetworkTimeTrackerForTesting( |
| 385 | network_time::NetworkTimeTracker* tracker) { |
| 386 | network_time_tracker_ = tracker; |
| 387 | } |
| 388 | |
Sasha Perigo | 232e21aa | 2017-08-19 01:45:40 | [diff] [blame] | 389 | void ConfigSingleton::SetEnterpriseManagedForTesting(bool enterprise_managed) { |
| 390 | if (enterprise_managed) { |
| 391 | is_enterprise_managed_for_testing_ = ENTERPRISE_MANAGED_STATUS_TRUE; |
| 392 | } else { |
| 393 | is_enterprise_managed_for_testing_ = ENTERPRISE_MANAGED_STATUS_FALSE; |
| 394 | } |
| 395 | } |
| 396 | |
| 397 | bool ConfigSingleton::IsEnterpriseManagedFlagSetForTesting() const { |
Sasha Perigo | 232e21aa | 2017-08-19 01:45:40 | [diff] [blame] | 398 | if (is_enterprise_managed_for_testing_ == ENTERPRISE_MANAGED_STATUS_NOT_SET) { |
| 399 | return false; |
| 400 | } |
| 401 | return true; |
Sasha Perigo | 232e21aa | 2017-08-19 01:45:40 | [diff] [blame] | 402 | } |
| 403 | |
Mustafa Acer | 3bc5efb | 2017-09-19 23:05:46 | [diff] [blame] | 404 | int ConfigSingleton::GetErrorAssistantProtoVersionIdForTesting() const { |
| 405 | return error_assistant_proto_->version_id(); |
| 406 | } |
| 407 | |
Sasha Perigo | 232e21aa | 2017-08-19 01:45:40 | [diff] [blame] | 408 | bool ConfigSingleton::IsEnterpriseManaged() const { |
Sasha Perigo | 232e21aa | 2017-08-19 01:45:40 | [diff] [blame] | 409 | // Return the value of the testing flag if it's set. |
| 410 | if (is_enterprise_managed_for_testing_ == ENTERPRISE_MANAGED_STATUS_TRUE) { |
| 411 | return true; |
| 412 | } |
| 413 | if (is_enterprise_managed_for_testing_ == ENTERPRISE_MANAGED_STATUS_FALSE) { |
| 414 | return false; |
| 415 | } |
| 416 | |
| 417 | #if defined(OS_WIN) |
| 418 | if (base::win::IsEnterpriseManaged()) { |
| 419 | return true; |
| 420 | } |
| 421 | #elif defined(OS_CHROMEOS) |
| 422 | if (g_browser_process->platform_part()->browser_policy_connector_chromeos()) { |
| 423 | return true; |
| 424 | } |
| 425 | #endif // #if defined(OS_WIN) |
Sasha Perigo | 232e21aa | 2017-08-19 01:45:40 | [diff] [blame] | 426 | return false; |
Sasha Perigo | 232e21aa | 2017-08-19 01:45:40 | [diff] [blame] | 427 | } |
| 428 | |
meacer | 4404d45 | 2017-02-10 02:30:54 | [diff] [blame] | 429 | void ConfigSingleton::SetErrorAssistantProto( |
| 430 | std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig> proto) { |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 431 | DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
meacer | 4404d45 | 2017-02-10 02:30:54 | [diff] [blame] | 432 | CHECK(proto); |
Mustafa Acer | 3bc5efb | 2017-09-19 23:05:46 | [diff] [blame] | 433 | if (!error_assistant_proto_) { |
| 434 | // If the user hasn't seen an SSL error and a component update is available, |
| 435 | // the local resource bundle won't have been read and error_assistant_proto_ |
| 436 | // will be null. It's possible that the local resource bundle has a higher |
| 437 | // version_id than the component updater component, so load the local |
| 438 | // resource bundle once to compare versions. |
| 439 | // TODO(meacer): Ideally, ReadErrorAssistantProtoFromResourceBundle should |
| 440 | // only be called once and not on the UI thread. Move the call to the |
| 441 | // component updater component. |
| 442 | error_assistant_proto_ = ReadErrorAssistantProtoFromResourceBundle(); |
| 443 | } |
meacer | 4404d45 | 2017-02-10 02:30:54 | [diff] [blame] | 444 | // Ignore versions that are not new. |
| 445 | if (error_assistant_proto_ && |
| 446 | proto->version_id() <= error_assistant_proto_->version_id()) { |
| 447 | return; |
| 448 | } |
| 449 | error_assistant_proto_ = std::move(proto); |
Sasha Perigo | 2bfbbe90 | 2017-08-10 21:11:03 | [diff] [blame] | 450 | |
Sasha Perigo | 232e21aa | 2017-08-19 01:45:40 | [diff] [blame] | 451 | mitm_software_list_ = LoadMITMSoftwareList(*error_assistant_proto_); |
Sasha Perigo | 2bfbbe90 | 2017-08-10 21:11:03 | [diff] [blame] | 452 | |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 453 | captive_portal_spki_hashes_ = |
meacer | 4404d45 | 2017-02-10 02:30:54 | [diff] [blame] | 454 | LoadCaptivePortalCertHashes(*error_assistant_proto_); |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 455 | } |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 456 | |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 457 | bool ConfigSingleton::IsKnownCaptivePortalCert(const net::SSLInfo& ssl_info) { |
| 458 | DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| 459 | if (!captive_portal_spki_hashes_) { |
meacer | 4404d45 | 2017-02-10 02:30:54 | [diff] [blame] | 460 | error_assistant_proto_ = ReadErrorAssistantProtoFromResourceBundle(); |
| 461 | CHECK(error_assistant_proto_); |
| 462 | captive_portal_spki_hashes_ = |
| 463 | LoadCaptivePortalCertHashes(*error_assistant_proto_); |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 464 | } |
| 465 | |
| 466 | for (const net::HashValue& hash_value : ssl_info.public_key_hashes) { |
| 467 | if (hash_value.tag != net::HASH_VALUE_SHA256) { |
| 468 | continue; |
| 469 | } |
| 470 | if (captive_portal_spki_hashes_->find(hash_value.ToString()) != |
| 471 | captive_portal_spki_hashes_->end()) { |
| 472 | return true; |
| 473 | } |
| 474 | } |
| 475 | return false; |
| 476 | } |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 477 | |
Sasha Perigo | bc595cf | 2017-08-30 19:13:48 | [diff] [blame] | 478 | bool RegexMatchesAny(const std::vector<std::string>& organization_names, |
| 479 | const std::string& pattern) { |
| 480 | const re2::RE2 regex(pattern); |
| 481 | for (const std::string& organization_name : organization_names) { |
| 482 | if (re2::RE2::FullMatch(organization_name, regex)) { |
| 483 | return true; |
| 484 | } |
| 485 | } |
| 486 | return false; |
| 487 | } |
| 488 | |
Sasha Perigo | 232e21aa | 2017-08-19 01:45:40 | [diff] [blame] | 489 | const std::string ConfigSingleton::MatchKnownMITMSoftware( |
| 490 | const scoped_refptr<net::X509Certificate> cert) { |
Sasha Perigo | bc595cf | 2017-08-30 19:13:48 | [diff] [blame] | 491 | // Ignore if the certificate doesn't have an issuer common name or an |
| 492 | // organization name. |
| 493 | if (cert->issuer().common_name.empty() && |
| 494 | cert->issuer().organization_names.size() == 0) { |
Sasha Perigo | 232e21aa | 2017-08-19 01:45:40 | [diff] [blame] | 495 | return std::string(); |
Sasha Perigo | 2bfbbe90 | 2017-08-10 21:11:03 | [diff] [blame] | 496 | } |
| 497 | |
Sasha Perigo | bc595cf | 2017-08-30 19:13:48 | [diff] [blame] | 498 | // Load MITM software data from the SSL error assistant proto. |
Sasha Perigo | 2bfbbe90 | 2017-08-10 21:11:03 | [diff] [blame] | 499 | DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
Sasha Perigo | 232e21aa | 2017-08-19 01:45:40 | [diff] [blame] | 500 | if (!mitm_software_list_) { |
Sasha Perigo | 2bfbbe90 | 2017-08-10 21:11:03 | [diff] [blame] | 501 | error_assistant_proto_ = ReadErrorAssistantProtoFromResourceBundle(); |
| 502 | DCHECK(error_assistant_proto_); |
Sasha Perigo | 232e21aa | 2017-08-19 01:45:40 | [diff] [blame] | 503 | mitm_software_list_ = LoadMITMSoftwareList(*error_assistant_proto_); |
Sasha Perigo | 2bfbbe90 | 2017-08-10 21:11:03 | [diff] [blame] | 504 | } |
| 505 | |
Sasha Perigo | bc595cf | 2017-08-30 19:13:48 | [diff] [blame] | 506 | for (const MITMSoftwareType& mitm_software : *mitm_software_list_) { |
| 507 | // At least one of the common name or organization name fields should be |
| 508 | // populated on the MITM software list entry. |
| 509 | DCHECK(!(mitm_software.issuer_common_name_regex.empty() && |
| 510 | mitm_software.issuer_organization_regex.empty())); |
| 511 | if (mitm_software.issuer_common_name_regex.empty() && |
| 512 | mitm_software.issuer_organization_regex.empty()) { |
| 513 | continue; |
| 514 | } |
| 515 | |
| 516 | // If both |issuer_common_name_regex| and |issuer_organization_regex| are |
| 517 | // set, the certificate should match both regexes. |
| 518 | if (!mitm_software.issuer_common_name_regex.empty() && |
| 519 | !mitm_software.issuer_organization_regex.empty()) { |
| 520 | if (re2::RE2::FullMatch( |
| 521 | cert->issuer().common_name, |
| 522 | re2::RE2(mitm_software.issuer_common_name_regex)) && |
| 523 | RegexMatchesAny(cert->issuer().organization_names, |
| 524 | mitm_software.issuer_organization_regex)) { |
| 525 | return mitm_software.name; |
| 526 | } |
| 527 | |
| 528 | // If only |issuer_organization_regex| is set, the certificate's issuer |
| 529 | // organization name should match. |
| 530 | } else if (!mitm_software.issuer_organization_regex.empty()) { |
| 531 | if (RegexMatchesAny(cert->issuer().organization_names, |
| 532 | mitm_software.issuer_organization_regex)) { |
| 533 | return mitm_software.name; |
| 534 | } |
| 535 | |
| 536 | // If only |issuer_common_name_regex| is set, the certificate's issuer |
| 537 | // common name should match. |
| 538 | } else if (!mitm_software.issuer_common_name_regex.empty()) { |
| 539 | if (re2::RE2::FullMatch( |
| 540 | cert->issuer().common_name, |
| 541 | re2::RE2(mitm_software.issuer_common_name_regex))) { |
| 542 | return mitm_software.name; |
| 543 | } |
Sasha Perigo | 2bfbbe90 | 2017-08-10 21:11:03 | [diff] [blame] | 544 | } |
| 545 | } |
Sasha Perigo | 232e21aa | 2017-08-19 01:45:40 | [diff] [blame] | 546 | return std::string(); |
Sasha Perigo | 2bfbbe90 | 2017-08-10 21:11:03 | [diff] [blame] | 547 | } |
Sasha Perigo | 2bfbbe90 | 2017-08-10 21:11:03 | [diff] [blame] | 548 | |
meacer | c7693c79 | 2017-02-01 01:42:58 | [diff] [blame] | 549 | class SSLErrorHandlerDelegateImpl : public SSLErrorHandler::Delegate { |
| 550 | public: |
| 551 | SSLErrorHandlerDelegateImpl( |
| 552 | content::WebContents* web_contents, |
| 553 | const net::SSLInfo& ssl_info, |
| 554 | Profile* const profile, |
| 555 | int cert_error, |
| 556 | int options_mask, |
| 557 | const GURL& request_url, |
| 558 | std::unique_ptr<SSLCertReporter> ssl_cert_reporter, |
| 559 | const base::Callback<void(content::CertificateRequestResultType)>& |
| 560 | callback) |
| 561 | : web_contents_(web_contents), |
| 562 | ssl_info_(ssl_info), |
| 563 | profile_(profile), |
| 564 | cert_error_(cert_error), |
| 565 | options_mask_(options_mask), |
| 566 | request_url_(request_url), |
| 567 | ssl_cert_reporter_(std::move(ssl_cert_reporter)), |
| 568 | callback_(callback) {} |
| 569 | ~SSLErrorHandlerDelegateImpl() override; |
| 570 | |
| 571 | // SSLErrorHandler::Delegate methods: |
| 572 | void CheckForCaptivePortal() override; |
| 573 | bool GetSuggestedUrl(const std::vector<std::string>& dns_names, |
| 574 | GURL* suggested_url) const override; |
| 575 | void CheckSuggestedUrl( |
| 576 | const GURL& suggested_url, |
| 577 | const CommonNameMismatchHandler::CheckUrlCallback& callback) override; |
| 578 | void NavigateToSuggestedURL(const GURL& suggested_url) override; |
| 579 | bool IsErrorOverridable() const override; |
| 580 | void ShowCaptivePortalInterstitial(const GURL& landing_url) override; |
Sasha Perigo | 232e21aa | 2017-08-19 01:45:40 | [diff] [blame] | 581 | void ShowMITMSoftwareInterstitial(const std::string& mitm_software_name, |
| 582 | bool is_enterprise_managed) override; |
meacer | c7693c79 | 2017-02-01 01:42:58 | [diff] [blame] | 583 | void ShowSSLInterstitial() override; |
| 584 | void ShowBadClockInterstitial(const base::Time& now, |
| 585 | ssl_errors::ClockState clock_state) override; |
| 586 | |
| 587 | private: |
| 588 | content::WebContents* web_contents_; |
| 589 | const net::SSLInfo& ssl_info_; |
| 590 | Profile* const profile_; |
| 591 | const int cert_error_; |
| 592 | const int options_mask_; |
| 593 | const GURL request_url_; |
| 594 | std::unique_ptr<CommonNameMismatchHandler> common_name_mismatch_handler_; |
| 595 | std::unique_ptr<SSLCertReporter> ssl_cert_reporter_; |
| 596 | const base::Callback<void(content::CertificateRequestResultType)> callback_; |
| 597 | }; |
| 598 | |
| 599 | SSLErrorHandlerDelegateImpl::~SSLErrorHandlerDelegateImpl() { |
| 600 | if (common_name_mismatch_handler_) { |
| 601 | common_name_mismatch_handler_->Cancel(); |
| 602 | common_name_mismatch_handler_.reset(); |
| 603 | } |
| 604 | } |
| 605 | |
| 606 | void SSLErrorHandlerDelegateImpl::CheckForCaptivePortal() { |
| 607 | #if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION) |
| 608 | CaptivePortalService* captive_portal_service = |
| 609 | CaptivePortalServiceFactory::GetForProfile(profile_); |
| 610 | captive_portal_service->DetectCaptivePortal(); |
| 611 | #else |
| 612 | NOTREACHED(); |
| 613 | #endif |
| 614 | } |
| 615 | |
| 616 | bool SSLErrorHandlerDelegateImpl::GetSuggestedUrl( |
| 617 | const std::vector<std::string>& dns_names, |
| 618 | GURL* suggested_url) const { |
| 619 | return CommonNameMismatchHandler::GetSuggestedUrl(request_url_, dns_names, |
| 620 | suggested_url); |
| 621 | } |
| 622 | |
| 623 | void SSLErrorHandlerDelegateImpl::CheckSuggestedUrl( |
| 624 | const GURL& suggested_url, |
| 625 | const CommonNameMismatchHandler::CheckUrlCallback& callback) { |
| 626 | scoped_refptr<net::URLRequestContextGetter> request_context( |
| 627 | profile_->GetRequestContext()); |
| 628 | common_name_mismatch_handler_.reset( |
| 629 | new CommonNameMismatchHandler(request_url_, request_context)); |
| 630 | |
| 631 | common_name_mismatch_handler_->CheckSuggestedUrl(suggested_url, callback); |
| 632 | } |
| 633 | |
| 634 | void SSLErrorHandlerDelegateImpl::NavigateToSuggestedURL( |
| 635 | const GURL& suggested_url) { |
| 636 | content::NavigationController::LoadURLParams load_params(suggested_url); |
| 637 | load_params.transition_type = ui::PAGE_TRANSITION_TYPED; |
| 638 | web_contents_->GetController().LoadURLWithParams(load_params); |
| 639 | } |
| 640 | |
| 641 | bool SSLErrorHandlerDelegateImpl::IsErrorOverridable() const { |
| 642 | return SSLBlockingPage::IsOverridable(options_mask_, profile_); |
| 643 | } |
| 644 | |
| 645 | void SSLErrorHandlerDelegateImpl::ShowCaptivePortalInterstitial( |
| 646 | const GURL& landing_url) { |
meacer | c7693c79 | 2017-02-01 01:42:58 | [diff] [blame] | 647 | // Show captive portal blocking page. The interstitial owns the blocking page. |
| 648 | (new CaptivePortalBlockingPage(web_contents_, request_url_, landing_url, |
| 649 | std::move(ssl_cert_reporter_), ssl_info_, |
| 650 | callback_)) |
| 651 | ->Show(); |
meacer | c7693c79 | 2017-02-01 01:42:58 | [diff] [blame] | 652 | } |
| 653 | |
Sasha Perigo | 232e21aa | 2017-08-19 01:45:40 | [diff] [blame] | 654 | void SSLErrorHandlerDelegateImpl::ShowMITMSoftwareInterstitial( |
| 655 | const std::string& mitm_software_name, |
| 656 | bool is_enterprise_managed) { |
Sasha Perigo | e3a6375 | 2017-08-15 18:34:32 | [diff] [blame] | 657 | // Show MITM software blocking page. The interstitial owns the blocking page. |
Sasha Perigo | 232e21aa | 2017-08-19 01:45:40 | [diff] [blame] | 658 | (new MITMSoftwareBlockingPage( |
| 659 | web_contents_, cert_error_, request_url_, std::move(ssl_cert_reporter_), |
| 660 | ssl_info_, mitm_software_name, is_enterprise_managed, callback_)) |
Sasha Perigo | 2bfbbe90 | 2017-08-10 21:11:03 | [diff] [blame] | 661 | ->Show(); |
Sasha Perigo | 2bfbbe90 | 2017-08-10 21:11:03 | [diff] [blame] | 662 | } |
| 663 | |
meacer | c7693c79 | 2017-02-01 01:42:58 | [diff] [blame] | 664 | void SSLErrorHandlerDelegateImpl::ShowSSLInterstitial() { |
| 665 | // Show SSL blocking page. The interstitial owns the blocking page. |
estark | 5cbbc09 | 2017-06-23 02:00:56 | [diff] [blame] | 666 | (SSLBlockingPage::Create( |
| 667 | web_contents_, cert_error_, ssl_info_, request_url_, options_mask_, |
| 668 | base::Time::NowFromSystemTime(), std::move(ssl_cert_reporter_), |
| 669 | base::FeatureList::IsEnabled(kSuperfishInterstitial) && |
| 670 | IsSuperfish(ssl_info_.cert), |
| 671 | callback_)) |
meacer | c7693c79 | 2017-02-01 01:42:58 | [diff] [blame] | 672 | ->Show(); |
| 673 | } |
| 674 | |
| 675 | void SSLErrorHandlerDelegateImpl::ShowBadClockInterstitial( |
| 676 | const base::Time& now, |
| 677 | ssl_errors::ClockState clock_state) { |
| 678 | // Show bad clock page. The interstitial owns the blocking page. |
| 679 | (new BadClockBlockingPage(web_contents_, cert_error_, ssl_info_, request_url_, |
| 680 | now, clock_state, std::move(ssl_cert_reporter_), |
| 681 | callback_)) |
| 682 | ->Show(); |
| 683 | } |
meacer | 352dc2e | 2017-01-10 01:27:41 | [diff] [blame] | 684 | |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 685 | } // namespace |
| 686 | |
| 687 | DEFINE_WEB_CONTENTS_USER_DATA_KEY(SSLErrorHandler); |
meacer | 8b4cea2 | 2015-08-28 02:02:55 | [diff] [blame] | 688 | DEFINE_WEB_CONTENTS_USER_DATA_KEY(CommonNameMismatchRedirectObserver); |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 689 | |
meacer | c7693c79 | 2017-02-01 01:42:58 | [diff] [blame] | 690 | static base::LazyInstance<ConfigSingleton>::Leaky g_config = |
| 691 | LAZY_INSTANCE_INITIALIZER; |
| 692 | |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 693 | void SSLErrorHandler::HandleSSLError( |
| 694 | content::WebContents* web_contents, |
| 695 | int cert_error, |
| 696 | const net::SSLInfo& ssl_info, |
| 697 | const GURL& request_url, |
| 698 | int options_mask, |
dcheng | 4af4858 | 2016-04-19 00:29:35 | [diff] [blame] | 699 | std::unique_ptr<SSLCertReporter> ssl_cert_reporter, |
estark | 719dde5 | 2016-08-09 03:14:27 | [diff] [blame] | 700 | const base::Callback<void(content::CertificateRequestResultType)>& |
| 701 | callback) { |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 702 | DCHECK(!FromWebContents(web_contents)); |
meacer | c7693c79 | 2017-02-01 01:42:58 | [diff] [blame] | 703 | |
| 704 | Profile* profile = |
| 705 | Profile::FromBrowserContext(web_contents->GetBrowserContext()); |
| 706 | |
| 707 | SSLErrorHandler* error_handler = new SSLErrorHandler( |
| 708 | std::unique_ptr<SSLErrorHandler::Delegate>( |
| 709 | new SSLErrorHandlerDelegateImpl( |
| 710 | web_contents, ssl_info, profile, cert_error, options_mask, |
| 711 | request_url, std::move(ssl_cert_reporter), callback)), |
| 712 | web_contents, profile, cert_error, ssl_info, request_url, callback); |
avi | 284ec61 | 2017-05-03 01:52:23 | [diff] [blame] | 713 | web_contents->SetUserData(UserDataKey(), base::WrapUnique(error_handler)); |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 714 | error_handler->StartHandlingError(); |
| 715 | } |
| 716 | |
| 717 | // static |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 718 | void SSLErrorHandler::ResetConfigForTesting() { |
| 719 | g_config.Pointer()->ResetForTesting(); |
| 720 | } |
| 721 | |
| 722 | // static |
meacer | 352dc2e | 2017-01-10 01:27:41 | [diff] [blame] | 723 | void SSLErrorHandler::SetInterstitialDelayForTesting( |
| 724 | const base::TimeDelta& delay) { |
| 725 | g_config.Pointer()->SetInterstitialDelayForTesting(delay); |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 726 | } |
| 727 | |
| 728 | // static |
meacer | 352dc2e | 2017-01-10 01:27:41 | [diff] [blame] | 729 | void SSLErrorHandler::SetInterstitialTimerStartedCallbackForTesting( |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 730 | TimerStartedCallback* callback) { |
meacer | 352dc2e | 2017-01-10 01:27:41 | [diff] [blame] | 731 | g_config.Pointer()->SetTimerStartedCallbackForTesting(callback); |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 732 | } |
| 733 | |
felt | 534cc9e7 | 2015-09-01 20:38:59 | [diff] [blame] | 734 | // static |
meacer | 352dc2e | 2017-01-10 01:27:41 | [diff] [blame] | 735 | void SSLErrorHandler::SetClockForTesting(base::Clock* testing_clock) { |
| 736 | g_config.Pointer()->SetClockForTesting(testing_clock); |
felt | 534cc9e7 | 2015-09-01 20:38:59 | [diff] [blame] | 737 | } |
| 738 | |
estark | 5057f82 | 2016-11-08 21:34:54 | [diff] [blame] | 739 | // static |
meacer | 352dc2e | 2017-01-10 01:27:41 | [diff] [blame] | 740 | void SSLErrorHandler::SetNetworkTimeTrackerForTesting( |
estark | 5057f82 | 2016-11-08 21:34:54 | [diff] [blame] | 741 | network_time::NetworkTimeTracker* tracker) { |
meacer | 352dc2e | 2017-01-10 01:27:41 | [diff] [blame] | 742 | g_config.Pointer()->SetNetworkTimeTrackerForTesting(tracker); |
estark | 5057f82 | 2016-11-08 21:34:54 | [diff] [blame] | 743 | } |
| 744 | |
meacer | c7693c79 | 2017-02-01 01:42:58 | [diff] [blame] | 745 | // static |
Sasha Perigo | 232e21aa | 2017-08-19 01:45:40 | [diff] [blame] | 746 | void SSLErrorHandler::SetEnterpriseManagedForTesting(bool enterprise_managed) { |
| 747 | g_config.Pointer()->SetEnterpriseManagedForTesting(enterprise_managed); |
| 748 | } |
| 749 | |
| 750 | // static |
| 751 | bool SSLErrorHandler::IsEnterpriseManagedFlagSetForTesting() { |
| 752 | return g_config.Pointer()->IsEnterpriseManagedFlagSetForTesting(); |
| 753 | } |
| 754 | |
| 755 | // static |
meacer | c7693c79 | 2017-02-01 01:42:58 | [diff] [blame] | 756 | std::string SSLErrorHandler::GetHistogramNameForTesting() { |
| 757 | return kHistogram; |
| 758 | } |
| 759 | |
Mustafa Acer | 3bc5efb | 2017-09-19 23:05:46 | [diff] [blame] | 760 | // static |
| 761 | int SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting() { |
| 762 | return g_config.Pointer()->GetErrorAssistantProtoVersionIdForTesting(); |
| 763 | } |
| 764 | |
meacer | c7693c79 | 2017-02-01 01:42:58 | [diff] [blame] | 765 | bool SSLErrorHandler::IsTimerRunningForTesting() const { |
| 766 | return timer_.IsRunning(); |
| 767 | } |
| 768 | |
meacer | 4404d45 | 2017-02-10 02:30:54 | [diff] [blame] | 769 | void SSLErrorHandler::SetErrorAssistantProto( |
| 770 | std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig> config_proto) { |
meacer | 4404d45 | 2017-02-10 02:30:54 | [diff] [blame] | 771 | g_config.Pointer()->SetErrorAssistantProto(std::move(config_proto)); |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 772 | } |
| 773 | |
dcheng | 4af4858 | 2016-04-19 00:29:35 | [diff] [blame] | 774 | SSLErrorHandler::SSLErrorHandler( |
meacer | c7693c79 | 2017-02-01 01:42:58 | [diff] [blame] | 775 | std::unique_ptr<Delegate> delegate, |
dcheng | 4af4858 | 2016-04-19 00:29:35 | [diff] [blame] | 776 | content::WebContents* web_contents, |
meacer | c7693c79 | 2017-02-01 01:42:58 | [diff] [blame] | 777 | Profile* profile, |
dcheng | 4af4858 | 2016-04-19 00:29:35 | [diff] [blame] | 778 | int cert_error, |
| 779 | const net::SSLInfo& ssl_info, |
| 780 | const GURL& request_url, |
estark | 719dde5 | 2016-08-09 03:14:27 | [diff] [blame] | 781 | const base::Callback<void(content::CertificateRequestResultType)>& callback) |
meacer | d2c91b4 | 2015-03-20 18:34:40 | [diff] [blame] | 782 | : content::WebContentsObserver(web_contents), |
meacer | c7693c79 | 2017-02-01 01:42:58 | [diff] [blame] | 783 | delegate_(std::move(delegate)), |
meacer | d2c91b4 | 2015-03-20 18:34:40 | [diff] [blame] | 784 | web_contents_(web_contents), |
meacer | c7693c79 | 2017-02-01 01:42:58 | [diff] [blame] | 785 | profile_(profile), |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 786 | cert_error_(cert_error), |
| 787 | ssl_info_(ssl_info), |
| 788 | request_url_(request_url), |
estark | 93272ab | 2015-03-25 23:54:01 | [diff] [blame] | 789 | callback_(callback), |
estark | 5057f82 | 2016-11-08 21:34:54 | [diff] [blame] | 790 | weak_ptr_factory_(this) {} |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 791 | |
| 792 | SSLErrorHandler::~SSLErrorHandler() { |
| 793 | } |
| 794 | |
| 795 | void SSLErrorHandler::StartHandlingError() { |
| 796 | RecordUMA(HANDLE_ALL); |
| 797 | |
estark | 5cbbc09 | 2017-06-23 02:00:56 | [diff] [blame] | 798 | RecordSuperfishUMA(ssl_info_.cert); |
estark | 6767d1b | 2017-06-03 17:37:20 | [diff] [blame] | 799 | |
mab | 740b02b | 2016-03-17 23:44:21 | [diff] [blame] | 800 | if (ssl_errors::ErrorInfo::NetErrorToErrorType(cert_error_) == |
| 801 | ssl_errors::ErrorInfo::CERT_DATE_INVALID) { |
estark | 5057f82 | 2016-11-08 21:34:54 | [diff] [blame] | 802 | HandleCertDateInvalidError(); |
| 803 | return; |
felt | 534cc9e7 | 2015-09-01 20:38:59 | [diff] [blame] | 804 | } |
| 805 | |
meacer | 4f8acbd | 2017-03-02 04:23:52 | [diff] [blame] | 806 | const bool only_error_is_name_mismatch = |
Sasha Perigo | 2bfbbe90 | 2017-08-10 21:11:03 | [diff] [blame] | 807 | IsOnlyCertError(net::CERT_STATUS_COMMON_NAME_INVALID); |
meacer | 4f8acbd | 2017-03-02 04:23:52 | [diff] [blame] | 808 | |
meacer | 4f8acbd | 2017-03-02 04:23:52 | [diff] [blame] | 809 | // Check known captive portal certificate list if the only error is |
| 810 | // name-mismatch. If there are multiple errors, it indicates that the captive |
| 811 | // portal landing page itself will have SSL errors, and so it's not a very |
| 812 | // helpful place to direct the user to go. |
| 813 | if (base::FeatureList::IsEnabled(kCaptivePortalCertificateList) && |
| 814 | only_error_is_name_mismatch && |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 815 | g_config.Pointer()->IsKnownCaptivePortalCert(ssl_info_)) { |
| 816 | RecordUMA(CAPTIVE_PORTAL_CERT_FOUND); |
Mustafa Emre Acer | 12b5effe | 2017-09-26 19:26:35 | [diff] [blame^] | 817 | ShowCaptivePortalInterstitial(GURL()); |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 818 | return; |
| 819 | } |
meacer | b078580 | 2017-02-07 23:46:52 | [diff] [blame] | 820 | |
Sasha Perigo | 2bfbbe90 | 2017-08-10 21:11:03 | [diff] [blame] | 821 | // The MITM software interstitial is displayed if and only if: |
| 822 | // - the error thrown is not overridable |
| 823 | // - the only certificate error is CERT_STATUS_AUTHORITY_INVALID |
| 824 | // - the certificate contains a string that indicates it was issued by a |
| 825 | // MITM software |
| 826 | if (IsMITMSoftwareInterstitialEnabled() && !delegate_->IsErrorOverridable() && |
Sasha Perigo | 232e21aa | 2017-08-19 01:45:40 | [diff] [blame] | 827 | IsOnlyCertError(net::CERT_STATUS_AUTHORITY_INVALID)) { |
| 828 | const std::string found_mitm_software = |
| 829 | g_config.Pointer()->MatchKnownMITMSoftware(ssl_info_.cert); |
| 830 | if (!found_mitm_software.empty()) { |
| 831 | ShowMITMSoftwareInterstitial(found_mitm_software, |
| 832 | g_config.Pointer()->IsEnterpriseManaged()); |
| 833 | return; |
| 834 | } |
Sasha Perigo | 2bfbbe90 | 2017-08-10 21:11:03 | [diff] [blame] | 835 | } |
Sasha Perigo | 2bfbbe90 | 2017-08-10 21:11:03 | [diff] [blame] | 836 | |
bhanudev | 4b280dc | 2015-08-20 20:43:00 | [diff] [blame] | 837 | if (IsSSLCommonNameMismatchHandlingEnabled() && |
bhanudev | 3999e37 | 2015-08-21 07:23:10 | [diff] [blame] | 838 | cert_error_ == net::ERR_CERT_COMMON_NAME_INVALID && |
elawrence | c7484f5 | 2017-04-05 21:46:42 | [diff] [blame] | 839 | delegate_->IsErrorOverridable()) { |
| 840 | std::vector<std::string> dns_names; |
| 841 | ssl_info_.cert->GetSubjectAltName(&dns_names, nullptr); |
| 842 | GURL suggested_url; |
| 843 | if (!dns_names.empty() && |
| 844 | delegate_->GetSuggestedUrl(dns_names, &suggested_url)) { |
| 845 | RecordUMA(WWW_MISMATCH_FOUND_IN_SAN); |
bhanudev | 2051ce7 | 2015-08-18 22:21:18 | [diff] [blame] | 846 | |
elawrence | c7484f5 | 2017-04-05 21:46:42 | [diff] [blame] | 847 | // Show the SSL interstitial if |CERT_STATUS_COMMON_NAME_INVALID| is not |
| 848 | // the only error. Need not check for captive portal in this case. |
| 849 | // (See the comment below). |
| 850 | if (!only_error_is_name_mismatch) { |
| 851 | ShowSSLInterstitial(); |
| 852 | return; |
| 853 | } |
| 854 | delegate_->CheckSuggestedUrl( |
| 855 | suggested_url, |
| 856 | base::Bind(&SSLErrorHandler::CommonNameMismatchHandlerCallback, |
| 857 | weak_ptr_factory_.GetWeakPtr())); |
| 858 | timer_.Start(FROM_HERE, g_config.Pointer()->interstitial_delay(), this, |
| 859 | &SSLErrorHandler::ShowSSLInterstitial); |
| 860 | |
| 861 | if (g_config.Pointer()->timer_started_callback()) |
| 862 | g_config.Pointer()->timer_started_callback()->Run(web_contents_); |
| 863 | |
| 864 | // Do not check for a captive portal in this case, because a captive |
| 865 | // portal most likely cannot serve a valid certificate which passes the |
| 866 | // similarity check. |
bhanudev | 2051ce7 | 2015-08-18 22:21:18 | [diff] [blame] | 867 | return; |
| 868 | } |
bhanudev | 2051ce7 | 2015-08-18 22:21:18 | [diff] [blame] | 869 | } |
| 870 | |
meacer | c7693c79 | 2017-02-01 01:42:58 | [diff] [blame] | 871 | // Always listen to captive portal notifications, otherwise build fails |
| 872 | // because profile_ isn't used. This is a no-op on platforms where |
| 873 | // captive portal detection is disabled. |
| 874 | registrar_.Add(this, chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT, |
| 875 | content::Source<Profile>(profile_)); |
| 876 | |
brettw | ab78fef | 2016-10-12 02:56:05 | [diff] [blame] | 877 | #if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION) |
bhanudev | 2051ce7 | 2015-08-18 22:21:18 | [diff] [blame] | 878 | CaptivePortalTabHelper* captive_portal_tab_helper = |
| 879 | CaptivePortalTabHelper::FromWebContents(web_contents_); |
| 880 | if (captive_portal_tab_helper) { |
| 881 | captive_portal_tab_helper->OnSSLCertError(ssl_info_); |
| 882 | } |
| 883 | |
meacer | 5c77768 | 2015-01-09 06:54:53 | [diff] [blame] | 884 | if (IsCaptivePortalInterstitialEnabled()) { |
meacer | c7693c79 | 2017-02-01 01:42:58 | [diff] [blame] | 885 | delegate_->CheckForCaptivePortal(); |
meacer | 352dc2e | 2017-01-10 01:27:41 | [diff] [blame] | 886 | timer_.Start(FROM_HERE, g_config.Pointer()->interstitial_delay(), this, |
| 887 | &SSLErrorHandler::ShowSSLInterstitial); |
| 888 | if (g_config.Pointer()->timer_started_callback()) |
| 889 | g_config.Pointer()->timer_started_callback()->Run(web_contents_); |
meacer | 5c77768 | 2015-01-09 06:54:53 | [diff] [blame] | 890 | return; |
| 891 | } |
| 892 | #endif |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 893 | // Display an SSL interstitial. |
| 894 | ShowSSLInterstitial(); |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 895 | } |
| 896 | |
meacer | ca64d9819 | 2015-01-23 21:34:30 | [diff] [blame] | 897 | void SSLErrorHandler::ShowCaptivePortalInterstitial(const GURL& landing_url) { |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 898 | // Show captive portal blocking page. The interstitial owns the blocking page. |
meacer | c7693c79 | 2017-02-01 01:42:58 | [diff] [blame] | 899 | RecordUMA(delegate_->IsErrorOverridable() |
fahl | e66e7ff | 2015-04-23 19:38:30 | [diff] [blame] | 900 | ? SHOW_CAPTIVE_PORTAL_INTERSTITIAL_OVERRIDABLE |
| 901 | : SHOW_CAPTIVE_PORTAL_INTERSTITIAL_NONOVERRIDABLE); |
meacer | c7693c79 | 2017-02-01 01:42:58 | [diff] [blame] | 902 | delegate_->ShowCaptivePortalInterstitial(landing_url); |
Mustafa Emre Acer | 12b5effe | 2017-09-26 19:26:35 | [diff] [blame^] | 903 | |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 904 | // Once an interstitial is displayed, no need to keep the handler around. |
meacer | 8b4cea2 | 2015-08-28 02:02:55 | [diff] [blame] | 905 | // This is the equivalent of "delete this". It also destroys the timer. |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 906 | web_contents_->RemoveUserData(UserDataKey()); |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 907 | } |
| 908 | |
Sasha Perigo | 232e21aa | 2017-08-19 01:45:40 | [diff] [blame] | 909 | void SSLErrorHandler::ShowMITMSoftwareInterstitial( |
| 910 | const std::string& mitm_software_name, |
| 911 | bool is_enterprise_managed) { |
Sasha Perigo | 2bfbbe90 | 2017-08-10 21:11:03 | [diff] [blame] | 912 | // Show SSL blocking page. The interstitial owns the blocking page. |
| 913 | RecordUMA(SHOW_MITM_SOFTWARE_INTERSTITIAL); |
Sasha Perigo | 232e21aa | 2017-08-19 01:45:40 | [diff] [blame] | 914 | delegate_->ShowMITMSoftwareInterstitial(mitm_software_name, |
| 915 | is_enterprise_managed); |
Sasha Perigo | 2bfbbe90 | 2017-08-10 21:11:03 | [diff] [blame] | 916 | // Once an interstitial is displayed, no need to keep the handler around. |
| 917 | // This is the equivalent of "delete this". |
| 918 | web_contents_->RemoveUserData(UserDataKey()); |
Sasha Perigo | 2bfbbe90 | 2017-08-10 21:11:03 | [diff] [blame] | 919 | } |
| 920 | |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 921 | void SSLErrorHandler::ShowSSLInterstitial() { |
| 922 | // Show SSL blocking page. The interstitial owns the blocking page. |
meacer | c7693c79 | 2017-02-01 01:42:58 | [diff] [blame] | 923 | RecordUMA(delegate_->IsErrorOverridable() |
| 924 | ? SHOW_SSL_INTERSTITIAL_OVERRIDABLE |
| 925 | : SHOW_SSL_INTERSTITIAL_NONOVERRIDABLE); |
| 926 | delegate_->ShowSSLInterstitial(); |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 927 | // Once an interstitial is displayed, no need to keep the handler around. |
| 928 | // This is the equivalent of "delete this". |
| 929 | web_contents_->RemoveUserData(UserDataKey()); |
| 930 | } |
| 931 | |
mab | 740b02b | 2016-03-17 23:44:21 | [diff] [blame] | 932 | void SSLErrorHandler::ShowBadClockInterstitial( |
| 933 | const base::Time& now, |
| 934 | ssl_errors::ClockState clock_state) { |
felt | 534cc9e7 | 2015-09-01 20:38:59 | [diff] [blame] | 935 | RecordUMA(SHOW_BAD_CLOCK); |
meacer | c7693c79 | 2017-02-01 01:42:58 | [diff] [blame] | 936 | delegate_->ShowBadClockInterstitial(now, clock_state); |
felt | 534cc9e7 | 2015-09-01 20:38:59 | [diff] [blame] | 937 | // Once an interstitial is displayed, no need to keep the handler around. |
| 938 | // This is the equivalent of "delete this". |
| 939 | web_contents_->RemoveUserData(UserDataKey()); |
| 940 | } |
| 941 | |
meacer | 8b4cea2 | 2015-08-28 02:02:55 | [diff] [blame] | 942 | void SSLErrorHandler::CommonNameMismatchHandlerCallback( |
meacer | c7693c79 | 2017-02-01 01:42:58 | [diff] [blame] | 943 | CommonNameMismatchHandler::SuggestedUrlCheckResult result, |
meacer | 8b4cea2 | 2015-08-28 02:02:55 | [diff] [blame] | 944 | const GURL& suggested_url) { |
| 945 | timer_.Stop(); |
| 946 | if (result == CommonNameMismatchHandler::SuggestedUrlCheckResult:: |
| 947 | SUGGESTED_URL_AVAILABLE) { |
| 948 | RecordUMA(WWW_MISMATCH_URL_AVAILABLE); |
| 949 | CommonNameMismatchRedirectObserver::AddToConsoleAfterNavigation( |
| 950 | web_contents(), request_url_.host(), suggested_url.host()); |
meacer | c7693c79 | 2017-02-01 01:42:58 | [diff] [blame] | 951 | delegate_->NavigateToSuggestedURL(suggested_url); |
meacer | 8b4cea2 | 2015-08-28 02:02:55 | [diff] [blame] | 952 | } else { |
| 953 | RecordUMA(WWW_MISMATCH_URL_NOT_AVAILABLE); |
| 954 | ShowSSLInterstitial(); |
| 955 | } |
| 956 | } |
| 957 | |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 958 | void SSLErrorHandler::Observe( |
| 959 | int type, |
| 960 | const content::NotificationSource& source, |
| 961 | const content::NotificationDetails& details) { |
brettw | ab78fef | 2016-10-12 02:56:05 | [diff] [blame] | 962 | #if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION) |
thestig | 5283579 | 2016-06-13 21:08:09 | [diff] [blame] | 963 | DCHECK_EQ(chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT, type); |
| 964 | |
| 965 | timer_.Stop(); |
| 966 | CaptivePortalService::Results* results = |
| 967 | content::Details<CaptivePortalService::Results>(details).ptr(); |
| 968 | if (results->result == captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL) |
| 969 | ShowCaptivePortalInterstitial(results->landing_url); |
| 970 | else |
| 971 | ShowSSLInterstitial(); |
meacer | c7693c79 | 2017-02-01 01:42:58 | [diff] [blame] | 972 | #else |
| 973 | NOTREACHED(); |
meacer | 4ef065e | 2015-01-09 03:21:35 | [diff] [blame] | 974 | #endif |
| 975 | } |
meacer | d2c91b4 | 2015-03-20 18:34:40 | [diff] [blame] | 976 | |
jam | 6625f12 | 2017-01-20 06:37:33 | [diff] [blame] | 977 | void SSLErrorHandler::DidStartNavigation( |
| 978 | content::NavigationHandle* navigation_handle) { |
eugenebut | a11672fb | 2017-03-07 17:13:51 | [diff] [blame] | 979 | if (!navigation_handle->IsInMainFrame() || |
| 980 | navigation_handle->IsSameDocument()) { |
jam | 6625f12 | 2017-01-20 06:37:33 | [diff] [blame] | 981 | return; |
eugenebut | a11672fb | 2017-03-07 17:13:51 | [diff] [blame] | 982 | } |
jam | 6625f12 | 2017-01-20 06:37:33 | [diff] [blame] | 983 | |
toyoshim | 0df1d3a | 2016-09-09 09:52:48 | [diff] [blame] | 984 | // Destroy the error handler on all new navigations. This ensures that the |
| 985 | // handler is properly recreated when a hanging page is navigated to an SSL |
| 986 | // error, even when the tab's WebContents doesn't change. |
bhanudev | 85c5b8b5 | 2015-07-31 02:00:37 | [diff] [blame] | 987 | DeleteSSLErrorHandler(); |
| 988 | } |
| 989 | |
| 990 | void SSLErrorHandler::NavigationStopped() { |
| 991 | // Destroy the error handler when the page load is stopped. |
| 992 | DeleteSSLErrorHandler(); |
| 993 | } |
| 994 | |
| 995 | void SSLErrorHandler::DeleteSSLErrorHandler() { |
meacer | d2c91b4 | 2015-03-20 18:34:40 | [diff] [blame] | 996 | // Need to explicity deny the certificate via the callback, otherwise memory |
| 997 | // is leaked. |
| 998 | if (!callback_.is_null()) { |
estark | 719dde5 | 2016-08-09 03:14:27 | [diff] [blame] | 999 | base::ResetAndReturn(&callback_) |
| 1000 | .Run(content::CERTIFICATE_REQUEST_RESULT_TYPE_DENY); |
meacer | d2c91b4 | 2015-03-20 18:34:40 | [diff] [blame] | 1001 | } |
meacer | c7693c79 | 2017-02-01 01:42:58 | [diff] [blame] | 1002 | delegate_.reset(); |
meacer | 8b4cea2 | 2015-08-28 02:02:55 | [diff] [blame] | 1003 | // Deletes |this| and also destroys the timer. |
meacer | d2c91b4 | 2015-03-20 18:34:40 | [diff] [blame] | 1004 | web_contents_->RemoveUserData(UserDataKey()); |
| 1005 | } |
estark | 5057f82 | 2016-11-08 21:34:54 | [diff] [blame] | 1006 | |
| 1007 | void SSLErrorHandler::HandleCertDateInvalidError() { |
estark | 8d6c3c7 | 2016-12-14 03:55:22 | [diff] [blame] | 1008 | const base::TimeTicks now = base::TimeTicks::Now(); |
meacer | 352dc2e | 2017-01-10 01:27:41 | [diff] [blame] | 1009 | timer_.Start(FROM_HERE, g_config.Pointer()->interstitial_delay(), |
estark | 5057f82 | 2016-11-08 21:34:54 | [diff] [blame] | 1010 | base::Bind(&SSLErrorHandler::HandleCertDateInvalidErrorImpl, |
estark | 8d6c3c7 | 2016-12-14 03:55:22 | [diff] [blame] | 1011 | base::Unretained(this), now)); |
estark | 5057f82 | 2016-11-08 21:34:54 | [diff] [blame] | 1012 | // Try kicking off a time fetch to get an up-to-date estimate of the |
| 1013 | // true time. This will only have an effect if network time is |
| 1014 | // unavailable or if there is not already a query in progress. |
| 1015 | // |
| 1016 | // Pass a weak pointer as the callback; if the timer fires before the |
| 1017 | // fetch completes and shows an interstitial, this SSLErrorHandler |
| 1018 | // will be deleted. |
meacer | 352dc2e | 2017-01-10 01:27:41 | [diff] [blame] | 1019 | network_time::NetworkTimeTracker* tracker = |
| 1020 | g_config.Pointer()->network_time_tracker(); |
estark | 5057f82 | 2016-11-08 21:34:54 | [diff] [blame] | 1021 | if (!tracker->StartTimeFetch( |
| 1022 | base::Bind(&SSLErrorHandler::HandleCertDateInvalidErrorImpl, |
estark | 8d6c3c7 | 2016-12-14 03:55:22 | [diff] [blame] | 1023 | weak_ptr_factory_.GetWeakPtr(), now))) { |
| 1024 | HandleCertDateInvalidErrorImpl(now); |
estark | 5057f82 | 2016-11-08 21:34:54 | [diff] [blame] | 1025 | return; |
| 1026 | } |
| 1027 | |
meacer | 352dc2e | 2017-01-10 01:27:41 | [diff] [blame] | 1028 | if (g_config.Pointer()->timer_started_callback()) |
| 1029 | g_config.Pointer()->timer_started_callback()->Run(web_contents_); |
estark | 5057f82 | 2016-11-08 21:34:54 | [diff] [blame] | 1030 | } |
| 1031 | |
estark | 8d6c3c7 | 2016-12-14 03:55:22 | [diff] [blame] | 1032 | void SSLErrorHandler::HandleCertDateInvalidErrorImpl( |
| 1033 | base::TimeTicks started_handling_error) { |
| 1034 | UMA_HISTOGRAM_CUSTOM_TIMES( |
| 1035 | "interstitial.ssl_error_handler.cert_date_error_delay", |
| 1036 | base::TimeTicks::Now() - started_handling_error, |
| 1037 | base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromSeconds(4), |
| 1038 | 50); |
| 1039 | |
estark | 5057f82 | 2016-11-08 21:34:54 | [diff] [blame] | 1040 | timer_.Stop(); |
meacer | 352dc2e | 2017-01-10 01:27:41 | [diff] [blame] | 1041 | base::Clock* testing_clock = g_config.Pointer()->clock(); |
| 1042 | const base::Time now = |
| 1043 | testing_clock ? testing_clock->Now() : base::Time::NowFromSystemTime(); |
| 1044 | |
| 1045 | network_time::NetworkTimeTracker* tracker = |
| 1046 | g_config.Pointer()->network_time_tracker(); |
estark | 5057f82 | 2016-11-08 21:34:54 | [diff] [blame] | 1047 | ssl_errors::ClockState clock_state = ssl_errors::GetClockState(now, tracker); |
| 1048 | if (clock_state == ssl_errors::CLOCK_STATE_FUTURE || |
| 1049 | clock_state == ssl_errors::CLOCK_STATE_PAST) { |
| 1050 | ShowBadClockInterstitial(now, clock_state); |
| 1051 | return; // |this| is deleted after showing the interstitial. |
| 1052 | } |
| 1053 | ShowSSLInterstitial(); |
| 1054 | } |
Sasha Perigo | 2bfbbe90 | 2017-08-10 21:11:03 | [diff] [blame] | 1055 | |
| 1056 | // Returns true if |only_cert_error_expected| is the only error code present in |
| 1057 | // the certificate. The parameter |only_cert_error_expected| is a |
| 1058 | // net::CertStatus code representing the most serious error identified on the |
| 1059 | // certificate. For example, this could be net::CERT_STATUS_COMMON_NAME_INVALID. |
| 1060 | // This function is useful for rendering interstitials that are triggered by one |
| 1061 | // specific error code only. |
| 1062 | bool SSLErrorHandler::IsOnlyCertError( |
| 1063 | net::CertStatus only_cert_error_expected) const { |
| 1064 | const net::CertStatus other_errors = |
| 1065 | ssl_info_.cert_status ^ only_cert_error_expected; |
| 1066 | |
| 1067 | return cert_error_ == |
| 1068 | net::MapCertStatusToNetError(only_cert_error_expected) && |
| 1069 | (!net::IsCertStatusError(other_errors) || |
| 1070 | net::IsCertStatusMinorError(ssl_info_.cert_status)); |
| 1071 | } |