Make sure the win_chromium_gn_x64_dbg bot is using symbol_level=1
[chromium-blink-merge.git] / chrome / browser / ssl / ssl_error_handler.cc
blobe19b02bd5bad39066ca22ee530fd9aad1cb6c7b6
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.
5 #include "chrome/browser/ssl/ssl_error_handler.h"
7 #include "base/callback_helpers.h"
8 #include "base/metrics/field_trial.h"
9 #include "base/metrics/histogram.h"
10 #include "base/strings/stringprintf.h"
11 #include "base/time/time.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/browser/ssl/ssl_blocking_page.h"
14 #include "chrome/browser/ssl/ssl_cert_reporter.h"
15 #include "chrome/browser/ssl/ssl_error_classification.h"
16 #include "content/public/browser/notification_service.h"
17 #include "content/public/browser/notification_source.h"
18 #include "content/public/browser/render_frame_host.h"
19 #include "content/public/browser/web_contents.h"
20 #include "net/base/net_errors.h"
22 #if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
23 #include "chrome/browser/captive_portal/captive_portal_service.h"
24 #include "chrome/browser/captive_portal/captive_portal_service_factory.h"
25 #include "chrome/browser/captive_portal/captive_portal_tab_helper.h"
26 #include "chrome/browser/ssl/captive_portal_blocking_page.h"
27 #endif
29 namespace {
31 // The delay in milliseconds before displaying the SSL interstitial.
32 // This can be changed in tests.
33 // - If there is a name mismatch and a suggested URL available result arrives
34 // during this time, the user is redirected to the suggester URL.
35 // - If a "captive portal detected" result arrives during this time,
36 // a captive portal interstitial is displayed.
37 // - Otherwise, an SSL interstitial is displayed.
38 int64 g_interstitial_delay_in_milliseconds = 2000;
40 // Callback to call when the interstitial timer is started. Used for testing.
41 SSLErrorHandler::TimerStartedCallback* g_timer_started_callback = nullptr;
43 // Events for UMA.
44 enum SSLErrorHandlerEvent {
45 HANDLE_ALL,
46 SHOW_CAPTIVE_PORTAL_INTERSTITIAL_NONOVERRIDABLE,
47 SHOW_CAPTIVE_PORTAL_INTERSTITIAL_OVERRIDABLE,
48 SHOW_SSL_INTERSTITIAL_NONOVERRIDABLE,
49 SHOW_SSL_INTERSTITIAL_OVERRIDABLE,
50 WWW_MISMATCH_FOUND,
51 WWW_MISMATCH_URL_AVAILABLE,
52 WWW_MISMATCH_URL_NOT_AVAILABLE,
53 SSL_ERROR_HANDLER_EVENT_COUNT
56 // Adds a message to console after navigation commits and then, deletes itself.
57 // Also deletes itself if the navigation is stopped.
58 class CommonNameMismatchRedirectObserver
59 : public content::WebContentsObserver,
60 public content::WebContentsUserData<CommonNameMismatchRedirectObserver> {
61 public:
62 static void AddToConsoleAfterNavigation(
63 content::WebContents* web_contents,
64 const std::string& request_url_hostname,
65 const std::string& suggested_url_hostname) {
66 web_contents->SetUserData(
67 UserDataKey(),
68 new CommonNameMismatchRedirectObserver(
69 web_contents, request_url_hostname, suggested_url_hostname));
72 private:
73 CommonNameMismatchRedirectObserver(content::WebContents* web_contents,
74 const std::string& request_url_hostname,
75 const std::string& suggested_url_hostname)
76 : WebContentsObserver(web_contents),
77 web_contents_(web_contents),
78 request_url_hostname_(request_url_hostname),
79 suggested_url_hostname_(suggested_url_hostname) {}
80 ~CommonNameMismatchRedirectObserver() override {}
82 // WebContentsObserver:
83 void NavigationStopped() override {
84 // Deletes |this|.
85 web_contents_->RemoveUserData(UserDataKey());
88 void NavigationEntryCommitted(
89 const content::LoadCommittedDetails& /* load_details */) override {
90 web_contents_->GetMainFrame()->AddMessageToConsole(
91 content::CONSOLE_MESSAGE_LEVEL_LOG,
92 base::StringPrintf(
93 "Redirecting navigation %s -> %s because the server presented a "
94 "certificate valid for %s but not for %s.",
95 request_url_hostname_.c_str(), suggested_url_hostname_.c_str(),
96 suggested_url_hostname_.c_str(), request_url_hostname_.c_str()));
97 web_contents_->RemoveUserData(UserDataKey());
100 void WebContentsDestroyed() override {
101 web_contents_->RemoveUserData(UserDataKey());
104 content::WebContents* web_contents_;
105 const std::string request_url_hostname_;
106 const std::string suggested_url_hostname_;
108 DISALLOW_COPY_AND_ASSIGN(CommonNameMismatchRedirectObserver);
111 void RecordUMA(SSLErrorHandlerEvent event) {
112 UMA_HISTOGRAM_ENUMERATION("interstitial.ssl_error_handler", event,
113 SSL_ERROR_HANDLER_EVENT_COUNT);
116 #if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
117 bool IsCaptivePortalInterstitialEnabled() {
118 return base::FieldTrialList::FindFullName("CaptivePortalInterstitial") ==
119 "Enabled";
121 #endif
123 bool IsSSLCommonNameMismatchHandlingEnabled() {
124 return base::FieldTrialList::FindFullName("SSLCommonNameMismatchHandling") ==
125 "Enabled";
128 } // namespace
130 DEFINE_WEB_CONTENTS_USER_DATA_KEY(SSLErrorHandler);
131 DEFINE_WEB_CONTENTS_USER_DATA_KEY(CommonNameMismatchRedirectObserver);
133 void SSLErrorHandler::HandleSSLError(
134 content::WebContents* web_contents,
135 int cert_error,
136 const net::SSLInfo& ssl_info,
137 const GURL& request_url,
138 int options_mask,
139 scoped_ptr<SSLCertReporter> ssl_cert_reporter,
140 const base::Callback<void(bool)>& callback) {
141 DCHECK(!FromWebContents(web_contents));
142 SSLErrorHandler* error_handler =
143 new SSLErrorHandler(web_contents, cert_error, ssl_info, request_url,
144 options_mask, ssl_cert_reporter.Pass(), callback);
145 web_contents->SetUserData(UserDataKey(), error_handler);
146 error_handler->StartHandlingError();
149 // static
150 void SSLErrorHandler::SetInterstitialDelayForTest(base::TimeDelta delay) {
151 g_interstitial_delay_in_milliseconds = delay.InMilliseconds();
154 // static
155 void SSLErrorHandler::SetInterstitialTimerStartedCallbackForTest(
156 TimerStartedCallback* callback) {
157 DCHECK(!callback || !callback->is_null());
158 g_timer_started_callback = callback;
161 SSLErrorHandler::SSLErrorHandler(content::WebContents* web_contents,
162 int cert_error,
163 const net::SSLInfo& ssl_info,
164 const GURL& request_url,
165 int options_mask,
166 scoped_ptr<SSLCertReporter> ssl_cert_reporter,
167 const base::Callback<void(bool)>& callback)
168 : content::WebContentsObserver(web_contents),
169 web_contents_(web_contents),
170 cert_error_(cert_error),
171 ssl_info_(ssl_info),
172 request_url_(request_url),
173 options_mask_(options_mask),
174 callback_(callback),
175 profile_(Profile::FromBrowserContext(web_contents->GetBrowserContext())),
176 ssl_cert_reporter_(ssl_cert_reporter.Pass()) {}
178 SSLErrorHandler::~SSLErrorHandler() {
181 void SSLErrorHandler::StartHandlingError() {
182 RecordUMA(HANDLE_ALL);
184 std::vector<std::string> dns_names;
185 ssl_info_.cert->GetDNSNames(&dns_names);
186 DCHECK(!dns_names.empty());
187 GURL suggested_url;
188 if (IsSSLCommonNameMismatchHandlingEnabled() &&
189 cert_error_ == net::ERR_CERT_COMMON_NAME_INVALID &&
190 IsErrorOverridable() && GetSuggestedUrl(dns_names, &suggested_url)) {
191 RecordUMA(WWW_MISMATCH_FOUND);
192 net::CertStatus extra_cert_errors =
193 ssl_info_.cert_status ^ net::CERT_STATUS_COMMON_NAME_INVALID;
195 // Show the SSL intersitial if |CERT_STATUS_COMMON_NAME_INVALID| is not
196 // the only error. Need not check for captive portal in this case.
197 // (See the comment below).
198 if (net::IsCertStatusError(extra_cert_errors) &&
199 !net::IsCertStatusMinorError(ssl_info_.cert_status)) {
200 ShowSSLInterstitial();
201 return;
203 CheckSuggestedUrl(suggested_url);
204 timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(
205 g_interstitial_delay_in_milliseconds),
206 this, &SSLErrorHandler::ShowSSLInterstitial);
207 if (g_timer_started_callback)
208 g_timer_started_callback->Run(web_contents_);
210 // Do not check for a captive portal in this case, because a captive
211 // portal most likely cannot serve a valid certificate which passes the
212 // similarity check.
213 return;
216 #if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
217 CaptivePortalTabHelper* captive_portal_tab_helper =
218 CaptivePortalTabHelper::FromWebContents(web_contents_);
219 if (captive_portal_tab_helper) {
220 captive_portal_tab_helper->OnSSLCertError(ssl_info_);
223 registrar_.Add(this, chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT,
224 content::Source<Profile>(profile_));
226 if (IsCaptivePortalInterstitialEnabled()) {
227 CheckForCaptivePortal();
228 timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(
229 g_interstitial_delay_in_milliseconds),
230 this, &SSLErrorHandler::ShowSSLInterstitial);
231 if (g_timer_started_callback)
232 g_timer_started_callback->Run(web_contents_);
233 return;
235 #endif
236 // Display an SSL interstitial.
237 ShowSSLInterstitial();
240 void SSLErrorHandler::CheckForCaptivePortal() {
241 #if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
242 CaptivePortalService* captive_portal_service =
243 CaptivePortalServiceFactory::GetForProfile(profile_);
244 captive_portal_service->DetectCaptivePortal();
245 #else
246 NOTREACHED();
247 #endif
250 bool SSLErrorHandler::GetSuggestedUrl(const std::vector<std::string>& dns_names,
251 GURL* suggested_url) const {
252 return CommonNameMismatchHandler::GetSuggestedUrl(request_url_, dns_names,
253 suggested_url);
256 void SSLErrorHandler::CheckSuggestedUrl(const GURL& suggested_url) {
257 scoped_refptr<net::URLRequestContextGetter> request_context(
258 profile_->GetRequestContext());
259 common_name_mismatch_handler_.reset(
260 new CommonNameMismatchHandler(request_url_, request_context));
262 common_name_mismatch_handler_->CheckSuggestedUrl(
263 suggested_url,
264 base::Bind(&SSLErrorHandler::CommonNameMismatchHandlerCallback,
265 base::Unretained(this)));
268 void SSLErrorHandler::NavigateToSuggestedURL(const GURL& suggested_url) {
269 content::NavigationController::LoadURLParams load_params(suggested_url);
270 load_params.transition_type = ui::PAGE_TRANSITION_TYPED;
271 web_contents()->GetController().LoadURLWithParams(load_params);
274 bool SSLErrorHandler::IsErrorOverridable() const {
275 return SSLBlockingPage::IsOverridable(options_mask_, profile_);
278 void SSLErrorHandler::ShowCaptivePortalInterstitial(const GURL& landing_url) {
279 #if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
280 // Show captive portal blocking page. The interstitial owns the blocking page.
281 RecordUMA(IsErrorOverridable()
282 ? SHOW_CAPTIVE_PORTAL_INTERSTITIAL_OVERRIDABLE
283 : SHOW_CAPTIVE_PORTAL_INTERSTITIAL_NONOVERRIDABLE);
284 (new CaptivePortalBlockingPage(web_contents_, request_url_, landing_url,
285 ssl_cert_reporter_.Pass(), ssl_info_,
286 callback_))->Show();
287 // Once an interstitial is displayed, no need to keep the handler around.
288 // This is the equivalent of "delete this". It also destroys the timer.
289 web_contents_->RemoveUserData(UserDataKey());
290 #else
291 NOTREACHED();
292 #endif
295 void SSLErrorHandler::ShowSSLInterstitial() {
296 // Show SSL blocking page. The interstitial owns the blocking page.
297 RecordUMA(IsErrorOverridable() ? SHOW_SSL_INTERSTITIAL_OVERRIDABLE
298 : SHOW_SSL_INTERSTITIAL_NONOVERRIDABLE);
300 (new SSLBlockingPage(web_contents_, cert_error_, ssl_info_, request_url_,
301 options_mask_, base::Time::NowFromSystemTime(),
302 ssl_cert_reporter_.Pass(), callback_))
303 ->Show();
304 // Once an interstitial is displayed, no need to keep the handler around.
305 // This is the equivalent of "delete this".
306 web_contents_->RemoveUserData(UserDataKey());
309 void SSLErrorHandler::CommonNameMismatchHandlerCallback(
310 const CommonNameMismatchHandler::SuggestedUrlCheckResult& result,
311 const GURL& suggested_url) {
312 timer_.Stop();
313 if (result == CommonNameMismatchHandler::SuggestedUrlCheckResult::
314 SUGGESTED_URL_AVAILABLE) {
315 RecordUMA(WWW_MISMATCH_URL_AVAILABLE);
316 CommonNameMismatchRedirectObserver::AddToConsoleAfterNavigation(
317 web_contents(), request_url_.host(), suggested_url.host());
318 NavigateToSuggestedURL(suggested_url);
319 } else {
320 RecordUMA(WWW_MISMATCH_URL_NOT_AVAILABLE);
321 ShowSSLInterstitial();
325 void SSLErrorHandler::Observe(
326 int type,
327 const content::NotificationSource& source,
328 const content::NotificationDetails& details) {
329 #if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
330 if (type == chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT) {
331 timer_.Stop();
332 CaptivePortalService::Results* results =
333 content::Details<CaptivePortalService::Results>(details).ptr();
334 if (results->result == captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL)
335 ShowCaptivePortalInterstitial(results->landing_url);
336 else
337 ShowSSLInterstitial();
339 #endif
342 void SSLErrorHandler::DidStartNavigationToPendingEntry(
343 const GURL& /* url */,
344 content::NavigationController::ReloadType /* reload_type */) {
345 // Destroy the error handler on all new navigations. This ensures that the
346 // handler is properly recreated when a hanging page is navigated to an SSL
347 // error, even when the tab's WebContents doesn't change.
348 DeleteSSLErrorHandler();
351 void SSLErrorHandler::NavigationStopped() {
352 // Destroy the error handler when the page load is stopped.
353 DeleteSSLErrorHandler();
356 void SSLErrorHandler::DeleteSSLErrorHandler() {
357 // Need to explicity deny the certificate via the callback, otherwise memory
358 // is leaked.
359 if (!callback_.is_null()) {
360 base::ResetAndReturn(&callback_).Run(false);
362 if (common_name_mismatch_handler_) {
363 common_name_mismatch_handler_->Cancel();
364 common_name_mismatch_handler_.reset();
366 // Deletes |this| and also destroys the timer.
367 web_contents_->RemoveUserData(UserDataKey());