Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2014 The Chromium Authors. All rights reserved. |
|
felt
2014/07/15 20:44:34
Pretty sure this was correct before, they removed
radhikabhar
2014/07/16 22:35:15
Done.
| |
| 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 "net/cert/x509_cert_types.h" |
| 12 #include "components/network_time/network_time_tracker.h" | |
| 13 #include "net/cert/x509_certificate.h" | 12 #include "net/cert/x509_certificate.h" |
| 13 #include "url/gurl.h" | |
| 14 | 14 |
| 15 using base::Time; | 15 using base::Time; |
| 16 using base::TimeTicks; | 16 using base::TimeTicks; |
| 17 using base::TimeDelta; | 17 using base::TimeDelta; |
| 18 | 18 |
| 19 namespace { | 19 namespace { |
| 20 | 20 |
| 21 // Events for UMA. Do not reorder or change! | 21 // Events for UMA. Do not reorder or change! |
| 22 enum SSLInterstitialCause { | 22 enum SSLInterstitialCause { |
| 23 CLOCK_PAST, | 23 CLOCK_PAST, |
| 24 CLOCK_FUTURE, | 24 CLOCK_FUTURE, |
| 25 UNUSED_INTERSTITIAL_CAUSE_ENTRY, | 25 UNUSED_INTERSTITIAL_CAUSE_ENTRY, |
| 26 }; | 26 }; |
| 27 | 27 |
| 28 // Scores/weights which will be constant through all the SSL error types. | |
| 29 static const float kServerWeight = 0.5f; | |
| 30 static const float kClientWeight = 0.5f; | |
| 31 | |
| 28 void RecordSSLInterstitialCause(bool overridable, SSLInterstitialCause event) { | 32 void RecordSSLInterstitialCause(bool overridable, SSLInterstitialCause event) { |
| 29 if (overridable) { | 33 if (overridable) { |
| 30 UMA_HISTOGRAM_ENUMERATION("interstitial.ssl.cause.overridable", event, | 34 UMA_HISTOGRAM_ENUMERATION("interstitial.ssl.cause.overridable", event, |
| 31 UNUSED_INTERSTITIAL_CAUSE_ENTRY); | 35 UNUSED_INTERSTITIAL_CAUSE_ENTRY); |
| 32 } else { | 36 } else { |
| 33 UMA_HISTOGRAM_ENUMERATION("interstitial.ssl.cause.nonoverridable", event, | 37 UMA_HISTOGRAM_ENUMERATION("interstitial.ssl.cause.nonoverridable", event, |
| 34 UNUSED_INTERSTITIAL_CAUSE_ENTRY); | 38 UNUSED_INTERSTITIAL_CAUSE_ENTRY); |
| 35 } | 39 } |
| 36 } | 40 } |
| 37 | 41 |
| 42 // Utility function - For two unequal strings, this method checks to see whether | |
| 43 // the |longer_str| = prefix + |smaller_str|. If so, then it returns the prefix | |
|
palmer
2014/07/15 21:23:23
Nit: Antonyms are longer/shorter, larger/smaller.
radhikabhar
2014/07/16 22:35:14
Done.
| |
| 44 // otherwise it returns an empty string. | |
| 45 std::string GetStringPrefix(std::string longer_str, std::string smaller_str) { | |
| 46 std::size_t found = longer_str.find(smaller_str); | |
| 47 if (found != std::string::npos) { | |
| 48 std::string result_last = longer_str.substr(found); | |
| 49 if (result_last.compare(smaller_str) != 0 ) | |
| 50 return std::string(); | |
| 51 std::string result_first = longer_str.substr(0, found); | |
| 52 return result_first; | |
| 53 } | |
| 54 return std::string(); | |
| 55 } | |
| 56 | |
| 38 } // namespace | 57 } // namespace |
| 39 | 58 |
| 40 SSLErrorClassification::SSLErrorClassification( | 59 SSLErrorClassification::SSLErrorClassification( |
| 41 base::Time current_time, | 60 base::Time current_time, |
| 61 const GURL& url, | |
| 42 const net::X509Certificate& cert) | 62 const net::X509Certificate& cert) |
| 43 : current_time_(current_time), | 63 : current_time_(current_time), |
| 64 request_url_(url), | |
| 44 cert_(cert) { } | 65 cert_(cert) { } |
| 45 | 66 |
| 46 SSLErrorClassification::~SSLErrorClassification() { } | 67 SSLErrorClassification::~SSLErrorClassification() { } |
| 47 | 68 |
| 48 float SSLErrorClassification::InvalidDateSeverityScore() const { | 69 float SSLErrorClassification::InvalidDateSeverityScore() const{ |
| 49 // Client-side characterisitics. Check whether the system's clock is wrong or | 70 // CLient-side characteristics. Check whether or not the system's clock is |
|
felt
2014/07/15 20:44:34
nit: Client-
radhikabhar
2014/07/16 22:35:15
Done.
| |
| 50 // not and whether the user has encountered this error before or not. | 71 // worng and whether or not the user has already encountered this error |
|
felt
2014/07/15 20:44:34
nit: wrong
radhikabhar
2014/07/16 22:35:15
Done.
| |
| 72 // before. | |
| 51 float severity_date_score = 0.0f; | 73 float severity_date_score = 0.0f; |
| 52 | 74 |
| 53 static const float kClientWeight = 0.5f; | 75 static const float kCertificateExpiredWeight = 0.3f; |
| 76 static const float kNotYetValidWeight = 0.2f; | |
| 77 | |
| 54 static const float kSystemClockWeight = 0.75f; | 78 static const float kSystemClockWeight = 0.75f; |
| 55 static const float kSystemClockWrongWeight = 0.1f; | 79 static const float kSystemClockWrongWeight = 0.1f; |
| 56 static const float kSystemClockRightWeight = 1.0f; | 80 static const float kSystemClockRightWeight = 1.0f; |
| 57 | 81 |
| 58 static const float kServerWeight = 0.5f; | |
| 59 static const float kCertificateExpiredWeight = 0.3f; | |
| 60 static const float kNotYetValidWeight = 0.2f; | |
| 61 | |
| 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 |
| 72 // Server-side characteristics. Check whether the certificate has expired or | 92 // 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 | 93 // is not yet valid. If the certificate has expired then factor the time which |
| 74 // has passed since expiry. | 94 // has passed since expiry. |
| 75 if (cert_.HasExpired()) { | 95 if (cert_.HasExpired()) { |
| 76 severity_date_score += kServerWeight * kCertificateExpiredWeight * | 96 severity_date_score += kServerWeight * kCertificateExpiredWeight * |
| 77 CalculateScoreTimePassedSinceExpiry(); | 97 CalculateScoreTimePassedSinceExpiry(); |
| 78 } | 98 } |
| 79 if (current_time_ < cert_.valid_start()) | 99 if (current_time_ < cert_.valid_start()) |
| 80 severity_date_score += kServerWeight * kNotYetValidWeight; | 100 severity_date_score += kServerWeight * kNotYetValidWeight; |
| 81 return severity_date_score; | 101 return severity_date_score; |
| 82 } | 102 } |
| 83 | 103 |
| 104 float SSLErrorClassification::InvalidCommonNameSeverityScore() const { | |
| 105 float severity_name_score = 0.0f; | |
| 106 | |
| 107 static const float kWWWDifferenceWeight = 0.3f; | |
| 108 static const float kSubDomainWeight = 0.2f; | |
| 109 static const float kSubDomainInverseWeight = 1.0f; | |
| 110 | |
| 111 if (IsWWWDifference()) | |
| 112 severity_name_score += kServerWeight * kWWWDifferenceWeight; | |
| 113 if (IsSubDomainMatch()) | |
| 114 severity_name_score += kServerWeight * kSubDomainWeight; | |
| 115 // Inverse case is more likely to be a MITM attack. | |
| 116 if (IsSubDomainInverseMatch()) | |
| 117 severity_name_score += kServerWeight * kSubDomainInverseWeight; | |
| 118 return severity_name_score; | |
| 119 } | |
| 120 | |
| 121 void SSLErrorClassification::RecordUMAStatistics(bool overridable) { | |
|
felt
2014/07/15 20:44:34
Can you add UMA stats for all the other ones too?
radhikabhar
2014/07/16 22:35:14
Done.
| |
| 122 if (IsUserClockInThePast(base::Time::NowFromSystemTime())) | |
| 123 RecordSSLInterstitialCause(overridable, CLOCK_PAST); | |
| 124 if (IsUserClockInTheFuture(base::Time::NowFromSystemTime())) | |
| 125 RecordSSLInterstitialCause(overridable, CLOCK_FUTURE); | |
| 126 } | |
| 127 | |
| 84 base::TimeDelta SSLErrorClassification::TimePassedSinceExpiry() const { | 128 base::TimeDelta SSLErrorClassification::TimePassedSinceExpiry() const { |
| 85 base::TimeDelta delta = current_time_ - cert_.valid_expiry(); | 129 base::TimeDelta delta = current_time_ - cert_.valid_expiry(); |
| 86 return delta; | 130 return delta; |
| 87 } | 131 } |
| 88 | 132 |
| 89 float SSLErrorClassification::CalculateScoreTimePassedSinceExpiry() const { | 133 float SSLErrorClassification::CalculateScoreTimePassedSinceExpiry() const { |
| 90 base::TimeDelta delta = TimePassedSinceExpiry(); | 134 base::TimeDelta delta = TimePassedSinceExpiry(); |
| 91 int64 time_passed = delta.InDays(); | 135 int64 time_passed = delta.InDays(); |
| 92 const int64 kHighThreshold = 7; | 136 const int64 kHighThreshold = 7; |
| 93 const int64 kLowThreshold = 4; | 137 const int64 kLowThreshold = 4; |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 109 return false; | 153 return false; |
| 110 } | 154 } |
| 111 | 155 |
| 112 bool SSLErrorClassification::IsUserClockInTheFuture(base::Time time_now) { | 156 bool SSLErrorClassification::IsUserClockInTheFuture(base::Time time_now) { |
| 113 base::Time build_time = base::GetBuildTime(); | 157 base::Time build_time = base::GetBuildTime(); |
| 114 if (time_now > build_time + base::TimeDelta::FromDays(365)) | 158 if (time_now > build_time + base::TimeDelta::FromDays(365)) |
| 115 return true; | 159 return true; |
| 116 return false; | 160 return false; |
| 117 } | 161 } |
| 118 | 162 |
| 119 void SSLErrorClassification::RecordUMAStatistics(bool overridable) { | 163 bool SSLErrorClassification::IsWWWDifference() const { |
|
felt
2014/07/15 20:44:33
suggestion: rename this IsWWWSubDomainMatch to be
radhikabhar
2014/07/16 22:35:14
Done.
| |
| 120 if (IsUserClockInThePast(base::Time::NowFromSystemTime())) | 164 std::string host_name = request_url_.host(); |
| 121 RecordSSLInterstitialCause(overridable, CLOCK_PAST); | 165 if (request_url_.HostIsIPAddress() || host_name.empty()) |
| 122 if (IsUserClockInTheFuture(base::Time::NowFromSystemTime())) | 166 return false; |
| 123 RecordSSLInterstitialCause(overridable, CLOCK_FUTURE); | 167 |
| 168 std::vector<std::string> dns_names; | |
| 169 cert_.GetDNSNames(&dns_names); | |
|
felt
2014/07/15 20:44:34
spurious indentation
radhikabhar
2014/07/16 22:35:15
Done.
| |
| 170 bool result = false; | |
| 171 | |
| 172 // Need to account for all possible domains given in the SSL certificate. | |
| 173 for (size_t i = 0; i < dns_names.size(); ++i) { | |
| 174 std::string string_prefix; | |
| 175 if (dns_names[i].empty() || dns_names[i].find('\0') != std::string::npos | |
| 176 || dns_names[i].length() == host_name.length()) { | |
| 177 result = result || false; | |
| 178 } else if (dns_names[i].length() > host_name.length()) { | |
| 179 string_prefix = GetStringPrefix(dns_names[i], host_name); | |
|
palmer
2014/07/15 21:23:23
BUG: You really do need to tokenize the hostnames.
radhikabhar
2014/07/16 22:35:15
Done
| |
| 180 } else { | |
| 181 string_prefix = GetStringPrefix(host_name, dns_names[i]); | |
| 182 } | |
| 183 if (!string_prefix.empty() && string_prefix.compare("www.") == 0) | |
| 184 result = result || true; | |
| 185 } | |
| 186 return result; | |
| 124 } | 187 } |
| 188 | |
| 189 bool SSLErrorClassification::IsSubDomainMatch() const { | |
| 190 std::string host_name = request_url_.host(); | |
| 191 if (request_url_.HostIsIPAddress() || host_name.empty()) | |
| 192 return false; | |
| 193 | |
| 194 std::vector<std::string> dns_names; | |
| 195 cert_.GetDNSNames(&dns_names); | |
| 196 bool result = false; | |
| 197 | |
| 198 // Need to account for all the possible domains given in the SSL certificate. | |
| 199 for (size_t i = 0; i < dns_names.size(); ++i) { | |
|
felt
2014/07/15 20:44:34
indentation
| |
| 200 if (dns_names[i].empty() || dns_names[i].find('\0') != std::string::npos | |
| 201 || dns_names[i].length() >= host_name.length()) | |
| 202 result = result || false; | |
| 203 std::string string_prefix = GetStringPrefix(host_name, dns_names[i]); | |
| 204 if (string_prefix.empty() || (string_prefix.compare("www.") == 0)) { | |
|
felt
2014/07/15 20:44:34
Why is this false if the string prefix is www.? Sh
radhikabhar
2014/07/16 22:35:14
Yes but then I wanted to differentiate between the
felt
2014/07/16 23:31:47
ok sounds good
| |
| 205 result = result || false; | |
| 206 } else { | |
| 207 size_t total_count = std::count(string_prefix.begin(), | |
|
felt
2014/07/15 20:44:34
What about the pair: a.foogoogle.com, google.com?
radhikabhar
2014/07/16 22:35:15
You are right. Fixed it by tokenizing the string a
| |
| 208 string_prefix.end(), '.'); | |
| 209 if (total_count == 1) | |
| 210 result = result || true; | |
| 211 } | |
| 212 } | |
| 213 return result; | |
| 214 } | |
| 215 | |
| 216 // The inverse case should be treated carefully as this is most likely a MITM | |
| 217 // attack. | |
|
felt
2014/07/15 20:44:33
Can you explain why, for posterity?
radhikabhar
2014/07/16 22:35:14
Done.
| |
| 218 bool SSLErrorClassification::IsSubDomainInverseMatch() const { | |
| 219 std::string host_name = request_url_.host(); | |
| 220 if (request_url_.HostIsIPAddress() || host_name.empty()) | |
| 221 return false; | |
| 222 | |
| 223 std::vector<std::string> dns_names; | |
| 224 cert_.GetDNSNames(&dns_names); | |
| 225 bool result = false; | |
| 226 | |
| 227 // Need to account for all the possible domains given in the SSL certificate. | |
| 228 for (size_t i = 0; i < dns_names.size(); ++i) { | |
| 229 if (dns_names[i].empty() || dns_names[i].find('\0') != std::string::npos | |
| 230 || dns_names[i].length() <= host_name.length()) | |
| 231 result = result || false; | |
| 232 std::string string_prefix = GetStringPrefix(dns_names[i], host_name); | |
| 233 if (string_prefix.empty() || (string_prefix.compare("www.") == 0)) { | |
| 234 result = result || false; | |
| 235 } else { | |
| 236 size_t total_count = std::count(string_prefix.begin(), | |
| 237 string_prefix.end(),'.'); | |
|
felt
2014/07/15 20:44:34
nit: space between arguments
radhikabhar
2014/07/16 22:35:15
Done.
| |
| 238 if (total_count == 1) | |
| 239 result = result || true; | |
| 240 } | |
| 241 } | |
| 242 return result; | |
| 243 } | |
| 244 | |
| 245 // This method is valid for wildcard certificates only. | |
|
felt
2014/07/15 20:44:34
you should make sure that it returns false if it r
radhikabhar
2014/07/16 22:35:14
I actually do check it for the google cert on line
felt
2014/07/16 23:31:47
missed that, OK
| |
| 246 bool SSLErrorClassification::IsHostNameTooBroad() const { | |
|
felt
2014/07/15 20:44:34
You might want to consider renaming this something
radhikabhar
2014/07/16 22:35:15
Done.
| |
| 247 std::string host_name = request_url_.host(); | |
| 248 if (request_url_.HostIsIPAddress() || host_name.empty()) | |
| 249 return false; | |
| 250 | |
| 251 std::vector<std::string> dns_names; | |
| 252 cert_.GetDNSNames(&dns_names); | |
| 253 bool result = false; | |
| 254 | |
| 255 // This method requires that the host name be longer than the dns name on | |
| 256 // the certificate. | |
| 257 for (size_t i = 0; i < dns_names.size(); ++i) { | |
| 258 if (dns_names[i].find("*") == std::string::npos) { | |
|
felt
2014/07/15 20:44:34
More specifically, you are expecting the first two
radhikabhar
2014/07/16 22:35:14
Done.
| |
| 259 result = result || false; | |
| 260 } else { | |
| 261 if (dns_names[i].empty() || dns_names[i].find('\0') != std::string::npos | |
| 262 || dns_names[i].length() >= host_name.length()) { | |
| 263 result = result || false; | |
| 264 } else { | |
| 265 // Move past the '*' and '.'. | |
| 266 std::string extracted_common_name = dns_names[i].substr(2); | |
| 267 std::string string_prefix = GetStringPrefix(host_name, | |
| 268 extracted_common_name); | |
|
felt
2014/07/15 20:44:33
^ indentation
radhikabhar
2014/07/16 22:35:15
Done.
| |
| 269 size_t total_count = std::count(string_prefix.begin(), | |
| 270 string_prefix.end(), '.'); | |
| 271 if (total_count == 2) | |
| 272 result = result || true; | |
| 273 } | |
| 274 } | |
| 275 } | |
| 276 return result; | |
| 277 } | |
| 278 | |
| 279 bool SSLErrorClassification::IsSelfSigned() const { | |
| 280 // Check whether the issuer and the subject are the same. | |
| 281 const net::CertPrincipal& subject = cert_.subject(); | |
| 282 const net::CertPrincipal& issuer = cert_.issuer(); | |
| 283 bool result = subject.common_name == issuer.common_name && | |
| 284 subject.locality_name == issuer.locality_name && | |
| 285 subject.state_or_province_name == issuer.state_or_province_name && | |
| 286 subject.country_name == issuer.country_name && | |
| 287 subject.street_addresses == issuer.street_addresses && | |
| 288 subject.organization_names == issuer.organization_names && | |
| 289 subject.organization_unit_names == issuer.organization_names && | |
| 290 subject.domain_components == issuer.domain_components; | |
| 291 return result; | |
| 292 } | |
| OLD | NEW |