12983f5b9c608c0dd88ef3fdfec7ae53d5dbc00a
[platform/framework/web/crosswalk.git] / src / chrome / browser / ssl / ssl_error_classification.cc
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 <vector>
6
7 #include "chrome/browser/ssl/ssl_error_classification.h"
8
9 #include "base/build_time.h"
10 #include "base/metrics/field_trial.h"
11 #include "base/metrics/histogram.h"
12 #include "base/strings/string_split.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/time/time.h"
15 #include "chrome/browser/browser_process.h"
16 #include "chrome/browser/chrome_notification_types.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/ssl/ssl_error_info.h"
19 #include "content/public/browser/notification_service.h"
20 #include "content/public/browser/web_contents.h"
21 #include "net/base/net_util.h"
22 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
23 #include "net/cert/x509_cert_types.h"
24 #include "net/cert/x509_certificate.h"
25 #include "url/gurl.h"
26
27 #if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
28 #include "chrome/browser/captive_portal/captive_portal_service.h"
29 #include "chrome/browser/captive_portal/captive_portal_service_factory.h"
30 #endif
31
32 #if defined(OS_WIN)
33 #include "base/win/win_util.h"
34 #include "base/win/windows_version.h"
35 #endif
36
37 using base::Time;
38 using base::TimeTicks;
39 using base::TimeDelta;
40
41 namespace {
42
43 // Events for UMA. Do not reorder or change!
44 enum SSLInterstitialCause {
45   CLOCK_PAST,
46   CLOCK_FUTURE,
47   WWW_SUBDOMAIN_MATCH,
48   SUBDOMAIN_MATCH,
49   SUBDOMAIN_INVERSE_MATCH,
50   SUBDOMAIN_OUTSIDE_WILDCARD,
51   HOST_NAME_NOT_KNOWN_TLD,
52   LIKELY_MULTI_TENANT_HOSTING,
53   UNUSED_INTERSTITIAL_CAUSE_ENTRY,
54 };
55
56 // Events for UMA. Do not reorder or change!
57 enum SSLInterstitialCauseCaptivePortal {
58   CAPTIVE_PORTAL_DETECTION_ENABLED,
59   CAPTIVE_PORTAL_DETECTION_ENABLED_OVERRIDABLE,
60   CAPTIVE_PORTAL_PROBE_COMPLETED,
61   CAPTIVE_PORTAL_PROBE_COMPLETED_OVERRIDABLE,
62   CAPTIVE_PORTAL_NO_RESPONSE,
63   CAPTIVE_PORTAL_NO_RESPONSE_OVERRIDABLE,
64   CAPTIVE_PORTAL_DETECTED,
65   CAPTIVE_PORTAL_DETECTED_OVERRIDABLE,
66   UNUSED_CAPTIVE_PORTAL_EVENT,
67 };
68
69 void RecordSSLInterstitialSeverityScore(float ssl_severity_score,
70                                         int cert_error) {
71   if (SSLErrorInfo::NetErrorToErrorType(cert_error) ==
72       SSLErrorInfo::CERT_DATE_INVALID) {
73     UMA_HISTOGRAM_COUNTS_100("interstitial.ssl.severity_score.date_invalid",
74                              static_cast<int>(ssl_severity_score * 100));
75   } else if (SSLErrorInfo::NetErrorToErrorType(cert_error) ==
76       SSLErrorInfo::CERT_COMMON_NAME_INVALID) {
77     UMA_HISTOGRAM_COUNTS_100(
78         "interstitial.ssl.severity_score.common_name_invalid",
79         static_cast<int>(ssl_severity_score * 100));
80   }
81 }
82
83 // Scores/weights which will be constant through all the SSL error types.
84 static const float kServerWeight = 0.5f;
85 static const float kClientWeight = 0.5f;
86
87 void RecordSSLInterstitialCause(bool overridable, SSLInterstitialCause event) {
88   if (overridable) {
89     UMA_HISTOGRAM_ENUMERATION("interstitial.ssl.cause.overridable", event,
90                               UNUSED_INTERSTITIAL_CAUSE_ENTRY);
91   } else {
92     UMA_HISTOGRAM_ENUMERATION("interstitial.ssl.cause.nonoverridable", event,
93                               UNUSED_INTERSTITIAL_CAUSE_ENTRY);
94   }
95 }
96
97 #if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
98 void RecordCaptivePortalEventStats(SSLInterstitialCauseCaptivePortal event) {
99   UMA_HISTOGRAM_ENUMERATION("interstitial.ssl.captive_portal",
100                             event,
101                             UNUSED_CAPTIVE_PORTAL_EVENT);
102 }
103 #endif
104
105 int GetLevensteinDistance(const std::string& str1,
106                           const std::string& str2) {
107   if (str1 == str2)
108     return 0;
109   if (str1.size() == 0)
110     return str2.size();
111   if (str2.size() == 0)
112     return str1.size();
113   std::vector<int> kFirstRow(str2.size() + 1, 0);
114   std::vector<int> kSecondRow(str2.size() + 1, 0);
115
116   for (size_t i = 0; i < kFirstRow.size(); ++i)
117     kFirstRow[i] = i;
118   for (size_t i = 0; i < str1.size(); ++i) {
119     kSecondRow[0] = i + 1;
120     for (size_t j = 0; j < str2.size(); ++j) {
121       int cost = str1[i] == str2[j] ? 0 : 1;
122       kSecondRow[j+1] = std::min(std::min(
123           kSecondRow[j] + 1, kFirstRow[j + 1] + 1), kFirstRow[j] + cost);
124     }
125     for (size_t j = 0; j < kFirstRow.size(); j++)
126       kFirstRow[j] = kSecondRow[j];
127   }
128   return kSecondRow[str2.size()];
129 }
130
131 } // namespace
132
133 SSLErrorClassification::SSLErrorClassification(
134     content::WebContents* web_contents,
135     const base::Time& current_time,
136     const GURL& url,
137     int cert_error,
138     const net::X509Certificate& cert)
139   : web_contents_(web_contents),
140     current_time_(current_time),
141     request_url_(url),
142     cert_error_(cert_error),
143     cert_(cert),
144     captive_portal_detection_enabled_(false),
145     captive_portal_probe_completed_(false),
146     captive_portal_no_response_(false),
147     captive_portal_detected_(false) {
148 #if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
149   Profile* profile = Profile::FromBrowserContext(
150       web_contents_->GetBrowserContext());
151   CaptivePortalService* captive_portal_service =
152       CaptivePortalServiceFactory::GetForProfile(profile);
153   captive_portal_detection_enabled_ = captive_portal_service->enabled();
154   captive_portal_service->DetectCaptivePortal();
155   registrar_.Add(this,
156                  chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT,
157                  content::Source<Profile>(profile));
158 #endif
159 }
160
161 SSLErrorClassification::~SSLErrorClassification() { }
162
163 void SSLErrorClassification::RecordCaptivePortalUMAStatistics(
164     bool overridable) const {
165 #if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
166   if (captive_portal_detection_enabled_)
167     RecordCaptivePortalEventStats(
168         overridable ?
169         CAPTIVE_PORTAL_DETECTION_ENABLED_OVERRIDABLE :
170         CAPTIVE_PORTAL_DETECTION_ENABLED);
171   if (captive_portal_probe_completed_)
172     RecordCaptivePortalEventStats(
173         overridable ?
174         CAPTIVE_PORTAL_PROBE_COMPLETED_OVERRIDABLE :
175         CAPTIVE_PORTAL_PROBE_COMPLETED);
176   // Log only one of portal detected and no response results.
177   if (captive_portal_detected_)
178     RecordCaptivePortalEventStats(
179         overridable ?
180         CAPTIVE_PORTAL_DETECTED_OVERRIDABLE :
181         CAPTIVE_PORTAL_DETECTED);
182   else if (captive_portal_no_response_)
183     RecordCaptivePortalEventStats(
184         overridable ?
185         CAPTIVE_PORTAL_NO_RESPONSE_OVERRIDABLE :
186         CAPTIVE_PORTAL_NO_RESPONSE);
187 #endif
188 }
189
190 void SSLErrorClassification::InvalidDateSeverityScore() {
191   SSLErrorInfo::ErrorType type =
192       SSLErrorInfo::NetErrorToErrorType(cert_error_);
193   DCHECK(type == SSLErrorInfo::CERT_DATE_INVALID);
194
195   // Client-side characteristics. Check whether or not the system's clock is
196   // wrong and whether or not the user has encountered this error before.
197   float severity_date_score = 0.0f;
198
199   static const float kCertificateExpiredWeight = 0.3f;
200   static const float kNotYetValidWeight = 0.2f;
201
202   static const float kSystemClockWeight = 0.75f;
203   static const float kSystemClockWrongWeight = 0.1f;
204   static const float kSystemClockRightWeight = 1.0f;
205
206   if (IsUserClockInThePast(current_time_)  ||
207       IsUserClockInTheFuture(current_time_)) {
208     severity_date_score += kClientWeight * kSystemClockWeight *
209         kSystemClockWrongWeight;
210   } else {
211     severity_date_score += kClientWeight * kSystemClockWeight *
212         kSystemClockRightWeight;
213   }
214   // TODO(felt): (crbug.com/393262) Check website settings.
215
216   // Server-side characteristics. Check whether the certificate has expired or
217   // is not yet valid. If the certificate has expired then factor the time which
218   // has passed since expiry.
219   if (cert_.HasExpired()) {
220     severity_date_score += kServerWeight * kCertificateExpiredWeight *
221         CalculateScoreTimePassedSinceExpiry();
222   }
223   if (current_time_ < cert_.valid_start())
224     severity_date_score += kServerWeight * kNotYetValidWeight;
225
226   RecordSSLInterstitialSeverityScore(severity_date_score, cert_error_);
227 }
228
229 void SSLErrorClassification::InvalidCommonNameSeverityScore() {
230   SSLErrorInfo::ErrorType type =
231       SSLErrorInfo::NetErrorToErrorType(cert_error_);
232   DCHECK(type == SSLErrorInfo::CERT_COMMON_NAME_INVALID);
233   float severity_name_score = 0.0f;
234
235   static const float kWWWDifferenceWeight = 0.3f;
236   static const float kNameUnderAnyNamesWeight = 0.2f;
237   static const float kAnyNamesUnderNameWeight = 1.0f;
238   static const float kLikelyMultiTenantHostingWeight = 0.1f;
239
240   std::string host_name = request_url_.host();
241   if (IsHostNameKnownTLD(host_name)) {
242     Tokens host_name_tokens = Tokenize(host_name);
243     if (IsWWWSubDomainMatch())
244       severity_name_score += kServerWeight * kWWWDifferenceWeight;
245     if (IsSubDomainOutsideWildcard(host_name_tokens))
246       severity_name_score += kServerWeight * kWWWDifferenceWeight;
247
248     std::vector<std::string> dns_names;
249     cert_.GetDNSNames(&dns_names);
250     std::vector<Tokens> dns_name_tokens = GetTokenizedDNSNames(dns_names);
251     if (NameUnderAnyNames(host_name_tokens, dns_name_tokens))
252       severity_name_score += kServerWeight * kNameUnderAnyNamesWeight;
253     // Inverse case is more likely to be a MITM attack.
254     if (AnyNamesUnderName(dns_name_tokens, host_name_tokens))
255       severity_name_score += kServerWeight * kAnyNamesUnderNameWeight;
256     if (IsCertLikelyFromMultiTenantHosting())
257       severity_name_score += kServerWeight * kLikelyMultiTenantHostingWeight;
258   }
259
260   static const float kEnvironmentWeight = 0.25f;
261
262   severity_name_score += kClientWeight * kEnvironmentWeight *
263       CalculateScoreEnvironments();
264
265   RecordSSLInterstitialSeverityScore(severity_name_score, cert_error_);
266 }
267
268 void SSLErrorClassification::RecordUMAStatistics(
269     bool overridable) const {
270   SSLErrorInfo::ErrorType type =
271       SSLErrorInfo::NetErrorToErrorType(cert_error_);
272   switch (type) {
273     case SSLErrorInfo::CERT_DATE_INVALID: {
274       if (IsUserClockInThePast(base::Time::NowFromSystemTime()))
275         RecordSSLInterstitialCause(overridable, CLOCK_PAST);
276       if (IsUserClockInTheFuture(base::Time::NowFromSystemTime()))
277         RecordSSLInterstitialCause(overridable, CLOCK_FUTURE);
278       break;
279     }
280     case SSLErrorInfo::CERT_COMMON_NAME_INVALID: {
281       std::string host_name = request_url_.host();
282       if (IsHostNameKnownTLD(host_name)) {
283         Tokens host_name_tokens = Tokenize(host_name);
284         if (IsWWWSubDomainMatch())
285           RecordSSLInterstitialCause(overridable, WWW_SUBDOMAIN_MATCH);
286         if (IsSubDomainOutsideWildcard(host_name_tokens))
287           RecordSSLInterstitialCause(overridable, SUBDOMAIN_OUTSIDE_WILDCARD);
288         std::vector<std::string> dns_names;
289         cert_.GetDNSNames(&dns_names);
290         std::vector<Tokens> dns_name_tokens = GetTokenizedDNSNames(dns_names);
291         if (NameUnderAnyNames(host_name_tokens, dns_name_tokens))
292           RecordSSLInterstitialCause(overridable, SUBDOMAIN_MATCH);
293         if (AnyNamesUnderName(dns_name_tokens, host_name_tokens))
294           RecordSSLInterstitialCause(overridable, SUBDOMAIN_INVERSE_MATCH);
295         if (IsCertLikelyFromMultiTenantHosting())
296           RecordSSLInterstitialCause(overridable, LIKELY_MULTI_TENANT_HOSTING);
297       } else {
298          RecordSSLInterstitialCause(overridable, HOST_NAME_NOT_KNOWN_TLD);
299       }
300       break;
301     }
302     default: {
303       break;
304     }
305   }
306 }
307
308 base::TimeDelta SSLErrorClassification::TimePassedSinceExpiry() const {
309   base::TimeDelta delta = current_time_ - cert_.valid_expiry();
310   return delta;
311 }
312
313 float SSLErrorClassification::CalculateScoreTimePassedSinceExpiry() const {
314   base::TimeDelta delta = TimePassedSinceExpiry();
315   int64 time_passed = delta.InDays();
316   const int64 kHighThreshold = 7;
317   const int64 kLowThreshold = 4;
318   static const float kHighThresholdWeight = 0.4f;
319   static const float kMediumThresholdWeight = 0.3f;
320   static const float kLowThresholdWeight = 0.2f;
321   if (time_passed >= kHighThreshold)
322     return kHighThresholdWeight;
323   else if (time_passed >= kLowThreshold)
324     return kMediumThresholdWeight;
325   else
326     return kLowThresholdWeight;
327 }
328
329 float SSLErrorClassification::CalculateScoreEnvironments() const {
330   static const float kWifiWeight = 0.7f;
331   static const float kCellularWeight = 0.7f;
332   static const float kEthernetWeight = 0.7f;
333   static const float kOtherWeight = 0.7f;
334   net::NetworkChangeNotifier::ConnectionType type =
335       net::NetworkChangeNotifier::GetConnectionType();
336   if (type == net::NetworkChangeNotifier::CONNECTION_WIFI)
337     return kWifiWeight;
338   if (type == net::NetworkChangeNotifier::CONNECTION_2G ||
339       type == net::NetworkChangeNotifier::CONNECTION_3G ||
340       type == net::NetworkChangeNotifier::CONNECTION_4G ) {
341     return kCellularWeight;
342   }
343   if (type == net::NetworkChangeNotifier::CONNECTION_ETHERNET)
344     return kEthernetWeight;
345 #if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
346   // Assume if captive portals are detected then the user is connected using a
347   // hot spot.
348   static const float kHotspotWeight = 0.2f;
349   if (captive_portal_probe_completed_ && captive_portal_detected_)
350       return kHotspotWeight;
351 #endif
352   return kOtherWeight;
353 }
354
355 bool SSLErrorClassification::IsUserClockInThePast(const base::Time& time_now) {
356   base::Time build_time = base::GetBuildTime();
357   if (time_now < build_time - base::TimeDelta::FromDays(2))
358     return true;
359   return false;
360 }
361
362 bool SSLErrorClassification::IsUserClockInTheFuture(
363     const base::Time& time_now) {
364   base::Time build_time = base::GetBuildTime();
365   if (time_now > build_time + base::TimeDelta::FromDays(365))
366     return true;
367   return false;
368 }
369
370 bool SSLErrorClassification::MaybeWindowsLacksSHA256Support() {
371 #if defined(OS_WIN)
372   return !base::win::MaybeHasSHA256Support();
373 #else
374   return false;
375 #endif
376 }
377
378 bool SSLErrorClassification::IsHostNameKnownTLD(const std::string& host_name) {
379   size_t tld_length =
380       net::registry_controlled_domains::GetRegistryLength(
381           host_name,
382           net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES,
383           net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
384   if (tld_length == 0 || tld_length == std::string::npos)
385     return false;
386   return true;
387 }
388
389 std::vector<SSLErrorClassification::Tokens> SSLErrorClassification::
390 GetTokenizedDNSNames(const std::vector<std::string>& dns_names) {
391   std::vector<std::vector<std::string>> dns_name_tokens;
392   for (size_t i = 0; i < dns_names.size(); ++i) {
393     std::vector<std::string> dns_name_token_single;
394     if (dns_names[i].empty() || dns_names[i].find('\0') != std::string::npos
395         || !(IsHostNameKnownTLD(dns_names[i]))) {
396       dns_name_token_single.push_back(std::string());
397     } else {
398       dns_name_token_single = Tokenize(dns_names[i]);
399     }
400     dns_name_tokens.push_back(dns_name_token_single);
401   }
402   return dns_name_tokens;
403 }
404
405 size_t SSLErrorClassification::FindSubDomainDifference(
406     const Tokens& potential_subdomain, const Tokens& parent) const {
407   // A check to ensure that the number of tokens in the tokenized_parent is
408   // less than the tokenized_potential_subdomain.
409   if (parent.size() >= potential_subdomain.size())
410     return 0;
411
412   size_t tokens_match = 0;
413   size_t diff_size = potential_subdomain.size() - parent.size();
414   for (size_t i = 0; i < parent.size(); ++i) {
415     if (parent[i] == potential_subdomain[i + diff_size])
416       tokens_match++;
417   }
418   if (tokens_match == parent.size())
419     return diff_size;
420   return 0;
421 }
422
423 SSLErrorClassification::Tokens SSLErrorClassification::
424 Tokenize(const std::string& name) {
425   Tokens name_tokens;
426   base::SplitStringDontTrim(name, '.', &name_tokens);
427   return name_tokens;
428 }
429
430 // We accept the inverse case for www for historical reasons.
431 bool SSLErrorClassification::IsWWWSubDomainMatch() const {
432   std::string host_name = request_url_.host();
433   if (IsHostNameKnownTLD(host_name)) {
434     std::vector<std::string> dns_names;
435     cert_.GetDNSNames(&dns_names);
436     bool result = false;
437     // Need to account for all possible domains given in the SSL certificate.
438     for (size_t i = 0; i < dns_names.size(); ++i) {
439       if (dns_names[i].empty() || dns_names[i].find('\0') != std::string::npos
440           || dns_names[i].length() == host_name.length()
441           || !(IsHostNameKnownTLD(dns_names[i]))) {
442         result = result || false;
443       } else if (dns_names[i].length() > host_name.length()) {
444         result = result ||
445             net::StripWWW(base::ASCIIToUTF16(dns_names[i])) ==
446             base::ASCIIToUTF16(host_name);
447       } else {
448         result = result ||
449             net::StripWWW(base::ASCIIToUTF16(host_name)) ==
450             base::ASCIIToUTF16(dns_names[i]);
451       }
452     }
453     return result;
454   }
455   return false;
456 }
457
458 bool SSLErrorClassification::NameUnderAnyNames(
459     const Tokens& child,
460     const std::vector<Tokens>& potential_parents) const {
461   bool result = false;
462   // Need to account for all the possible domains given in the SSL certificate.
463   for (size_t i = 0; i < potential_parents.size(); ++i) {
464     if (potential_parents[i].empty() ||
465         potential_parents[i].size() >= child.size()) {
466       result = result || false;
467     } else {
468       size_t domain_diff = FindSubDomainDifference(child,
469                                                    potential_parents[i]);
470       if (domain_diff == 1 &&  child[0] != "www")
471         result = result || true;
472     }
473   }
474   return result;
475 }
476
477 bool SSLErrorClassification::AnyNamesUnderName(
478     const std::vector<Tokens>& potential_children,
479     const Tokens& parent) const {
480   bool result = false;
481   // Need to account for all the possible domains given in the SSL certificate.
482   for (size_t i = 0; i < potential_children.size(); ++i) {
483     if (potential_children[i].empty() ||
484         potential_children[i].size() <= parent.size()) {
485       result = result || false;
486     } else {
487       size_t domain_diff = FindSubDomainDifference(potential_children[i],
488                                                    parent);
489       if (domain_diff == 1 &&  potential_children[i][0] != "www")
490         result = result || true;
491     }
492   }
493   return result;
494 }
495
496 bool SSLErrorClassification::IsSubDomainOutsideWildcard(
497     const Tokens& host_name_tokens) const {
498   std::string host_name = request_url_.host();
499   std::vector<std::string> dns_names;
500   cert_.GetDNSNames(&dns_names);
501   bool result = false;
502
503   // This method requires that the host name be longer than the dns name on
504   // the certificate.
505   for (size_t i = 0; i < dns_names.size(); ++i) {
506     const std::string& name = dns_names[i];
507     if (name.length() < 2 || name.length() >= host_name.length() ||
508         name.find('\0') != std::string::npos ||
509         !IsHostNameKnownTLD(name)
510         || name[0] != '*' || name[1] != '.') {
511       continue;
512     }
513
514     // Move past the "*.".
515     std::string extracted_dns_name = name.substr(2);
516     if (FindSubDomainDifference(
517         host_name_tokens, Tokenize(extracted_dns_name)) == 2) {
518       return true;
519     }
520   }
521   return result;
522 }
523
524 bool SSLErrorClassification::IsCertLikelyFromMultiTenantHosting() const {
525   std::string host_name = request_url_.host();
526   std::vector<std::string> dns_names;
527   std::vector<std::string> dns_names_domain;
528   cert_.GetDNSNames(&dns_names);
529   size_t dns_names_size = dns_names.size();
530
531   // If there is only 1 DNS name then it is definitely not a shared certificate.
532   if (dns_names_size == 0 || dns_names_size == 1)
533     return false;
534
535   // Check to see if all the domains in the SAN field in the SSL certificate are
536   // the same or not.
537   for (size_t i = 0; i < dns_names_size; ++i) {
538     dns_names_domain.push_back(
539         net::registry_controlled_domains::
540         GetDomainAndRegistry(
541             dns_names[i],
542             net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES));
543   }
544   for (size_t i = 1; i < dns_names_domain.size(); ++i) {
545     if (dns_names_domain[i] != dns_names_domain[0])
546       return false;
547   }
548
549   // If the number of DNS names is more than 5 then assume that it is a shared
550   // certificate.
551   static const int kDistinctNameThreshold = 5;
552   if (dns_names_size > kDistinctNameThreshold)
553     return true;
554
555   // Heuristic - The edit distance between all the strings should be at least 5
556   // for it to be counted as a shared SSLCertificate. If even one pair of
557   // strings edit distance is below 5 then the certificate is no longer
558   // considered as a shared certificate. Include the host name in the URL also
559   // while comparing.
560   dns_names.push_back(host_name);
561   static const int kMinimumEditDsitance = 5;
562   for (size_t i = 0; i < dns_names_size; ++i) {
563     for (size_t j = i + 1; j < dns_names_size; ++j) {
564       int edit_distance = GetLevensteinDistance(dns_names[i], dns_names[j]);
565       if (edit_distance < kMinimumEditDsitance)
566         return false;
567     }
568   }
569   return true;
570 }
571
572 void SSLErrorClassification::Observe(
573     int type,
574     const content::NotificationSource& source,
575     const content::NotificationDetails& details) {
576 #if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
577   // When detection is disabled, captive portal service always sends
578   // RESULT_INTERNET_CONNECTED. Ignore any probe results in that case.
579   if (!captive_portal_detection_enabled_)
580     return;
581   if (type == chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT) {
582     captive_portal_probe_completed_ = true;
583     CaptivePortalService::Results* results =
584         content::Details<CaptivePortalService::Results>(
585             details).ptr();
586     // If a captive portal was detected at any point when the interstitial was
587     // displayed, assume that the interstitial was caused by a captive portal.
588     // Example scenario:
589     // 1- Interstitial displayed and captive portal detected, setting the flag.
590     // 2- Captive portal detection automatically opens portal login page.
591     // 3- User logs in on the portal login page.
592     // A notification will be received here for RESULT_INTERNET_CONNECTED. Make
593     // sure we don't clear the captive protal flag, since the interstitial was
594     // potentially caused by the captive portal.
595     captive_portal_detected_ = captive_portal_detected_ ||
596         (results->result == captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL);
597     // Also keep track of non-HTTP portals and error cases.
598     captive_portal_no_response_ = captive_portal_no_response_ ||
599         (results->result == captive_portal::RESULT_NO_RESPONSE);
600   }
601 #endif
602 }