Make castv2 performance test work.
[chromium-blink-merge.git] / chrome / browser / ssl / ssl_error_classification.cc
blobeea89250afa724677523c9a09e001a80b2e5b6ae
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 UNUSED_INTERSTITIAL_CAUSE_ENTRY,
61 // Events for UMA. Do not reorder or change!
62 enum SSLInterstitialCauseCaptivePortal {
63 CAPTIVE_PORTAL_ALL,
64 CAPTIVE_PORTAL_DETECTION_ENABLED,
65 CAPTIVE_PORTAL_DETECTION_ENABLED_OVERRIDABLE,
66 CAPTIVE_PORTAL_PROBE_COMPLETED,
67 CAPTIVE_PORTAL_PROBE_COMPLETED_OVERRIDABLE,
68 CAPTIVE_PORTAL_NO_RESPONSE,
69 CAPTIVE_PORTAL_NO_RESPONSE_OVERRIDABLE,
70 CAPTIVE_PORTAL_DETECTED,
71 CAPTIVE_PORTAL_DETECTED_OVERRIDABLE,
72 UNUSED_CAPTIVE_PORTAL_EVENT,
75 void RecordSSLInterstitialCause(bool overridable, SSLInterstitialCause event) {
76 if (overridable) {
77 UMA_HISTOGRAM_ENUMERATION("interstitial.ssl.cause.overridable", event,
78 UNUSED_INTERSTITIAL_CAUSE_ENTRY);
79 } else {
80 UMA_HISTOGRAM_ENUMERATION("interstitial.ssl.cause.nonoverridable", event,
81 UNUSED_INTERSTITIAL_CAUSE_ENTRY);
85 #if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
86 void RecordCaptivePortalEventStats(SSLInterstitialCauseCaptivePortal event) {
87 UMA_HISTOGRAM_ENUMERATION("interstitial.ssl.captive_portal",
88 event,
89 UNUSED_CAPTIVE_PORTAL_EVENT);
91 #endif
93 int GetLevensteinDistance(const std::string& str1,
94 const std::string& str2) {
95 if (str1 == str2)
96 return 0;
97 if (str1.size() == 0)
98 return str2.size();
99 if (str2.size() == 0)
100 return str1.size();
101 std::vector<int> kFirstRow(str2.size() + 1, 0);
102 std::vector<int> kSecondRow(str2.size() + 1, 0);
104 for (size_t i = 0; i < kFirstRow.size(); ++i)
105 kFirstRow[i] = i;
106 for (size_t i = 0; i < str1.size(); ++i) {
107 kSecondRow[0] = i + 1;
108 for (size_t j = 0; j < str2.size(); ++j) {
109 int cost = str1[i] == str2[j] ? 0 : 1;
110 kSecondRow[j+1] = std::min(std::min(
111 kSecondRow[j] + 1, kFirstRow[j + 1] + 1), kFirstRow[j] + cost);
113 for (size_t j = 0; j < kFirstRow.size(); j++)
114 kFirstRow[j] = kSecondRow[j];
116 return kSecondRow[str2.size()];
119 } // namespace
121 SSLErrorClassification::SSLErrorClassification(
122 content::WebContents* web_contents,
123 const base::Time& current_time,
124 const GURL& url,
125 int cert_error,
126 const net::X509Certificate& cert)
127 : web_contents_(web_contents),
128 current_time_(current_time),
129 request_url_(url),
130 cert_error_(cert_error),
131 cert_(cert),
132 captive_portal_detection_enabled_(false),
133 captive_portal_probe_completed_(false),
134 captive_portal_no_response_(false),
135 captive_portal_detected_(false) {
136 #if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
137 Profile* profile = Profile::FromBrowserContext(
138 web_contents_->GetBrowserContext());
139 captive_portal_detection_enabled_ =
140 CaptivePortalServiceFactory::GetForProfile(profile)->enabled();
141 registrar_.Add(this,
142 chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT,
143 content::Source<Profile>(profile));
144 #endif
147 SSLErrorClassification::~SSLErrorClassification() { }
149 void SSLErrorClassification::RecordCaptivePortalUMAStatistics(
150 bool overridable) const {
151 #if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
152 RecordCaptivePortalEventStats(CAPTIVE_PORTAL_ALL);
153 if (captive_portal_detection_enabled_)
154 RecordCaptivePortalEventStats(
155 overridable ?
156 CAPTIVE_PORTAL_DETECTION_ENABLED_OVERRIDABLE :
157 CAPTIVE_PORTAL_DETECTION_ENABLED);
158 if (captive_portal_probe_completed_)
159 RecordCaptivePortalEventStats(
160 overridable ?
161 CAPTIVE_PORTAL_PROBE_COMPLETED_OVERRIDABLE :
162 CAPTIVE_PORTAL_PROBE_COMPLETED);
163 // Log only one of portal detected and no response results.
164 if (captive_portal_detected_)
165 RecordCaptivePortalEventStats(
166 overridable ?
167 CAPTIVE_PORTAL_DETECTED_OVERRIDABLE :
168 CAPTIVE_PORTAL_DETECTED);
169 else if (captive_portal_no_response_)
170 RecordCaptivePortalEventStats(
171 overridable ?
172 CAPTIVE_PORTAL_NO_RESPONSE_OVERRIDABLE :
173 CAPTIVE_PORTAL_NO_RESPONSE);
174 #endif
177 void SSLErrorClassification::RecordUMAStatistics(
178 bool overridable) const {
179 SSLErrorInfo::ErrorType type =
180 SSLErrorInfo::NetErrorToErrorType(cert_error_);
181 UMA_HISTOGRAM_ENUMERATION(
182 "interstitial.ssl_error_type", type, SSLErrorInfo::END_OF_ENUM);
183 switch (type) {
184 case SSLErrorInfo::CERT_DATE_INVALID: {
185 if (IsUserClockInThePast(base::Time::NowFromSystemTime())) {
186 RecordSSLInterstitialCause(overridable, CLOCK_PAST);
187 } else if (IsUserClockInTheFuture(base::Time::NowFromSystemTime())) {
188 RecordSSLInterstitialCause(overridable, CLOCK_FUTURE);
189 } else if (cert_.HasExpired() && TimePassedSinceExpiry().InDays() < 28) {
190 RecordSSLInterstitialCause(overridable, EXPIRED_RECENTLY);
192 break;
194 case SSLErrorInfo::CERT_COMMON_NAME_INVALID: {
195 std::string host_name = request_url_.host();
196 if (IsHostNameKnownTLD(host_name)) {
197 Tokens host_name_tokens = Tokenize(host_name);
198 if (IsWWWSubDomainMatch())
199 RecordSSLInterstitialCause(overridable, WWW_SUBDOMAIN_MATCH);
200 if (IsSubDomainOutsideWildcard(host_name_tokens))
201 RecordSSLInterstitialCause(overridable, SUBDOMAIN_OUTSIDE_WILDCARD);
202 std::vector<std::string> dns_names;
203 cert_.GetDNSNames(&dns_names);
204 std::vector<Tokens> dns_name_tokens = GetTokenizedDNSNames(dns_names);
205 if (NameUnderAnyNames(host_name_tokens, dns_name_tokens))
206 RecordSSLInterstitialCause(overridable, SUBDOMAIN_MATCH);
207 if (AnyNamesUnderName(dns_name_tokens, host_name_tokens))
208 RecordSSLInterstitialCause(overridable, SUBDOMAIN_INVERSE_MATCH);
209 if (IsCertLikelyFromMultiTenantHosting())
210 RecordSSLInterstitialCause(overridable, LIKELY_MULTI_TENANT_HOSTING);
211 } else {
212 RecordSSLInterstitialCause(overridable, HOST_NAME_NOT_KNOWN_TLD);
214 break;
216 case SSLErrorInfo::CERT_AUTHORITY_INVALID: {
217 const std::string& hostname = request_url_.HostNoBrackets();
218 if (net::IsLocalhost(hostname))
219 RecordSSLInterstitialCause(overridable, LOCALHOST);
220 if (IsHostnameNonUniqueOrDotless(hostname))
221 RecordSSLInterstitialCause(overridable, PRIVATE_URL);
222 if (captive_portal_probe_completed_ && captive_portal_detected_)
223 RecordSSLInterstitialCause(overridable, AUTHORITY_ERROR_CAPTIVE_PORTAL);
224 if (net::X509Certificate::IsSelfSigned(cert_.os_cert_handle()))
225 RecordSSLInterstitialCause(overridable, SELF_SIGNED);
226 break;
228 default:
229 break;
231 UMA_HISTOGRAM_ENUMERATION("interstitial.ssl.connection_type",
232 net::NetworkChangeNotifier::GetConnectionType(),
233 net::NetworkChangeNotifier::CONNECTION_LAST);
236 base::TimeDelta SSLErrorClassification::TimePassedSinceExpiry() const {
237 base::TimeDelta delta = current_time_ - cert_.valid_expiry();
238 return delta;
241 bool SSLErrorClassification::IsUserClockInThePast(const base::Time& time_now) {
242 #if defined(DONT_EMBED_BUILD_METADATA) && !defined(OFFICIAL_BUILD)
243 return false;
244 #else
245 base::Time build_time = base::GetBuildTime();
246 if (time_now < build_time - base::TimeDelta::FromDays(2))
247 return true;
248 return false;
249 #endif
252 bool SSLErrorClassification::IsUserClockInTheFuture(
253 const base::Time& time_now) {
254 #if defined(DONT_EMBED_BUILD_METADATA) && !defined(OFFICIAL_BUILD)
255 return false;
256 #else
257 base::Time build_time = base::GetBuildTime();
258 if (time_now > build_time + base::TimeDelta::FromDays(365))
259 return true;
260 return false;
261 #endif
264 bool SSLErrorClassification::MaybeWindowsLacksSHA256Support() {
265 #if defined(OS_WIN)
266 return !base::win::MaybeHasSHA256Support();
267 #else
268 return false;
269 #endif
272 bool SSLErrorClassification::IsHostNameKnownTLD(const std::string& host_name) {
273 size_t tld_length =
274 net::registry_controlled_domains::GetRegistryLength(
275 host_name,
276 net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES,
277 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
278 if (tld_length == 0 || tld_length == std::string::npos)
279 return false;
280 return true;
283 std::vector<SSLErrorClassification::Tokens> SSLErrorClassification::
284 GetTokenizedDNSNames(const std::vector<std::string>& dns_names) {
285 std::vector<std::vector<std::string>> dns_name_tokens;
286 for (size_t i = 0; i < dns_names.size(); ++i) {
287 std::vector<std::string> dns_name_token_single;
288 if (dns_names[i].empty() || dns_names[i].find('\0') != std::string::npos
289 || !(IsHostNameKnownTLD(dns_names[i]))) {
290 dns_name_token_single.push_back(std::string());
291 } else {
292 dns_name_token_single = Tokenize(dns_names[i]);
294 dns_name_tokens.push_back(dns_name_token_single);
296 return dns_name_tokens;
299 size_t SSLErrorClassification::FindSubDomainDifference(
300 const Tokens& potential_subdomain, const Tokens& parent) const {
301 // A check to ensure that the number of tokens in the tokenized_parent is
302 // less than the tokenized_potential_subdomain.
303 if (parent.size() >= potential_subdomain.size())
304 return 0;
306 size_t tokens_match = 0;
307 size_t diff_size = potential_subdomain.size() - parent.size();
308 for (size_t i = 0; i < parent.size(); ++i) {
309 if (parent[i] == potential_subdomain[i + diff_size])
310 tokens_match++;
312 if (tokens_match == parent.size())
313 return diff_size;
314 return 0;
317 SSLErrorClassification::Tokens SSLErrorClassification::
318 Tokenize(const std::string& name) {
319 Tokens name_tokens;
320 base::SplitStringDontTrim(name, '.', &name_tokens);
321 return name_tokens;
324 // We accept the inverse case for www for historical reasons.
325 bool SSLErrorClassification::IsWWWSubDomainMatch() const {
326 std::string host_name = request_url_.host();
327 if (IsHostNameKnownTLD(host_name)) {
328 std::vector<std::string> dns_names;
329 cert_.GetDNSNames(&dns_names);
330 bool result = false;
331 // Need to account for all possible domains given in the SSL certificate.
332 for (size_t i = 0; i < dns_names.size(); ++i) {
333 if (dns_names[i].empty() || dns_names[i].find('\0') != std::string::npos
334 || dns_names[i].length() == host_name.length()
335 || !(IsHostNameKnownTLD(dns_names[i]))) {
336 result = result || false;
337 } else if (dns_names[i].length() > host_name.length()) {
338 result = result ||
339 net::StripWWW(base::ASCIIToUTF16(dns_names[i])) ==
340 base::ASCIIToUTF16(host_name);
341 } else {
342 result = result ||
343 net::StripWWW(base::ASCIIToUTF16(host_name)) ==
344 base::ASCIIToUTF16(dns_names[i]);
347 return result;
349 return false;
352 bool SSLErrorClassification::NameUnderAnyNames(
353 const Tokens& child,
354 const std::vector<Tokens>& potential_parents) const {
355 bool result = false;
356 // Need to account for all the possible domains given in the SSL certificate.
357 for (size_t i = 0; i < potential_parents.size(); ++i) {
358 if (potential_parents[i].empty() ||
359 potential_parents[i].size() >= child.size()) {
360 result = result || false;
361 } else {
362 size_t domain_diff = FindSubDomainDifference(child,
363 potential_parents[i]);
364 if (domain_diff == 1 && child[0] != "www")
365 result = result || true;
368 return result;
371 bool SSLErrorClassification::AnyNamesUnderName(
372 const std::vector<Tokens>& potential_children,
373 const Tokens& parent) const {
374 bool result = false;
375 // Need to account for all the possible domains given in the SSL certificate.
376 for (size_t i = 0; i < potential_children.size(); ++i) {
377 if (potential_children[i].empty() ||
378 potential_children[i].size() <= parent.size()) {
379 result = result || false;
380 } else {
381 size_t domain_diff = FindSubDomainDifference(potential_children[i],
382 parent);
383 if (domain_diff == 1 && potential_children[i][0] != "www")
384 result = result || true;
387 return result;
390 bool SSLErrorClassification::IsSubDomainOutsideWildcard(
391 const Tokens& host_name_tokens) const {
392 std::string host_name = request_url_.host();
393 std::vector<std::string> dns_names;
394 cert_.GetDNSNames(&dns_names);
395 bool result = false;
397 // This method requires that the host name be longer than the dns name on
398 // the certificate.
399 for (size_t i = 0; i < dns_names.size(); ++i) {
400 const std::string& name = dns_names[i];
401 if (name.length() < 2 || name.length() >= host_name.length() ||
402 name.find('\0') != std::string::npos ||
403 !IsHostNameKnownTLD(name)
404 || name[0] != '*' || name[1] != '.') {
405 continue;
408 // Move past the "*.".
409 std::string extracted_dns_name = name.substr(2);
410 if (FindSubDomainDifference(
411 host_name_tokens, Tokenize(extracted_dns_name)) == 2) {
412 return true;
415 return result;
418 bool SSLErrorClassification::IsCertLikelyFromMultiTenantHosting() const {
419 std::string host_name = request_url_.host();
420 std::vector<std::string> dns_names;
421 std::vector<std::string> dns_names_domain;
422 cert_.GetDNSNames(&dns_names);
423 size_t dns_names_size = dns_names.size();
425 // If there is only 1 DNS name then it is definitely not a shared certificate.
426 if (dns_names_size == 0 || dns_names_size == 1)
427 return false;
429 // Check to see if all the domains in the SAN field in the SSL certificate are
430 // the same or not.
431 for (size_t i = 0; i < dns_names_size; ++i) {
432 dns_names_domain.push_back(
433 net::registry_controlled_domains::
434 GetDomainAndRegistry(
435 dns_names[i],
436 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES));
438 for (size_t i = 1; i < dns_names_domain.size(); ++i) {
439 if (dns_names_domain[i] != dns_names_domain[0])
440 return false;
443 // If the number of DNS names is more than 5 then assume that it is a shared
444 // certificate.
445 static const int kDistinctNameThreshold = 5;
446 if (dns_names_size > kDistinctNameThreshold)
447 return true;
449 // Heuristic - The edit distance between all the strings should be at least 5
450 // for it to be counted as a shared SSLCertificate. If even one pair of
451 // strings edit distance is below 5 then the certificate is no longer
452 // considered as a shared certificate. Include the host name in the URL also
453 // while comparing.
454 dns_names.push_back(host_name);
455 static const int kMinimumEditDsitance = 5;
456 for (size_t i = 0; i < dns_names_size; ++i) {
457 for (size_t j = i + 1; j < dns_names_size; ++j) {
458 int edit_distance = GetLevensteinDistance(dns_names[i], dns_names[j]);
459 if (edit_distance < kMinimumEditDsitance)
460 return false;
463 return true;
466 // static
467 bool SSLErrorClassification::IsHostnameNonUniqueOrDotless(
468 const std::string& hostname) {
469 return net::IsHostnameNonUnique(hostname) ||
470 hostname.find('.') == std::string::npos;
473 void SSLErrorClassification::Observe(
474 int type,
475 const content::NotificationSource& source,
476 const content::NotificationDetails& details) {
477 #if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
478 // When detection is disabled, captive portal service always sends
479 // RESULT_INTERNET_CONNECTED. Ignore any probe results in that case.
480 if (!captive_portal_detection_enabled_)
481 return;
482 if (type == chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT) {
483 captive_portal_probe_completed_ = true;
484 CaptivePortalService::Results* results =
485 content::Details<CaptivePortalService::Results>(details).ptr();
486 // If a captive portal was detected at any point when the interstitial was
487 // displayed, assume that the interstitial was caused by a captive portal.
488 // Example scenario:
489 // 1- Interstitial displayed and captive portal detected, setting the flag.
490 // 2- Captive portal detection automatically opens portal login page.
491 // 3- User logs in on the portal login page.
492 // A notification will be received here for RESULT_INTERNET_CONNECTED. Make
493 // sure we don't clear the captive protal flag, since the interstitial was
494 // potentially caused by the captive portal.
495 captive_portal_detected_ = captive_portal_detected_ ||
496 (results->result == captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL);
497 // Also keep track of non-HTTP portals and error cases.
498 captive_portal_no_response_ = captive_portal_no_response_ ||
499 (results->result == captive_portal::RESULT_NO_RESPONSE);
501 #endif