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/clock.h"
12 #include "base/time/time.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/browser/ssl/bad_clock_blocking_page.h"
15 #include "chrome/browser/ssl/ssl_blocking_page.h"
16 #include "chrome/browser/ssl/ssl_cert_reporter.h"
17 #include "chrome/browser/ssl/ssl_error_classification.h"
18 #include "chrome/browser/ssl/ssl_error_info.h"
19 #include "content/public/browser/notification_service.h"
20 #include "content/public/browser/notification_source.h"
21 #include "content/public/browser/render_frame_host.h"
22 #include "content/public/browser/web_contents.h"
23 #include "net/base/net_errors.h"
25 #if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
26 #include "chrome/browser/captive_portal/captive_portal_service.h"
27 #include "chrome/browser/captive_portal/captive_portal_service_factory.h"
28 #include "chrome/browser/captive_portal/captive_portal_tab_helper.h"
29 #include "chrome/browser/ssl/captive_portal_blocking_page.h"
34 // The delay in milliseconds before displaying the SSL interstitial.
35 // This can be changed in tests.
36 // - If there is a name mismatch and a suggested URL available result arrives
37 // during this time, the user is redirected to the suggester URL.
38 // - If a "captive portal detected" result arrives during this time,
39 // a captive portal interstitial is displayed.
40 // - Otherwise, an SSL interstitial is displayed.
41 int64 g_interstitial_delay_in_milliseconds
= 2000;
43 // Callback to call when the interstitial timer is started. Used for testing.
44 SSLErrorHandler::TimerStartedCallback
* g_timer_started_callback
= nullptr;
46 // The clock to use when deciding which error type to display. Used for testing.
47 base::Clock
* g_testing_clock
= nullptr;
50 enum SSLErrorHandlerEvent
{
52 SHOW_CAPTIVE_PORTAL_INTERSTITIAL_NONOVERRIDABLE
,
53 SHOW_CAPTIVE_PORTAL_INTERSTITIAL_OVERRIDABLE
,
54 SHOW_SSL_INTERSTITIAL_NONOVERRIDABLE
,
55 SHOW_SSL_INTERSTITIAL_OVERRIDABLE
,
57 WWW_MISMATCH_URL_AVAILABLE
,
58 WWW_MISMATCH_URL_NOT_AVAILABLE
,
60 SSL_ERROR_HANDLER_EVENT_COUNT
63 // Adds a message to console after navigation commits and then, deletes itself.
64 // Also deletes itself if the navigation is stopped.
65 class CommonNameMismatchRedirectObserver
66 : public content::WebContentsObserver
,
67 public content::WebContentsUserData
<CommonNameMismatchRedirectObserver
> {
69 static void AddToConsoleAfterNavigation(
70 content::WebContents
* web_contents
,
71 const std::string
& request_url_hostname
,
72 const std::string
& suggested_url_hostname
) {
73 web_contents
->SetUserData(
75 new CommonNameMismatchRedirectObserver(
76 web_contents
, request_url_hostname
, suggested_url_hostname
));
80 CommonNameMismatchRedirectObserver(content::WebContents
* web_contents
,
81 const std::string
& request_url_hostname
,
82 const std::string
& suggested_url_hostname
)
83 : WebContentsObserver(web_contents
),
84 web_contents_(web_contents
),
85 request_url_hostname_(request_url_hostname
),
86 suggested_url_hostname_(suggested_url_hostname
) {}
87 ~CommonNameMismatchRedirectObserver() override
{}
89 // WebContentsObserver:
90 void NavigationStopped() override
{
92 web_contents_
->RemoveUserData(UserDataKey());
95 void NavigationEntryCommitted(
96 const content::LoadCommittedDetails
& /* load_details */) override
{
97 web_contents_
->GetMainFrame()->AddMessageToConsole(
98 content::CONSOLE_MESSAGE_LEVEL_LOG
,
100 "Redirecting navigation %s -> %s because the server presented a "
101 "certificate valid for %s but not for %s.",
102 request_url_hostname_
.c_str(), suggested_url_hostname_
.c_str(),
103 suggested_url_hostname_
.c_str(), request_url_hostname_
.c_str()));
104 web_contents_
->RemoveUserData(UserDataKey());
107 void WebContentsDestroyed() override
{
108 web_contents_
->RemoveUserData(UserDataKey());
111 content::WebContents
* web_contents_
;
112 const std::string request_url_hostname_
;
113 const std::string suggested_url_hostname_
;
115 DISALLOW_COPY_AND_ASSIGN(CommonNameMismatchRedirectObserver
);
118 void RecordUMA(SSLErrorHandlerEvent event
) {
119 UMA_HISTOGRAM_ENUMERATION("interstitial.ssl_error_handler", event
,
120 SSL_ERROR_HANDLER_EVENT_COUNT
);
123 #if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
124 bool IsCaptivePortalInterstitialEnabled() {
125 return base::FieldTrialList::FindFullName("CaptivePortalInterstitial") ==
130 bool IsSSLCommonNameMismatchHandlingEnabled() {
131 return base::FieldTrialList::FindFullName("SSLCommonNameMismatchHandling") ==
135 bool IsErrorDueToBadClock(const base::Time
& now
, int error
) {
136 if (SSLErrorInfo::NetErrorToErrorType(error
) !=
137 SSLErrorInfo::CERT_DATE_INVALID
) {
140 return SSLErrorClassification::IsUserClockInThePast(now
) ||
141 SSLErrorClassification::IsUserClockInTheFuture(now
);
146 DEFINE_WEB_CONTENTS_USER_DATA_KEY(SSLErrorHandler
);
147 DEFINE_WEB_CONTENTS_USER_DATA_KEY(CommonNameMismatchRedirectObserver
);
149 void SSLErrorHandler::HandleSSLError(
150 content::WebContents
* web_contents
,
152 const net::SSLInfo
& ssl_info
,
153 const GURL
& request_url
,
155 scoped_ptr
<SSLCertReporter
> ssl_cert_reporter
,
156 const base::Callback
<void(bool)>& callback
) {
157 DCHECK(!FromWebContents(web_contents
));
158 SSLErrorHandler
* error_handler
=
159 new SSLErrorHandler(web_contents
, cert_error
, ssl_info
, request_url
,
160 options_mask
, ssl_cert_reporter
.Pass(), callback
);
161 web_contents
->SetUserData(UserDataKey(), error_handler
);
162 error_handler
->StartHandlingError();
166 void SSLErrorHandler::SetInterstitialDelayForTest(base::TimeDelta delay
) {
167 g_interstitial_delay_in_milliseconds
= delay
.InMilliseconds();
171 void SSLErrorHandler::SetInterstitialTimerStartedCallbackForTest(
172 TimerStartedCallback
* callback
) {
173 DCHECK(!callback
|| !callback
->is_null());
174 g_timer_started_callback
= callback
;
178 void SSLErrorHandler::SetClockForTest(base::Clock
* testing_clock
) {
179 g_testing_clock
= testing_clock
;
182 SSLErrorHandler::SSLErrorHandler(content::WebContents
* web_contents
,
184 const net::SSLInfo
& ssl_info
,
185 const GURL
& request_url
,
187 scoped_ptr
<SSLCertReporter
> ssl_cert_reporter
,
188 const base::Callback
<void(bool)>& callback
)
189 : content::WebContentsObserver(web_contents
),
190 web_contents_(web_contents
),
191 cert_error_(cert_error
),
193 request_url_(request_url
),
194 options_mask_(options_mask
),
196 profile_(Profile::FromBrowserContext(web_contents
->GetBrowserContext())),
197 ssl_cert_reporter_(ssl_cert_reporter
.Pass()) {}
199 SSLErrorHandler::~SSLErrorHandler() {
202 void SSLErrorHandler::StartHandlingError() {
203 RecordUMA(HANDLE_ALL
);
205 const base::Time now
= g_testing_clock
== nullptr
206 ? base::Time::NowFromSystemTime()
207 : g_testing_clock
->Now();
208 if (IsErrorDueToBadClock(now
, cert_error_
)) {
209 ShowBadClockInterstitial(now
);
210 return; // |this| is deleted after showing the interstitial.
213 std::vector
<std::string
> dns_names
;
214 ssl_info_
.cert
->GetDNSNames(&dns_names
);
215 DCHECK(!dns_names
.empty());
217 if (IsSSLCommonNameMismatchHandlingEnabled() &&
218 cert_error_
== net::ERR_CERT_COMMON_NAME_INVALID
&&
219 IsErrorOverridable() && GetSuggestedUrl(dns_names
, &suggested_url
)) {
220 RecordUMA(WWW_MISMATCH_FOUND
);
221 net::CertStatus extra_cert_errors
=
222 ssl_info_
.cert_status
^ net::CERT_STATUS_COMMON_NAME_INVALID
;
224 // Show the SSL intersitial if |CERT_STATUS_COMMON_NAME_INVALID| is not
225 // the only error. Need not check for captive portal in this case.
226 // (See the comment below).
227 if (net::IsCertStatusError(extra_cert_errors
) &&
228 !net::IsCertStatusMinorError(ssl_info_
.cert_status
)) {
229 ShowSSLInterstitial();
232 CheckSuggestedUrl(suggested_url
);
233 timer_
.Start(FROM_HERE
, base::TimeDelta::FromMilliseconds(
234 g_interstitial_delay_in_milliseconds
),
235 this, &SSLErrorHandler::ShowSSLInterstitial
);
236 if (g_timer_started_callback
)
237 g_timer_started_callback
->Run(web_contents_
);
239 // Do not check for a captive portal in this case, because a captive
240 // portal most likely cannot serve a valid certificate which passes the
245 #if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
246 CaptivePortalTabHelper
* captive_portal_tab_helper
=
247 CaptivePortalTabHelper::FromWebContents(web_contents_
);
248 if (captive_portal_tab_helper
) {
249 captive_portal_tab_helper
->OnSSLCertError(ssl_info_
);
252 registrar_
.Add(this, chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT
,
253 content::Source
<Profile
>(profile_
));
255 if (IsCaptivePortalInterstitialEnabled()) {
256 CheckForCaptivePortal();
257 timer_
.Start(FROM_HERE
, base::TimeDelta::FromMilliseconds(
258 g_interstitial_delay_in_milliseconds
),
259 this, &SSLErrorHandler::ShowSSLInterstitial
);
260 if (g_timer_started_callback
)
261 g_timer_started_callback
->Run(web_contents_
);
265 // Display an SSL interstitial.
266 ShowSSLInterstitial();
269 void SSLErrorHandler::CheckForCaptivePortal() {
270 #if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
271 CaptivePortalService
* captive_portal_service
=
272 CaptivePortalServiceFactory::GetForProfile(profile_
);
273 captive_portal_service
->DetectCaptivePortal();
279 bool SSLErrorHandler::GetSuggestedUrl(const std::vector
<std::string
>& dns_names
,
280 GURL
* suggested_url
) const {
281 return CommonNameMismatchHandler::GetSuggestedUrl(request_url_
, dns_names
,
285 void SSLErrorHandler::CheckSuggestedUrl(const GURL
& suggested_url
) {
286 scoped_refptr
<net::URLRequestContextGetter
> request_context(
287 profile_
->GetRequestContext());
288 common_name_mismatch_handler_
.reset(
289 new CommonNameMismatchHandler(request_url_
, request_context
));
291 common_name_mismatch_handler_
->CheckSuggestedUrl(
293 base::Bind(&SSLErrorHandler::CommonNameMismatchHandlerCallback
,
294 base::Unretained(this)));
297 void SSLErrorHandler::NavigateToSuggestedURL(const GURL
& suggested_url
) {
298 content::NavigationController::LoadURLParams
load_params(suggested_url
);
299 load_params
.transition_type
= ui::PAGE_TRANSITION_TYPED
;
300 web_contents()->GetController().LoadURLWithParams(load_params
);
303 bool SSLErrorHandler::IsErrorOverridable() const {
304 return SSLBlockingPage::IsOverridable(options_mask_
, profile_
);
307 void SSLErrorHandler::ShowCaptivePortalInterstitial(const GURL
& landing_url
) {
308 #if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
309 // Show captive portal blocking page. The interstitial owns the blocking page.
310 RecordUMA(IsErrorOverridable()
311 ? SHOW_CAPTIVE_PORTAL_INTERSTITIAL_OVERRIDABLE
312 : SHOW_CAPTIVE_PORTAL_INTERSTITIAL_NONOVERRIDABLE
);
313 (new CaptivePortalBlockingPage(web_contents_
, request_url_
, landing_url
,
314 ssl_cert_reporter_
.Pass(), ssl_info_
,
316 // Once an interstitial is displayed, no need to keep the handler around.
317 // This is the equivalent of "delete this". It also destroys the timer.
318 web_contents_
->RemoveUserData(UserDataKey());
324 void SSLErrorHandler::ShowSSLInterstitial() {
325 // Show SSL blocking page. The interstitial owns the blocking page.
326 RecordUMA(IsErrorOverridable() ? SHOW_SSL_INTERSTITIAL_OVERRIDABLE
327 : SHOW_SSL_INTERSTITIAL_NONOVERRIDABLE
);
329 (new SSLBlockingPage(web_contents_
, cert_error_
, ssl_info_
, request_url_
,
330 options_mask_
, base::Time::NowFromSystemTime(),
331 ssl_cert_reporter_
.Pass(), callback_
))
333 // Once an interstitial is displayed, no need to keep the handler around.
334 // This is the equivalent of "delete this".
335 web_contents_
->RemoveUserData(UserDataKey());
338 void SSLErrorHandler::ShowBadClockInterstitial(const base::Time
& now
) {
339 RecordUMA(SHOW_BAD_CLOCK
);
340 (new BadClockBlockingPage(web_contents_
, cert_error_
, ssl_info_
, request_url_
,
343 // Once an interstitial is displayed, no need to keep the handler around.
344 // This is the equivalent of "delete this".
345 web_contents_
->RemoveUserData(UserDataKey());
348 void SSLErrorHandler::CommonNameMismatchHandlerCallback(
349 const CommonNameMismatchHandler::SuggestedUrlCheckResult
& result
,
350 const GURL
& suggested_url
) {
352 if (result
== CommonNameMismatchHandler::SuggestedUrlCheckResult::
353 SUGGESTED_URL_AVAILABLE
) {
354 RecordUMA(WWW_MISMATCH_URL_AVAILABLE
);
355 CommonNameMismatchRedirectObserver::AddToConsoleAfterNavigation(
356 web_contents(), request_url_
.host(), suggested_url
.host());
357 NavigateToSuggestedURL(suggested_url
);
359 RecordUMA(WWW_MISMATCH_URL_NOT_AVAILABLE
);
360 ShowSSLInterstitial();
364 void SSLErrorHandler::Observe(
366 const content::NotificationSource
& source
,
367 const content::NotificationDetails
& details
) {
368 #if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
369 if (type
== chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT
) {
371 CaptivePortalService::Results
* results
=
372 content::Details
<CaptivePortalService::Results
>(details
).ptr();
373 if (results
->result
== captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL
)
374 ShowCaptivePortalInterstitial(results
->landing_url
);
376 ShowSSLInterstitial();
381 void SSLErrorHandler::DidStartNavigationToPendingEntry(
382 const GURL
& /* url */,
383 content::NavigationController::ReloadType
/* reload_type */) {
384 // Destroy the error handler on all new navigations. This ensures that the
385 // handler is properly recreated when a hanging page is navigated to an SSL
386 // error, even when the tab's WebContents doesn't change.
387 DeleteSSLErrorHandler();
390 void SSLErrorHandler::NavigationStopped() {
391 // Destroy the error handler when the page load is stopped.
392 DeleteSSLErrorHandler();
395 void SSLErrorHandler::DeleteSSLErrorHandler() {
396 // Need to explicity deny the certificate via the callback, otherwise memory
398 if (!callback_
.is_null()) {
399 base::ResetAndReturn(&callback_
).Run(false);
401 if (common_name_mismatch_handler_
) {
402 common_name_mismatch_handler_
->Cancel();
403 common_name_mismatch_handler_
.reset();
405 // Deletes |this| and also destroys the timer.
406 web_contents_
->RemoveUserData(UserDataKey());