Revert "Reland c91b178b07b0d - Delete dead signin code (SigninGlobalError)"
[chromium-blink-merge.git] / chrome / browser / ssl / ssl_error_classification.cc
blobdc953fa3afa0ba2ea0949d5e73e941391a7af654
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 <vector>
7 #include "chrome/browser/ssl/ssl_error_classification.h"
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"
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
32 #if defined(OS_WIN)
33 #include "base/win/win_util.h"
34 #include "base/win/windows_version.h"
35 #endif
37 using base::Time;
38 using base::TimeTicks;
39 using base::TimeDelta;
41 namespace {
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 LOCALHOST,
54 PRIVATE_URL,
55 AUTHORITY_ERROR_CAPTIVE_PORTAL,
56 SELF_SIGNED,
57 EXPIRED_RECENTLY,
58 LIKELY_SAME_DOMAIN,
59 UNUSED_INTERSTITIAL_CAUSE_ENTRY,
62 // Events for UMA. Do not reorder or change!
63 enum SSLInterstitialCauseCaptivePortal {
64 CAPTIVE_PORTAL_ALL,
65 CAPTIVE_PORTAL_DETECTION_ENABLED,
66 CAPTIVE_PORTAL_DETECTION_ENABLED_OVERRIDABLE,
67 CAPTIVE_PORTAL_PROBE_COMPLETED,
68 CAPTIVE_PORTAL_PROBE_COMPLETED_OVERRIDABLE,
69 CAPTIVE_PORTAL_NO_RESPONSE,
70 CAPTIVE_PORTAL_NO_RESPONSE_OVERRIDABLE,
71 CAPTIVE_PORTAL_DETECTED,
72 CAPTIVE_PORTAL_DETECTED_OVERRIDABLE,
73 UNUSED_CAPTIVE_PORTAL_EVENT,
76 void RecordSSLInterstitialCause(bool overridable, SSLInterstitialCause event) {
77 if (overridable) {
78 UMA_HISTOGRAM_ENUMERATION("interstitial.ssl.cause.overridable", event,
79 UNUSED_INTERSTITIAL_CAUSE_ENTRY);
80 } else {
81 UMA_HISTOGRAM_ENUMERATION("interstitial.ssl.cause.nonoverridable", event,
82 UNUSED_INTERSTITIAL_CAUSE_ENTRY);
86 #if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
87 void RecordCaptivePortalEventStats(SSLInterstitialCauseCaptivePortal event) {
88 UMA_HISTOGRAM_ENUMERATION("interstitial.ssl.captive_portal",
89 event,
90 UNUSED_CAPTIVE_PORTAL_EVENT);
92 #endif
94 int GetLevensteinDistance(const std::string& str1,
95 const std::string& str2) {
96 if (str1 == str2)
97 return 0;
98 if (str1.size() == 0)
99 return str2.size();
100 if (str2.size() == 0)
101 return str1.size();
102 std::vector<int> kFirstRow(str2.size() + 1, 0);
103 std::vector<int> kSecondRow(str2.size() + 1, 0);
105 for (size_t i = 0; i < kFirstRow.size(); ++i)
106 kFirstRow[i] = i;
107 for (size_t i = 0; i < str1.size(); ++i) {
108 kSecondRow[0] = i + 1;
109 for (size_t j = 0; j < str2.size(); ++j) {
110 int cost = str1[i] == str2[j] ? 0 : 1;
111 kSecondRow[j+1] = std::min(std::min(
112 kSecondRow[j] + 1, kFirstRow[j + 1] + 1), kFirstRow[j] + cost);
114 for (size_t j = 0; j < kFirstRow.size(); j++)
115 kFirstRow[j] = kSecondRow[j];
117 return kSecondRow[str2.size()];
120 } // namespace
122 SSLErrorClassification::SSLErrorClassification(
123 content::WebContents* web_contents,
124 const base::Time& current_time,
125 const GURL& url,
126 int cert_error,
127 const net::X509Certificate& cert)
128 : web_contents_(web_contents),
129 current_time_(current_time),
130 request_url_(url),
131 cert_error_(cert_error),
132 cert_(cert),
133 captive_portal_detection_enabled_(false),
134 captive_portal_probe_completed_(false),
135 captive_portal_no_response_(false),
136 captive_portal_detected_(false) {
137 #if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
138 Profile* profile = Profile::FromBrowserContext(
139 web_contents_->GetBrowserContext());
140 captive_portal_detection_enabled_ =
141 CaptivePortalServiceFactory::GetForProfile(profile)->enabled();
142 registrar_.Add(this,
143 chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT,
144 content::Source<Profile>(profile));
145 #endif
148 SSLErrorClassification::~SSLErrorClassification() { }
150 void SSLErrorClassification::RecordCaptivePortalUMAStatistics(
151 bool overridable) const {
152 #if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
153 RecordCaptivePortalEventStats(CAPTIVE_PORTAL_ALL);
154 if (captive_portal_detection_enabled_)
155 RecordCaptivePortalEventStats(
156 overridable ?
157 CAPTIVE_PORTAL_DETECTION_ENABLED_OVERRIDABLE :
158 CAPTIVE_PORTAL_DETECTION_ENABLED);
159 if (captive_portal_probe_completed_)
160 RecordCaptivePortalEventStats(
161 overridable ?
162 CAPTIVE_PORTAL_PROBE_COMPLETED_OVERRIDABLE :
163 CAPTIVE_PORTAL_PROBE_COMPLETED);
164 // Log only one of portal detected and no response results.
165 if (captive_portal_detected_)
166 RecordCaptivePortalEventStats(
167 overridable ?
168 CAPTIVE_PORTAL_DETECTED_OVERRIDABLE :
169 CAPTIVE_PORTAL_DETECTED);
170 else if (captive_portal_no_response_)
171 RecordCaptivePortalEventStats(
172 overridable ?
173 CAPTIVE_PORTAL_NO_RESPONSE_OVERRIDABLE :
174 CAPTIVE_PORTAL_NO_RESPONSE);
175 #endif
178 void SSLErrorClassification::RecordUMAStatistics(
179 bool overridable) const {
180 SSLErrorInfo::ErrorType type =
181 SSLErrorInfo::NetErrorToErrorType(cert_error_);
182 UMA_HISTOGRAM_ENUMERATION(
183 "interstitial.ssl_error_type", type, SSLErrorInfo::END_OF_ENUM);
184 switch (type) {
185 case SSLErrorInfo::CERT_DATE_INVALID: {
186 if (IsUserClockInThePast(base::Time::NowFromSystemTime())) {
187 RecordSSLInterstitialCause(overridable, CLOCK_PAST);
188 } else if (IsUserClockInTheFuture(base::Time::NowFromSystemTime())) {
189 RecordSSLInterstitialCause(overridable, CLOCK_FUTURE);
190 } else if (cert_.HasExpired() && TimePassedSinceExpiry().InDays() < 28) {
191 RecordSSLInterstitialCause(overridable, EXPIRED_RECENTLY);
193 break;
195 case SSLErrorInfo::CERT_COMMON_NAME_INVALID: {
196 std::string host_name = request_url_.host();
197 if (IsHostNameKnownTLD(host_name)) {
198 Tokens host_name_tokens = Tokenize(host_name);
199 if (IsWWWSubDomainMatch())
200 RecordSSLInterstitialCause(overridable, WWW_SUBDOMAIN_MATCH);
201 if (IsSubDomainOutsideWildcard(host_name_tokens))
202 RecordSSLInterstitialCause(overridable, SUBDOMAIN_OUTSIDE_WILDCARD);
203 std::vector<std::string> dns_names;
204 cert_.GetDNSNames(&dns_names);
205 std::vector<Tokens> dns_name_tokens = GetTokenizedDNSNames(dns_names);
206 if (NameUnderAnyNames(host_name_tokens, dns_name_tokens))
207 RecordSSLInterstitialCause(overridable, SUBDOMAIN_MATCH);
208 if (AnyNamesUnderName(dns_name_tokens, host_name_tokens))
209 RecordSSLInterstitialCause(overridable, SUBDOMAIN_INVERSE_MATCH);
210 if (IsCertLikelyFromMultiTenantHosting())
211 RecordSSLInterstitialCause(overridable, LIKELY_MULTI_TENANT_HOSTING);
212 if (IsCertLikelyFromSameDomain())
213 RecordSSLInterstitialCause(overridable, LIKELY_SAME_DOMAIN);
214 } else {
215 RecordSSLInterstitialCause(overridable, HOST_NAME_NOT_KNOWN_TLD);
217 break;
219 case SSLErrorInfo::CERT_AUTHORITY_INVALID: {
220 const std::string& hostname = request_url_.HostNoBrackets();
221 if (net::IsLocalhost(hostname))
222 RecordSSLInterstitialCause(overridable, LOCALHOST);
223 if (IsHostnameNonUniqueOrDotless(hostname))
224 RecordSSLInterstitialCause(overridable, PRIVATE_URL);
225 if (captive_portal_probe_completed_ && captive_portal_detected_)
226 RecordSSLInterstitialCause(overridable, AUTHORITY_ERROR_CAPTIVE_PORTAL);
227 if (net::X509Certificate::IsSelfSigned(cert_.os_cert_handle()))
228 RecordSSLInterstitialCause(overridable, SELF_SIGNED);
229 break;
231 default:
232 break;
234 UMA_HISTOGRAM_ENUMERATION("interstitial.ssl.connection_type",
235 net::NetworkChangeNotifier::GetConnectionType(),
236 net::NetworkChangeNotifier::CONNECTION_LAST);
239 base::TimeDelta SSLErrorClassification::TimePassedSinceExpiry() const {
240 base::TimeDelta delta = current_time_ - cert_.valid_expiry();
241 return delta;
244 bool SSLErrorClassification::IsUserClockInThePast(const base::Time& time_now) {
245 #if defined(DONT_EMBED_BUILD_METADATA) && !defined(OFFICIAL_BUILD)
246 return false;
247 #else
248 base::Time build_time = base::GetBuildTime();
249 if (time_now < build_time - base::TimeDelta::FromDays(2))
250 return true;
251 return false;
252 #endif
255 bool SSLErrorClassification::IsUserClockInTheFuture(
256 const base::Time& time_now) {
257 #if defined(DONT_EMBED_BUILD_METADATA) && !defined(OFFICIAL_BUILD)
258 return false;
259 #else
260 base::Time build_time = base::GetBuildTime();
261 if (time_now > build_time + base::TimeDelta::FromDays(365))
262 return true;
263 return false;
264 #endif
267 bool SSLErrorClassification::MaybeWindowsLacksSHA256Support() {
268 #if defined(OS_WIN)
269 return !base::win::MaybeHasSHA256Support();
270 #else
271 return false;
272 #endif
275 bool SSLErrorClassification::IsHostNameKnownTLD(const std::string& host_name) {
276 size_t tld_length =
277 net::registry_controlled_domains::GetRegistryLength(
278 host_name,
279 net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES,
280 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
281 if (tld_length == 0 || tld_length == std::string::npos)
282 return false;
283 return true;
286 std::vector<SSLErrorClassification::Tokens> SSLErrorClassification::
287 GetTokenizedDNSNames(const std::vector<std::string>& dns_names) {
288 std::vector<std::vector<std::string>> dns_name_tokens;
289 for (size_t i = 0; i < dns_names.size(); ++i) {
290 std::vector<std::string> dns_name_token_single;
291 if (dns_names[i].empty() || dns_names[i].find('\0') != std::string::npos
292 || !(IsHostNameKnownTLD(dns_names[i]))) {
293 dns_name_token_single.push_back(std::string());
294 } else {
295 dns_name_token_single = Tokenize(dns_names[i]);
297 dns_name_tokens.push_back(dns_name_token_single);
299 return dns_name_tokens;
302 size_t SSLErrorClassification::FindSubDomainDifference(
303 const Tokens& potential_subdomain, const Tokens& parent) const {
304 // A check to ensure that the number of tokens in the tokenized_parent is
305 // less than the tokenized_potential_subdomain.
306 if (parent.size() >= potential_subdomain.size())
307 return 0;
309 size_t tokens_match = 0;
310 size_t diff_size = potential_subdomain.size() - parent.size();
311 for (size_t i = 0; i < parent.size(); ++i) {
312 if (parent[i] == potential_subdomain[i + diff_size])
313 tokens_match++;
315 if (tokens_match == parent.size())
316 return diff_size;
317 return 0;
320 SSLErrorClassification::Tokens SSLErrorClassification::
321 Tokenize(const std::string& name) {
322 return base::SplitString(
323 name, ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
326 // We accept the inverse case for www for historical reasons.
327 bool SSLErrorClassification::GetWWWSubDomainMatch(
328 const std::string& host_name,
329 const std::vector<std::string>& dns_names,
330 std::string* www_match_host_name) {
331 if (IsHostNameKnownTLD(host_name)) {
332 // Need to account for all possible domains given in the SSL certificate.
333 for (size_t i = 0; i < dns_names.size(); ++i) {
334 if (dns_names[i].empty() ||
335 dns_names[i].find('\0') != std::string::npos ||
336 dns_names[i].length() == host_name.length() ||
337 !IsHostNameKnownTLD(dns_names[i])) {
338 continue;
339 } else if (dns_names[i].length() > host_name.length()) {
340 if (net::StripWWW(base::ASCIIToUTF16(dns_names[i])) ==
341 base::ASCIIToUTF16(host_name)) {
342 *www_match_host_name = dns_names[i];
343 return true;
345 } else {
346 if (net::StripWWW(base::ASCIIToUTF16(host_name)) ==
347 base::ASCIIToUTF16(dns_names[i])) {
348 *www_match_host_name = dns_names[i];
349 return true;
354 return false;
357 bool SSLErrorClassification::IsWWWSubDomainMatch() const {
358 const std::string& host_name = request_url_.host();
359 std::vector<std::string> dns_names;
360 cert_.GetDNSNames(&dns_names);
361 std::string www_host;
362 return GetWWWSubDomainMatch(host_name, dns_names, &www_host);
365 bool SSLErrorClassification::NameUnderAnyNames(
366 const Tokens& child,
367 const std::vector<Tokens>& potential_parents) const {
368 bool result = false;
369 // Need to account for all the possible domains given in the SSL certificate.
370 for (size_t i = 0; i < potential_parents.size(); ++i) {
371 if (potential_parents[i].empty() ||
372 potential_parents[i].size() >= child.size()) {
373 result = result || false;
374 } else {
375 size_t domain_diff = FindSubDomainDifference(child,
376 potential_parents[i]);
377 if (domain_diff == 1 && child[0] != "www")
378 result = result || true;
381 return result;
384 bool SSLErrorClassification::AnyNamesUnderName(
385 const std::vector<Tokens>& potential_children,
386 const Tokens& parent) const {
387 bool result = false;
388 // Need to account for all the possible domains given in the SSL certificate.
389 for (size_t i = 0; i < potential_children.size(); ++i) {
390 if (potential_children[i].empty() ||
391 potential_children[i].size() <= parent.size()) {
392 result = result || false;
393 } else {
394 size_t domain_diff = FindSubDomainDifference(potential_children[i],
395 parent);
396 if (domain_diff == 1 && potential_children[i][0] != "www")
397 result = result || true;
400 return result;
403 bool SSLErrorClassification::IsSubDomainOutsideWildcard(
404 const Tokens& host_name_tokens) const {
405 std::string host_name = request_url_.host();
406 std::vector<std::string> dns_names;
407 cert_.GetDNSNames(&dns_names);
408 bool result = false;
410 // This method requires that the host name be longer than the dns name on
411 // the certificate.
412 for (size_t i = 0; i < dns_names.size(); ++i) {
413 const std::string& name = dns_names[i];
414 if (name.length() < 2 || name.length() >= host_name.length() ||
415 name.find('\0') != std::string::npos ||
416 !IsHostNameKnownTLD(name)
417 || name[0] != '*' || name[1] != '.') {
418 continue;
421 // Move past the "*.".
422 std::string extracted_dns_name = name.substr(2);
423 if (FindSubDomainDifference(
424 host_name_tokens, Tokenize(extracted_dns_name)) == 2) {
425 return true;
428 return result;
431 bool SSLErrorClassification::IsCertLikelyFromMultiTenantHosting() const {
432 std::string host_name = request_url_.host();
433 std::vector<std::string> dns_names;
434 std::vector<std::string> dns_names_domain;
435 cert_.GetDNSNames(&dns_names);
436 size_t dns_names_size = dns_names.size();
438 // If there is only 1 DNS name then it is definitely not a shared certificate.
439 if (dns_names_size == 0 || dns_names_size == 1)
440 return false;
442 // Check to see if all the domains in the SAN field in the SSL certificate are
443 // the same or not.
444 for (size_t i = 0; i < dns_names_size; ++i) {
445 dns_names_domain.push_back(
446 net::registry_controlled_domains::
447 GetDomainAndRegistry(
448 dns_names[i],
449 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES));
451 for (size_t i = 1; i < dns_names_domain.size(); ++i) {
452 if (dns_names_domain[i] != dns_names_domain[0])
453 return false;
456 // If the number of DNS names is more than 5 then assume that it is a shared
457 // certificate.
458 static const int kDistinctNameThreshold = 5;
459 if (dns_names_size > kDistinctNameThreshold)
460 return true;
462 // Heuristic - The edit distance between all the strings should be at least 5
463 // for it to be counted as a shared SSLCertificate. If even one pair of
464 // strings edit distance is below 5 then the certificate is no longer
465 // considered as a shared certificate. Include the host name in the URL also
466 // while comparing.
467 dns_names.push_back(host_name);
468 static const int kMinimumEditDsitance = 5;
469 for (size_t i = 0; i < dns_names_size; ++i) {
470 for (size_t j = i + 1; j < dns_names_size; ++j) {
471 int edit_distance = GetLevensteinDistance(dns_names[i], dns_names[j]);
472 if (edit_distance < kMinimumEditDsitance)
473 return false;
476 return true;
479 bool SSLErrorClassification::IsCertLikelyFromSameDomain() const {
480 std::string host_name = request_url_.host();
481 std::vector<std::string> dns_names;
482 cert_.GetDNSNames(&dns_names);
484 dns_names.push_back(host_name);
485 std::vector<std::string> dns_names_domain;
487 for (const std::string& dns_name : dns_names) {
488 dns_names_domain.push_back(
489 net::registry_controlled_domains::GetDomainAndRegistry(
490 dns_name,
491 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES));
494 DCHECK(!dns_names_domain.empty());
495 const std::string& host_name_domain = dns_names_domain.back();
497 // Last element is the original domain. So, excluding it.
498 return std::find(dns_names_domain.begin(), dns_names_domain.end() - 1,
499 host_name_domain) != dns_names_domain.end() - 1;
502 // static
503 bool SSLErrorClassification::IsHostnameNonUniqueOrDotless(
504 const std::string& hostname) {
505 return net::IsHostnameNonUnique(hostname) ||
506 hostname.find('.') == std::string::npos;
509 void SSLErrorClassification::Observe(
510 int type,
511 const content::NotificationSource& source,
512 const content::NotificationDetails& details) {
513 #if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
514 // When detection is disabled, captive portal service always sends
515 // RESULT_INTERNET_CONNECTED. Ignore any probe results in that case.
516 if (!captive_portal_detection_enabled_)
517 return;
518 if (type == chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT) {
519 captive_portal_probe_completed_ = true;
520 CaptivePortalService::Results* results =
521 content::Details<CaptivePortalService::Results>(details).ptr();
522 // If a captive portal was detected at any point when the interstitial was
523 // displayed, assume that the interstitial was caused by a captive portal.
524 // Example scenario:
525 // 1- Interstitial displayed and captive portal detected, setting the flag.
526 // 2- Captive portal detection automatically opens portal login page.
527 // 3- User logs in on the portal login page.
528 // A notification will be received here for RESULT_INTERNET_CONNECTED. Make
529 // sure we don't clear the captive protal flag, since the interstitial was
530 // potentially caused by the captive portal.
531 captive_portal_detected_ = captive_portal_detected_ ||
532 (results->result == captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL);
533 // Also keep track of non-HTTP portals and error cases.
534 captive_portal_no_response_ = captive_portal_no_response_ ||
535 (results->result == captive_portal::RESULT_NO_RESPONSE);
537 #endif