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 |