Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(750)

Unified Diff: chrome/browser/ssl/ssl_error_classification.cc

Issue 400323002: Refactor the captive portal code to move from the ssl_blocking_page class to the ssl_error_classific (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebase-Update Created 6 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/ssl/ssl_error_classification.cc
diff --git a/chrome/browser/ssl/ssl_error_classification.cc b/chrome/browser/ssl/ssl_error_classification.cc
index 0f8911dac843bcaaf7ea553afdbadfc743ac6b52..e6464d8c8bace76a0c196e39c06bd1d6e1e2d979 100644
--- a/chrome/browser/ssl/ssl_error_classification.cc
+++ b/chrome/browser/ssl/ssl_error_classification.cc
@@ -12,21 +12,31 @@
#include "base/strings/string_split.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ssl/ssl_error_info.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/web_contents.h"
#include "net/base/net_util.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "net/cert/x509_cert_types.h"
#include "net/cert/x509_certificate.h"
#include "url/gurl.h"
-using base::Time;
-using base::TimeTicks;
-using base::TimeDelta;
+#if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
+#include "chrome/browser/captive_portal/captive_portal_service.h"
+#include "chrome/browser/captive_portal/captive_portal_service_factory.h"
+#endif
#if defined(OS_WIN)
#include "base/win/windows_version.h"
#endif
+using base::Time;
+using base::TimeTicks;
+using base::TimeDelta;
+
namespace {
// Events for UMA. Do not reorder or change!
@@ -38,9 +48,23 @@ enum SSLInterstitialCause {
SUBDOMAIN_INVERSE_MATCH,
SUBDOMAIN_OUTSIDE_WILDCARD,
HOST_NAME_NOT_KNOWN_TLD,
+ LIKELY_MULTI_TENANT_HOSTING,
UNUSED_INTERSTITIAL_CAUSE_ENTRY,
};
+// Events for UMA. Do not reorder or change!
+enum SSLInterstitialCauseCaptivePortal {
+ CAPTIVE_PORTAL_DETECTION_ENABLED,
+ CAPTIVE_PORTAL_DETECTION_ENABLED_OVERRIDABLE,
+ CAPTIVE_PORTAL_PROBE_COMPLETED,
+ CAPTIVE_PORTAL_PROBE_COMPLETED_OVERRIDABLE,
+ CAPTIVE_PORTAL_NO_RESPONSE,
+ CAPTIVE_PORTAL_NO_RESPONSE_OVERRIDABLE,
+ CAPTIVE_PORTAL_DETECTED,
+ CAPTIVE_PORTAL_DETECTED_OVERRIDABLE,
+ UNUSED_CAPTIVE_PORTAL_EVENT,
+};
+
// Scores/weights which will be constant through all the SSL error types.
static const float kServerWeight = 0.5f;
static const float kClientWeight = 0.5f;
@@ -55,26 +79,104 @@ void RecordSSLInterstitialCause(bool overridable, SSLInterstitialCause event) {
}
}
+void RecordCaptivePortalEventStats(SSLInterstitialCauseCaptivePortal event) {
+ UMA_HISTOGRAM_ENUMERATION("interstitial.ssl.captive_portal",
+ event,
+ UNUSED_CAPTIVE_PORTAL_EVENT);
+}
+
+int GetLevensteinDistance(const std::string& str1,
+ const std::string& str2) {
+ if (str1 == str2)
+ return 0;
+ if (str1.size() == 0)
+ return str2.size();
+ if (str2.size() == 0)
+ return str1.size();
+ std::vector<int> kFirstRow(str2.size() + 1, 0);
+ std::vector<int> kSecondRow(str2.size() + 1, 0);
+
+ for (size_t i = 0; i < kFirstRow.size(); ++i)
+ kFirstRow[i] = i;
+ for (size_t i = 0; i < str1.size(); ++i) {
+ kSecondRow[0] = i + 1;
+ for (size_t j = 0; j < str2.size(); ++j) {
+ int cost = str1[i] == str2[j] ? 0 : 1;
+ kSecondRow[j+1] = std::min(std::min(
+ kSecondRow[j] + 1, kFirstRow[j + 1] + 1), kFirstRow[j] + cost);
+ }
+ for (size_t j = 0; j < kFirstRow.size(); j++)
+ kFirstRow[j] = kSecondRow[j];
+ }
+ return kSecondRow[str2.size()];
+}
+
} // namespace
SSLErrorClassification::SSLErrorClassification(
+ content::WebContents* web_contents,
const base::Time& current_time,
const GURL& url,
+ int cert_error,
const net::X509Certificate& cert)
- : current_time_(current_time),
+ : web_contents_(web_contents),
+ current_time_(current_time),
request_url_(url),
- cert_(cert) { }
+ cert_error_(cert_error),
+ cert_(cert),
+ captive_portal_detection_enabled_(false),
+ captive_portal_probe_completed_(false),
+ captive_portal_no_response_(false),
+ captive_portal_detected_(false) {
+#if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
+ Profile* profile = Profile::FromBrowserContext(
+ web_contents_->GetBrowserContext());
+ CaptivePortalService* captive_portal_service =
+ CaptivePortalServiceFactory::GetForProfile(profile);
+ captive_portal_detection_enabled_ = captive_portal_service->enabled();
+ captive_portal_service->DetectCaptivePortal();
+ registrar_.Add(this,
+ chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT,
+ content::Source<Profile>(profile));
+#endif
+}
SSLErrorClassification::~SSLErrorClassification() { }
-float SSLErrorClassification::InvalidDateSeverityScore(
- int cert_error) const {
+void SSLErrorClassification::RecordCaptivePortalUMAStatistics(
+ bool overridable) const {
+#if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
+ if (captive_portal_detection_enabled_)
+ RecordCaptivePortalEventStats(
+ overridable ?
+ CAPTIVE_PORTAL_DETECTION_ENABLED_OVERRIDABLE :
+ CAPTIVE_PORTAL_DETECTION_ENABLED);
+ if (captive_portal_probe_completed_)
+ RecordCaptivePortalEventStats(
+ overridable ?
+ CAPTIVE_PORTAL_PROBE_COMPLETED_OVERRIDABLE :
+ CAPTIVE_PORTAL_PROBE_COMPLETED);
+ // Log only one of portal detected and no response results.
+ if (captive_portal_detected_)
+ RecordCaptivePortalEventStats(
+ overridable ?
+ CAPTIVE_PORTAL_DETECTED_OVERRIDABLE :
+ CAPTIVE_PORTAL_DETECTED);
+ else if (captive_portal_no_response_)
+ RecordCaptivePortalEventStats(
+ overridable ?
+ CAPTIVE_PORTAL_NO_RESPONSE_OVERRIDABLE :
+ CAPTIVE_PORTAL_NO_RESPONSE);
+#endif
+}
+
+void SSLErrorClassification::InvalidDateSeverityScore() {
SSLErrorInfo::ErrorType type =
- SSLErrorInfo::NetErrorToErrorType(cert_error);
+ SSLErrorInfo::NetErrorToErrorType(cert_error_);
DCHECK(type == SSLErrorInfo::CERT_DATE_INVALID);
+
// Client-side characteristics. Check whether or not the system's clock is
- // wrong and whether or not the user has already encountered this error
- // before.
+ // wrong and whether or not the user has encountered this error before.
float severity_date_score = 0.0f;
static const float kCertificateExpiredWeight = 0.3f;
@@ -103,19 +205,20 @@ float SSLErrorClassification::InvalidDateSeverityScore(
}
if (current_time_ < cert_.valid_start())
severity_date_score += kServerWeight * kNotYetValidWeight;
- return severity_date_score;
+ // TODO(radhikabhar): Record the severity score in a histogram. This will be
+ // in the next CL - just called the function in ssl_blocking_page.cc.
}
-float SSLErrorClassification::InvalidCommonNameSeverityScore(
- int cert_error) const {
+void SSLErrorClassification::InvalidCommonNameSeverityScore() {
SSLErrorInfo::ErrorType type =
- SSLErrorInfo::NetErrorToErrorType(cert_error);
+ SSLErrorInfo::NetErrorToErrorType(cert_error_);
DCHECK(type == SSLErrorInfo::CERT_COMMON_NAME_INVALID);
float severity_name_score = 0.0f;
static const float kWWWDifferenceWeight = 0.3f;
- static const float kSubDomainWeight = 0.2f;
- static const float kSubDomainInverseWeight = 1.0f;
+ static const float kNameUnderAnyNamesWeight = 0.2f;
+ static const float kAnyNamesUnderNamesWeight = 1.0f;
+ static const float kLikelyMultiTenantHostingWeight = 0.1f;
std::string host_name = request_url_.host();
if (IsHostNameKnownTLD(host_name)) {
@@ -129,18 +232,27 @@ float SSLErrorClassification::InvalidCommonNameSeverityScore(
cert_.GetDNSNames(&dns_names);
std::vector<Tokens> dns_name_tokens = GetTokenizedDNSNames(dns_names);
if (NameUnderAnyNames(host_name_tokens, dns_name_tokens))
- severity_name_score += kServerWeight * kSubDomainWeight;
+ severity_name_score += kServerWeight * kNameUnderAnyNamesWeight;
// Inverse case is more likely to be a MITM attack.
if (AnyNamesUnderName(dns_name_tokens, host_name_tokens))
- severity_name_score += kServerWeight * kSubDomainInverseWeight;
+ severity_name_score += kServerWeight * kAnyNamesUnderNamesWeight;
+ if (IsCertLikelyFromMultiTenantHosting())
+ severity_name_score += kServerWeight * kLikelyMultiTenantHostingWeight;
}
- return severity_name_score;
+
+ static const float kEnvironmentWeight = 0.25f;
+
+ severity_name_score += kClientWeight * kEnvironmentWeight *
+ CalculateScoreEnvironments();
+ // TODO(radhikabhar): Record the severity score in a histogram. Same as above
+ // - this will be in the next CL. So just called the function in the
+ // ssl_blocking_page.cc.
}
-void SSLErrorClassification::RecordUMAStatistics(bool overridable,
- int cert_error) {
+void SSLErrorClassification::RecordUMAStatistics(
+ bool overridable) const {
SSLErrorInfo::ErrorType type =
- SSLErrorInfo::NetErrorToErrorType(cert_error);
+ SSLErrorInfo::NetErrorToErrorType(cert_error_);
switch (type) {
case SSLErrorInfo::CERT_DATE_INVALID: {
if (IsUserClockInThePast(base::Time::NowFromSystemTime()))
@@ -164,6 +276,8 @@ void SSLErrorClassification::RecordUMAStatistics(bool overridable,
RecordSSLInterstitialCause(overridable, SUBDOMAIN_MATCH);
if (AnyNamesUnderName(dns_name_tokens, host_name_tokens))
RecordSSLInterstitialCause(overridable, SUBDOMAIN_INVERSE_MATCH);
+ if (IsCertLikelyFromMultiTenantHosting())
+ RecordSSLInterstitialCause(overridable, LIKELY_MULTI_TENANT_HOSTING);
} else {
RecordSSLInterstitialCause(overridable, HOST_NAME_NOT_KNOWN_TLD);
}
@@ -196,6 +310,32 @@ float SSLErrorClassification::CalculateScoreTimePassedSinceExpiry() const {
return kLowThresholdWeight;
}
+float SSLErrorClassification::CalculateScoreEnvironments() const {
+ static const float kWifiWeight = 0.7f;
+ static const float kCellularWeight = 0.7f;
+ static const float kHotspotWeight = 0.2f;
+ static const float kEthernetWeight = 0.7f;
+ static const float kOtherWeight = 0.7f;
+ net::NetworkChangeNotifier::ConnectionType type =
+ net::NetworkChangeNotifier::GetConnectionType();
+ if (type == net::NetworkChangeNotifier::CONNECTION_WIFI)
+ return kWifiWeight;
+ if (type == net::NetworkChangeNotifier::CONNECTION_2G ||
+ type == net::NetworkChangeNotifier::CONNECTION_3G ||
+ type == net::NetworkChangeNotifier::CONNECTION_4G ) {
+ return kCellularWeight;
+ }
+ if (type == net::NetworkChangeNotifier::CONNECTION_ETHERNET)
+ return kEthernetWeight;
+#if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
+ // Assume if captive portals are detected then the user is connected using a
+ // hot spot.
+ if (captive_portal_probe_completed_ && captive_portal_detected_)
+ return kHotspotWeight;
+#endif
+ return kOtherWeight;
+}
+
bool SSLErrorClassification::IsUserClockInThePast(const base::Time& time_now) {
base::Time build_time = base::GetBuildTime();
if (time_now < build_time - base::TimeDelta::FromDays(2))
@@ -366,3 +506,84 @@ bool SSLErrorClassification::IsSubDomainOutsideWildcard(
}
return result;
}
+
+bool SSLErrorClassification::IsCertLikelyFromMultiTenantHosting() const {
+ std::string host_name = request_url_.host();
+ std::vector<std::string> dns_names;
+ std::vector<std::string> dns_names_domain;
+ cert_.GetDNSNames(&dns_names);
+ size_t dns_names_size = dns_names.size();
+
+ // If there is only 1 DNS name then it is definitely not a shared certificate.
+ if (dns_names_size == 0 || dns_names_size == 1)
+ return false;
+
+ // Check to see if all the domains in the SAN field in the SSL certificate are
+ // the same or not.
+ for (size_t i = 0; i < dns_names_size; ++i) {
+ dns_names_domain.push_back(
+ net::registry_controlled_domains::
+ GetDomainAndRegistry(
+ dns_names[i],
+ net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES));
+ }
+ for (size_t i = 1; i < dns_names_domain.size(); ++i) {
+ if (dns_names_domain[i] != dns_names_domain[0])
+ return false;
+ }
+
+ // If the number of DNS names is more than 5 then assume that it is a shared
+ // certificate.
+ static const int kDistinctNameThreshold = 5;
+ if (dns_names_size > kDistinctNameThreshold)
+ return true;
+
+ // Heuristic - The edit distance between all the strings should be at least 5
+ // for it to be counted as a shared SSLCertificate. If even one pair of
+ // strings edit distance is below 5 then the certificate is no longer
+ // considered as a shared certificate. Include the host name in the URL also
+ // while comparing.
+ dns_names.push_back(host_name);
+ static const int kMinimumEditDsitance = 5;
+ for (size_t i = 0; i < dns_names_size; ++i) {
+ for (size_t j = i + 1; j < dns_names_size; ++j) {
+ int edit_distance = GetLevensteinDistance(dns_names[i], dns_names[j]);
+ if (edit_distance < kMinimumEditDsitance)
+ return false;
+ }
+ }
+ return true;
+}
+
+
+void SSLErrorClassification::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+#if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
+ // When detection is disabled, captive portal service always sends
+ // RESULT_INTERNET_CONNECTED. Ignore any probe results in that case.
+ if (!captive_portal_detection_enabled_)
+ return;
+ if (type == chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT) {
+ captive_portal_probe_completed_ = true;
+ CaptivePortalService::Results* results =
+ content::Details<CaptivePortalService::Results>(
+ details).ptr();
+ // If a captive portal was detected at any point when the interstitial was
+ // displayed, assume that the interstitial was caused by a captive portal.
+ // Example scenario:
+ // 1- Interstitial displayed and captive portal detected, setting the flag.
+ // 2- Captive portal detection automatically opens portal login page.
+ // 3- User logs in on the portal login page.
+ // A notification will be received here for RESULT_INTERNET_CONNECTED. Make
+ // sure we don't clear the captive protal flag, since the interstitial was
+ // potentially caused by the captive portal.
+ captive_portal_detected_ = captive_portal_detected_ ||
+ (results->result == captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL);
+ // Also keep track of non-HTTP portals and error cases.
+ captive_portal_no_response_ = captive_portal_no_response_ ||
+ (results->result == captive_portal::RESULT_NO_RESPONSE);
+ }
+#endif
+}

Powered by Google App Engine
This is Rietveld 408576698