Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/ssl/ssl_error_classification.h" | 5 #include "chrome/browser/ssl/ssl_error_classification.h" |
| 6 | 6 |
| 7 #include "base/build_time.h" | 7 #include "base/build_time.h" |
| 8 #include "base/metrics/field_trial.h" | 8 #include "base/metrics/field_trial.h" |
| 9 #include "base/metrics/histogram.h" | 9 #include "base/metrics/histogram.h" |
| 10 #include "base/time/time.h" | 10 #include "base/time/time.h" |
| 11 #include "chrome/browser/browser_process.h" | 11 #include "chrome/browser/browser_process.h" |
| 12 #include "components/network_time/network_time_tracker.h" | 12 #include "net/cert/x509_cert_types.h" |
| 13 #include "net/cert/x509_certificate.h" | 13 #include "net/cert/x509_certificate.h" |
| 14 #include "url/gurl.h" | |
| 14 | 15 |
| 15 using base::Time; | 16 using base::Time; |
| 16 using base::TimeTicks; | 17 using base::TimeTicks; |
| 17 using base::TimeDelta; | 18 using base::TimeDelta; |
| 18 | 19 |
| 19 namespace { | 20 namespace { |
| 20 | 21 |
| 21 // Events for UMA. Do not reorder or change! | 22 // Events for UMA. Do not reorder or change! |
| 22 enum SSLInterstitialCause { | 23 enum SSLInterstitialCause { |
| 23 CLOCK_PAST, | 24 CLOCK_PAST, |
| 24 CLOCK_FUTURE, | 25 CLOCK_FUTURE, |
| 25 UNUSED_INTERSTITIAL_CAUSE_ENTRY, | 26 UNUSED_INTERSTITIAL_CAUSE_ENTRY, |
| 26 }; | 27 }; |
| 27 | 28 |
| 29 // Scores/weights which will be constant through all the SSL error types. | |
|
felt
2014/07/15 00:52:44
As we discussed earlier today, I'm not sure whethe
radhikabhar
2014/07/15 17:34:09
Done.
| |
| 30 static const float kServerWeight = 0.5f; | |
| 31 static const float kClientWeight = 0.5f; | |
| 32 | |
| 28 void RecordSSLInterstitialCause(bool overridable, SSLInterstitialCause event) { | 33 void RecordSSLInterstitialCause(bool overridable, SSLInterstitialCause event) { |
| 29 if (overridable) { | 34 if (overridable) { |
| 30 UMA_HISTOGRAM_ENUMERATION("interstitial.ssl.cause.overridable", event, | 35 UMA_HISTOGRAM_ENUMERATION("interstitial.ssl.cause.overridable", event, |
| 31 UNUSED_INTERSTITIAL_CAUSE_ENTRY); | 36 UNUSED_INTERSTITIAL_CAUSE_ENTRY); |
| 32 } else { | 37 } else { |
| 33 UMA_HISTOGRAM_ENUMERATION("interstitial.ssl.cause.nonoverridable", event, | 38 UMA_HISTOGRAM_ENUMERATION("interstitial.ssl.cause.nonoverridable", event, |
| 34 UNUSED_INTERSTITIAL_CAUSE_ENTRY); | 39 UNUSED_INTERSTITIAL_CAUSE_ENTRY); |
| 35 } | 40 } |
| 36 } | 41 } |
| 37 | 42 |
| 43 // Utility function - If |str2| is of the form "bc" and |str1| is of the form | |
| 44 // "abc" then this function returns "a" otherwise it returns an empty string. | |
|
felt
2014/07/15 00:52:44
Is this supposed to be checking whether str2 = pre
radhikabhar
2014/07/15 17:34:09
Done.
| |
| 45 std::string GetStringDifference(std::string str1, std::string str2) { | |
| 46 // Str1 is longer than str2. | |
| 47 std::size_t found = str1.find(str2); | |
|
felt
2014/07/15 00:52:44
nit: a stray space before the = sign
radhikabhar
2014/07/15 17:34:09
Done.
| |
| 48 if (found != std::string::npos) { | |
| 49 std::string result_last = str1.substr(found); | |
| 50 if (result_last.compare(str2) != 0 ) | |
| 51 return std::string(); | |
|
felt
2014/07/15 00:52:44
to make sure I understand correctly: the reason fo
radhikabhar
2014/07/15 17:34:09
Yes, because we don't want o be accepting foo.goog
| |
| 52 std::string result_first = str1.substr(0, found); | |
| 53 return result_first; | |
|
felt
2014/07/15 00:52:44
why is this indented?
radhikabhar
2014/07/15 17:34:09
Done.
| |
| 54 } | |
| 55 return std::string(); | |
| 56 } | |
| 57 | |
| 38 } // namespace | 58 } // namespace |
| 39 | 59 |
| 40 SSLErrorClassification::SSLErrorClassification( | 60 SSLErrorClassification::SSLErrorClassification( |
| 41 base::Time current_time, | 61 base::Time current_time, |
| 62 const GURL& url, | |
| 42 const net::X509Certificate& cert) | 63 const net::X509Certificate& cert) |
| 43 : current_time_(current_time), | 64 : current_time_(current_time), |
| 65 request_url_(url), | |
| 44 cert_(cert) { } | 66 cert_(cert) { } |
| 45 | 67 |
| 46 SSLErrorClassification::~SSLErrorClassification() { } | 68 SSLErrorClassification::~SSLErrorClassification() { } |
| 47 | 69 |
| 48 float SSLErrorClassification::InvalidDateSeverityScore() const { | 70 float SSLErrorClassification::InvalidDateSeverityScore() const { |
| 49 // Client-side characterisitics. Check whether the system's clock is wrong or | 71 // Client-side characterisitics. Check whether the system's clock is wrong or |
| 50 // not and whether the user has encountered this error before or not. | 72 // not and whether the user has encountered this error before or not. |
| 51 float severity_date_score = 0.0f; | 73 float severity_date_score = 0.0f; |
| 52 | 74 |
| 53 static const float kClientWeight = 0.5f; | |
| 54 static const float kSystemClockWeight = 0.75f; | 75 static const float kSystemClockWeight = 0.75f; |
| 55 static const float kSystemClockWrongWeight = 0.1f; | 76 static const float kSystemClockWrongWeight = 0.1f; |
| 56 static const float kSystemClockRightWeight = 1.0f; | 77 static const float kSystemClockRightWeight = 1.0f; |
| 57 | 78 |
| 58 static const float kServerWeight = 0.5f; | |
| 59 static const float kCertificateExpiredWeight = 0.3f; | 79 static const float kCertificateExpiredWeight = 0.3f; |
| 60 static const float kNotYetValidWeight = 0.2f; | 80 static const float kNotYetValidWeight = 0.2f; |
| 61 | 81 |
| 62 if (IsUserClockInThePast(current_time_) || | 82 if (IsUserClockInThePast(current_time_) || |
| 63 IsUserClockInTheFuture(current_time_)) { | 83 IsUserClockInTheFuture(current_time_)) { |
| 64 severity_date_score = kClientWeight * kSystemClockWeight * | 84 severity_date_score = kClientWeight * kSystemClockWeight * |
| 65 kSystemClockWrongWeight; | 85 kSystemClockWrongWeight; |
| 66 } else { | 86 } else { |
| 67 severity_date_score = kClientWeight * kSystemClockWeight * | 87 severity_date_score = kClientWeight * kSystemClockWeight * |
| 68 kSystemClockRightWeight; | 88 kSystemClockRightWeight; |
| 69 } | 89 } |
| 70 // TODO(radhikabhar): (crbug.com/393262) Check website settings. | 90 // TODO(radhikabhar): (crbug.com/393262) Check website settings. |
| 71 | 91 |
| 92 float SSLErrorClassification::InvalidDateSeverityScore() const{ | |
|
felt
2014/07/15 00:52:44
It looks like a rebase went wrong -- InvalidDateSe
radhikabhar
2014/07/15 17:34:09
Done.
| |
| 93 // CLient-side characteristics. Check whether or not the system's clock is | |
| 94 // worng and whether or not the user has already encountered this error | |
| 95 // before. | |
| 96 float severity_date_score = 0.0f; | |
| 97 | |
| 98 static const float kCertificateExpiredWeight = 0.3f; | |
| 99 static const float kNotYetValidWeight = 0.2f; | |
| 100 | |
| 101 static const float kSystemClockWeight = 0.75f; | |
| 102 static const float kSystemClockWrongWeight = 0.1f; | |
| 103 static const float kSystemClockRightWeight = 1.0f; | |
| 104 | |
| 105 if (IsUserClockInThePast(current_time_) || | |
| 106 IsUserClockInTheFuture(current_time_)) { | |
| 107 severity_date_score += kClientWeight * kSystemClockWeight * | |
| 108 kSystemClockWrongWeight; | |
| 109 } else { | |
| 110 severity_date_score += kClientWeight * kSystemClockWeight * | |
| 111 kSystemClockRightWeight; | |
| 112 } | |
| 113 // TODO(radhikabhar): (crbug.com/393262) Check website settings. | |
| 114 | |
| 72 // Server-side characteristics. Check whether the certificate has expired or | 115 // Server-side characteristics. Check whether the certificate has expired or |
| 73 // is not yet valid. If the certificate has expired then factor the time which | 116 // is not yet valid. If the certificate has expired then factor the time which |
| 74 // has passed since expiry. | 117 // has passed since expiry. |
| 75 if (cert_.HasExpired()) { | 118 if (cert_.HasExpired()) { |
| 76 severity_date_score += kServerWeight * kCertificateExpiredWeight * | 119 severity_date_score += kServerWeight * kCertificateExpiredWeight * |
| 77 CalculateScoreTimePassedSinceExpiry(); | 120 CalculateScoreTimePassedSinceExpiry(); |
| 78 } | 121 } |
| 79 if (current_time_ < cert_.valid_start()) | 122 if (current_time_ < cert_.valid_start()) |
| 80 severity_date_score += kServerWeight * kNotYetValidWeight; | 123 severity_date_score += kServerWeight * kNotYetValidWeight; |
| 81 return severity_date_score; | 124 return severity_date_score; |
| 82 } | 125 } |
| 83 | 126 |
| 127 float SSLErrorClassification::InvalidCommonNameSeverityScore() const { | |
| 128 float severity_name_score = 0.0f; | |
| 129 | |
| 130 static const float kWWWDifferenceWeight = 0.3f; | |
| 131 static const float kRegisteredDomainWeight = 0.2f; | |
| 132 static const float kRegisteredDomainInverseWeight = 1.0f; | |
| 133 | |
| 134 if (IsWWWDifference()) | |
| 135 severity_name_score += kServerWeight * kWWWDifferenceWeight; | |
| 136 if (IsRegisteredDomainMatch()) | |
| 137 severity_name_score += kServerWeight * kRegisteredDomainWeight; | |
| 138 // Inverse case is more likely to be a MITM attack. | |
| 139 if (IsRegisteredDomainInverseMatch()) | |
| 140 severity_name_score += kServerWeight * kRegisteredDomainInverseWeight; | |
| 141 return severity_name_score; | |
| 142 } | |
| 143 | |
| 144 void SSLErrorClassification::RecordUMAStatistics(bool overridable) { | |
| 145 if (IsUserClockInThePast(base::Time::NowFromSystemTime())) | |
| 146 RecordSSLInterstitialCause(overridable, CLOCK_PAST); | |
| 147 if (IsUserClockInTheFuture(base::Time::NowFromSystemTime())) | |
| 148 RecordSSLInterstitialCause(overridable, CLOCK_FUTURE); | |
| 149 } | |
| 150 | |
| 84 base::TimeDelta SSLErrorClassification::TimePassedSinceExpiry() const { | 151 base::TimeDelta SSLErrorClassification::TimePassedSinceExpiry() const { |
| 85 base::TimeDelta delta = current_time_ - cert_.valid_expiry(); | 152 base::TimeDelta delta = current_time_ - cert_.valid_expiry(); |
| 86 return delta; | 153 return delta; |
| 87 } | 154 } |
| 88 | 155 |
| 89 float SSLErrorClassification::CalculateScoreTimePassedSinceExpiry() const { | 156 float SSLErrorClassification::CalculateScoreTimePassedSinceExpiry() const { |
| 90 base::TimeDelta delta = TimePassedSinceExpiry(); | 157 base::TimeDelta delta = TimePassedSinceExpiry(); |
| 91 int64 time_passed = delta.InDays(); | 158 int64 time_passed = delta.InDays(); |
| 92 const int64 kHighThreshold = 7; | 159 const int64 kHighThreshold = 7; |
| 93 const int64 kLowThreshold = 4; | 160 const int64 kLowThreshold = 4; |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 109 return false; | 176 return false; |
| 110 } | 177 } |
| 111 | 178 |
| 112 bool SSLErrorClassification::IsUserClockInTheFuture(base::Time time_now) { | 179 bool SSLErrorClassification::IsUserClockInTheFuture(base::Time time_now) { |
| 113 base::Time build_time = base::GetBuildTime(); | 180 base::Time build_time = base::GetBuildTime(); |
| 114 if (time_now > build_time + base::TimeDelta::FromDays(365)) | 181 if (time_now > build_time + base::TimeDelta::FromDays(365)) |
| 115 return true; | 182 return true; |
| 116 return false; | 183 return false; |
| 117 } | 184 } |
| 118 | 185 |
| 119 void SSLErrorClassification::RecordUMAStatistics(bool overridable) { | 186 bool SSLErrorClassification::IsWWWDifference() const { |
| 120 if (IsUserClockInThePast(base::Time::NowFromSystemTime())) | 187 std::string host_name = request_url_.host(); |
| 121 RecordSSLInterstitialCause(overridable, CLOCK_PAST); | 188 if (request_url_.HostIsIPAddress() || host_name.empty()) |
| 122 if (IsUserClockInTheFuture(base::Time::NowFromSystemTime())) | 189 return false; |
| 123 RecordSSLInterstitialCause(overridable, CLOCK_FUTURE); | 190 |
| 191 std::vector<std::string> dns_names; | |
| 192 cert_.GetDNSNames(&dns_names); | |
| 193 bool result = false; | |
| 194 | |
| 195 // Need to account for all possible domains given in the SSL certificate. | |
| 196 for (size_t i = 0; i < dns_names.size(); ++i) { | |
| 197 std::string string_diff; | |
| 198 if (dns_names[i].empty() || dns_names[i].find('\0') != std::string::npos | |
| 199 || dns_names[i].length() == host_name.length()) | |
|
felt
2014/07/15 00:52:44
since this is multi-line, I'd recommend putting {
radhikabhar
2014/07/15 17:34:08
Done.
| |
| 200 result = result || false; | |
| 201 else if (dns_names[i].length() > host_name.length()) | |
| 202 string_diff = GetStringDifference(dns_names[i], host_name); | |
| 203 else | |
| 204 string_diff = GetStringDifference(host_name, dns_names[i]); | |
| 205 if (!string_diff.empty() && string_diff.compare("www.") == 0) | |
| 206 result = result || true; | |
| 207 } | |
| 208 return result; | |
| 124 } | 209 } |
|
felt
2014/07/15 00:52:44
It might be interesting to do another version of t
radhikabhar
2014/07/15 17:34:09
The function IsRegisteredDomainMatch() checks whet
| |
| 210 | |
| 211 bool SSLErrorClassification::IsRegisteredDomainMatch() const { | |
| 212 std::string host_name = request_url_.host(); | |
| 213 if (request_url_.HostIsIPAddress() || host_name.empty()) | |
| 214 return false; | |
| 215 | |
| 216 std::vector<std::string> dns_names; | |
| 217 cert_.GetDNSNames(&dns_names); | |
| 218 bool result = false; | |
| 219 | |
| 220 // Need to account for all the possible domains given in the SSL certificate. | |
| 221 for (size_t i = 0; i < dns_names.size(); ++i) { | |
| 222 if (dns_names[i].empty() || dns_names[i].find('\0') != std::string::npos | |
| 223 || dns_names[i].length() >= host_name.length()) | |
| 224 result = result || false; | |
| 225 std::string string_diff = GetStringDifference(host_name, dns_names[i]); | |
| 226 if (string_diff.empty() || (string_diff.compare("www.") == 0)) { | |
| 227 result = result || false; | |
| 228 } else { | |
| 229 size_t total_count = std::count(string_diff.begin(), | |
| 230 string_diff.end(), '.'); | |
| 231 if (total_count == 1) | |
| 232 result = result || true; | |
| 233 } | |
| 234 } | |
| 235 return result; | |
| 236 } | |
| 237 | |
| 238 // The inverse case should be treated carefully as this is most likely a MITM | |
| 239 // attack. | |
| 240 bool SSLErrorClassification::IsRegisteredDomainInverseMatch() const { | |
| 241 std::string host_name = request_url_.host(); | |
| 242 if (request_url_.HostIsIPAddress() || host_name.empty()) | |
| 243 return false; | |
| 244 | |
| 245 std::vector<std::string> dns_names; | |
| 246 cert_.GetDNSNames(&dns_names); | |
| 247 bool result = false; | |
| 248 | |
| 249 // Need to account for all the possible domains given in the SSL certificate. | |
| 250 for (size_t i = 0; i < dns_names.size(); ++i) { | |
| 251 if (dns_names[i].empty() || dns_names[i].find('\0') != std::string::npos | |
| 252 || dns_names[i].length() <= host_name.length()) | |
| 253 result = result || false; | |
| 254 std::string string_diff = GetStringDifference(dns_names[i], host_name); | |
| 255 if (string_diff.empty() || (string_diff.compare("www.") == 0)) { | |
| 256 result = result || false; | |
| 257 } else { | |
| 258 size_t total_count = std::count(string_diff.begin(), | |
| 259 string_diff.end(),'.'); | |
| 260 if (total_count == 1) | |
| 261 result = result || true; | |
| 262 } | |
| 263 } | |
| 264 return result; | |
| 265 } | |
| 266 | |
| 267 // This method is valid for wildcard certificates only. | |
| 268 bool SSLErrorClassification::IsHostNameTooBroad() const { | |
| 269 std::string host_name = request_url_.host(); | |
| 270 if (request_url_.HostIsIPAddress() || host_name.empty()) | |
| 271 return false; | |
| 272 | |
| 273 std::vector<std::string> dns_names; | |
| 274 cert_.GetDNSNames(&dns_names); | |
| 275 bool result = false; | |
| 276 | |
| 277 // This method requires that the host name be longer than the dns name on | |
| 278 // the certificate. | |
| 279 for (size_t i = 0; i < dns_names.size(); ++i) { | |
| 280 if (dns_names[i].find("*") == std::string::npos) { | |
| 281 result = result || false; | |
| 282 } else { | |
| 283 if (dns_names[i].empty() || dns_names[i].find('\0') != std::string::npos | |
| 284 || dns_names[i].length() >= host_name.length()) { | |
| 285 result = result || false; | |
| 286 } else { | |
| 287 // Move past the '*' and '.'. | |
| 288 std::string extracted_common_name = dns_names[i].substr(2); | |
| 289 std::string string_diff = GetStringDifference(host_name, | |
| 290 extracted_common_name); | |
| 291 size_t total_count = std::count(string_diff.begin(), | |
| 292 string_diff.end(), '.'); | |
| 293 if (total_count == 2) | |
| 294 result = result || true; | |
| 295 } | |
| 296 } | |
| 297 } | |
| 298 return result; | |
| 299 } | |
| 300 | |
| 301 bool SSLErrorClassification::IsSelfSigned() const { | |
| 302 // Check whether the issuer and the subject are the same. | |
| 303 const net::CertPrincipal& subject = cert_.subject(); | |
| 304 const net::CertPrincipal& issuer = cert_.issuer(); | |
| 305 bool result = subject.common_name == issuer.common_name && | |
| 306 subject.locality_name == issuer.locality_name && | |
| 307 subject.state_or_province_name == issuer.state_or_province_name && | |
| 308 subject.country_name == issuer.country_name && | |
| 309 subject.street_addresses == issuer.street_addresses && | |
| 310 subject.organization_names == issuer.organization_names && | |
| 311 subject.organization_unit_names == issuer.organization_names && | |
| 312 subject.domain_components == issuer.domain_components; | |
| 313 return result; | |
| 314 } | |
| OLD | NEW |