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"
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;
44 enum SSLErrorHandlerEvent
{
46 SHOW_CAPTIVE_PORTAL_INTERSTITIAL_NONOVERRIDABLE
,
47 SHOW_CAPTIVE_PORTAL_INTERSTITIAL_OVERRIDABLE
,
48 SHOW_SSL_INTERSTITIAL_NONOVERRIDABLE
,
49 SHOW_SSL_INTERSTITIAL_OVERRIDABLE
,
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
> {
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(
68 new CommonNameMismatchRedirectObserver(
69 web_contents
, request_url_hostname
, suggested_url_hostname
));
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
{
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
,
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") ==
123 bool IsSSLCommonNameMismatchHandlingEnabled() {
124 return base::FieldTrialList::FindFullName("SSLCommonNameMismatchHandling") ==
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
,
136 const net::SSLInfo
& ssl_info
,
137 const GURL
& request_url
,
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();
150 void SSLErrorHandler::SetInterstitialDelayForTest(base::TimeDelta delay
) {
151 g_interstitial_delay_in_milliseconds
= delay
.InMilliseconds();
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
,
163 const net::SSLInfo
& ssl_info
,
164 const GURL
& request_url
,
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
),
172 request_url_(request_url
),
173 options_mask_(options_mask
),
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());
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();
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
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_
);
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();
250 bool SSLErrorHandler::GetSuggestedUrl(const std::vector
<std::string
>& dns_names
,
251 GURL
* suggested_url
) const {
252 return CommonNameMismatchHandler::GetSuggestedUrl(request_url_
, dns_names
,
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(
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_
,
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());
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_
))
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
) {
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
);
320 RecordUMA(WWW_MISMATCH_URL_NOT_AVAILABLE
);
321 ShowSSLInterstitial();
325 void SSLErrorHandler::Observe(
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
) {
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
);
337 ShowSSLInterstitial();
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
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());