OLD | NEW |
---|---|
(Empty) | |
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 | |
3 // found in the LICENSE file. | |
4 | |
5 #include "chrome/browser/ssl/chrome_ssl_host_state_decisions.h" | |
6 | |
7 #include "base/base64.h" | |
8 #include "base/command_line.h" | |
9 #include "base/logging.h" | |
10 #include "base/metrics/field_trial.h" | |
11 #include "base/strings/string_number_conversions.h" | |
12 #include "base/time/default_clock.h" | |
13 #include "base/time/time.h" | |
14 #include "chrome/browser/content_settings/host_content_settings_map.h" | |
15 #include "chrome/browser/profiles/profile.h" | |
16 #include "chrome/common/chrome_switches.h" | |
17 #include "components/content_settings/core/common/content_settings_types.h" | |
18 #include "components/variations/variations_associated_data.h" | |
19 #include "net/base/hash_value.h" | |
20 #include "net/cert/x509_certificate.h" | |
21 #include "url/gurl.h" | |
22 | |
23 namespace { | |
24 | |
25 // Switch value that specifies that certificate decisions should be forgotten at | |
26 // the end of the current session. | |
27 const int64 kForgetAtSessionEndSwitchValue = -1; | |
28 | |
29 // Experiment information | |
30 const char kRememberCertificateErrorDecisionsFieldTrialName[] = | |
31 "RememberCertificateErrorDecisions"; | |
32 const char kRememberCertificateErrorDecisionsFieldTrialDefaultGroup[] = | |
33 "Default"; | |
34 const char kRememberCertificateErrorDecisionsFieldTrialLengthParam[] = "length"; | |
35 | |
36 // Keys for the per-site error + certificate finger to judgement content | |
37 // settings map. | |
38 const char kSSLCertDecisionCertErrorMapKey[] = "cert_exceptions_map"; | |
39 const char kSSLCertDecisionExpirationTimeKey[] = "decision_expiration_time"; | |
40 const char kSSLCertDecisionVersionKey[] = "version"; | |
41 | |
42 const int kDefaultSSLCertDecisionVersion = 1; | |
43 | |
44 // This is a helper function that returns the length of time before a | |
45 // certificate decision expires based on the command line flags. Returns a | |
46 // non-negative value in seconds or a value of -1 indicating that decisions | |
47 // should not be remembered after the current session has ended (but should be | |
48 // remembered indefinitely as long as the session does not end), which is the | |
49 // "old" style of certificate decision memory. Uses the experimental group | |
50 // unless overridden by a command line flag. | |
51 int64 GetExpirationDelta() { | |
52 // Check command line flags first to give them priority, then check | |
53 // experimental groups. | |
54 if (CommandLine::ForCurrentProcess()->HasSwitch( | |
55 switches::kRememberCertErrorDecisions)) { | |
56 std::string switch_value = | |
57 CommandLine::ForCurrentProcess()->GetSwitchValueASCII( | |
58 switches::kRememberCertErrorDecisions); | |
59 int64 expiration_delta; | |
60 if (!base::StringToInt64(base::StringPiece(switch_value), | |
61 &expiration_delta) || | |
62 expiration_delta < kForgetAtSessionEndSwitchValue) { | |
63 LOG(ERROR) << "Failed to parse the certificate error decision " | |
64 << "memory length: " << switch_value; | |
65 return kForgetAtSessionEndSwitchValue; | |
66 } | |
67 | |
68 return expiration_delta; | |
69 } | |
Ryan Sleevi
2014/07/31 00:31:28
Is this switch necessary, given that we can force
jww
2014/07/31 05:57:00
Yes, it's necessary because we provide flags in ch
| |
70 | |
71 // If the user is in the field trial, set the expiration to the length | |
72 // associated with that experimental group. The default group cannot have | |
73 // parameters associated with it, so it needs to be handled explictly. | |
74 std::string group_name = base::FieldTrialList::FindFullName( | |
75 kRememberCertificateErrorDecisionsFieldTrialName); | |
76 if (!group_name.empty() && | |
77 group_name.compare( | |
78 kRememberCertificateErrorDecisionsFieldTrialDefaultGroup) != 0) { | |
79 int64 field_trial_param_length; | |
80 std::string param = variations::GetVariationParamValue( | |
81 kRememberCertificateErrorDecisionsFieldTrialName, | |
82 kRememberCertificateErrorDecisionsFieldTrialLengthParam); | |
83 if (!param.empty() && base::StringToInt64(base::StringPiece(param), | |
84 &field_trial_param_length)) { | |
85 return field_trial_param_length; | |
86 } | |
87 } | |
88 | |
89 return kForgetAtSessionEndSwitchValue; | |
90 } | |
91 | |
92 std::string GetKey(net::X509Certificate* cert, net::CertStatus error) { | |
93 // Since a security decision will be made based on the fingerprint, Chrome | |
94 // should use the SHA-256 fingerprint for the certificate. | |
95 net::SHA256HashValue fingerprint = | |
96 net::X509Certificate::CalculateChainFingerprint256( | |
97 cert->os_cert_handle(), cert->GetIntermediateCertificates()); | |
98 std::string base64_fingerprint; | |
99 base::Base64Encode( | |
100 base::StringPiece(reinterpret_cast<const char*>(fingerprint.data), | |
101 sizeof(fingerprint.data)), | |
102 &base64_fingerprint); | |
103 return base::UintToString(error) + base64_fingerprint; | |
104 } | |
105 | |
106 } // namespace | |
107 | |
108 // This helper function gets the dictionary of certificate fingerprints to | |
109 // errors of certificates that have been accepted by the user from the content | |
110 // dictionary that has been passed in. The returned pointer is owned by the the | |
111 // argument dict that is passed in. | |
112 // | |
113 // If create_entries is set to |DoNotCreateDictionaryEntries|, | |
114 // GetValidCertDecisionsDict will return NULL if there is anything invalid about | |
115 // the setting, such as an invalid version or invalid value types (in addition | |
116 // to there not be any values in the dictionary). If create_entries is set to | |
117 // |CreateDictionaryEntries|, if no dictionary is found or the decisions are | |
118 // expired, a new dictionary will be created | |
119 base::DictionaryValue* ChromeSSLHostStateDecisions::GetValidCertDecisionsDict( | |
120 base::DictionaryValue* dict, | |
121 CreateDictionaryEntriesDisposition create_entries) { | |
122 // Extract the version of the certificate decision structure from the content | |
123 // setting. | |
124 int version; | |
125 bool success = dict->GetInteger(kSSLCertDecisionVersionKey, &version); | |
126 if (!success) { | |
127 if (create_entries == DoNotCreateDictionaryEntries) | |
128 return NULL; | |
129 | |
130 dict->SetInteger(kSSLCertDecisionVersionKey, | |
131 kDefaultSSLCertDecisionVersion); | |
132 version = kDefaultSSLCertDecisionVersion; | |
133 } | |
134 | |
135 // If the version is somehow a newer version than Chrome can handle, there's | |
136 // really nothing to do other than fail silently and pretend it doesn't exist | |
137 // (or is malformed). | |
138 if (version > kDefaultSSLCertDecisionVersion) { | |
139 LOG(ERROR) << "Failed to parse a certificate error exception that is in a " | |
140 << "newer version format (" << version << ") than is supported (" | |
141 << kDefaultSSLCertDecisionVersion << ")"; | |
142 return NULL; | |
143 } | |
144 | |
145 // Extract the certificate decision's expiration time from the content | |
146 // setting. If there is no expiration time, that means it should never expire | |
147 // and it should reset only at session restart, so skip all of the expiration | |
148 // checks. | |
149 bool expired = false; | |
150 base::Time now = clock_->Now(); | |
151 base::Time decision_expiration; | |
152 if (dict->HasKey(kSSLCertDecisionExpirationTimeKey)) { | |
153 std::string decision_expiration_string; | |
154 int64 decision_expiration_int64; | |
155 success = dict->GetString(kSSLCertDecisionExpirationTimeKey, | |
156 &decision_expiration_string); | |
157 if (!base::StringToInt64(base::StringPiece(decision_expiration_string), | |
158 &decision_expiration_int64)) { | |
159 LOG(ERROR) << "Failed to parse a certificate error exception that has a " | |
160 << "bad value for an expiration time: " | |
161 << decision_expiration_string; | |
162 return NULL; | |
163 } | |
164 decision_expiration = | |
165 base::Time::FromInternalValue(decision_expiration_int64); | |
166 } | |
167 | |
168 // Check to see if the user's certificate decision has expired. | |
169 // - Expired and |create_entries| is DoNotCreateDictionaryEntries, return | |
170 // NULL. | |
171 // - Expired and |create_entries| is CreateDictionaryEntries, update the | |
172 // expiration time. | |
173 if (should_remember_ssl_decisions_ != | |
174 ForgetSSLExceptionDecisionsAtSessionEnd && | |
175 decision_expiration.ToInternalValue() <= now.ToInternalValue()) { | |
176 if (create_entries == DoNotCreateDictionaryEntries) | |
177 return NULL; | |
178 | |
179 expired = true; | |
180 | |
181 base::Time expiration_time = | |
182 now + default_ssl_cert_decision_expiration_delta_; | |
183 // Unfortunately, JSON (and thus content settings) doesn't support int64 | |
184 // values, only doubles. Since this mildly depends on precision, it is | |
185 // better to store the value as a string. | |
186 dict->SetString(kSSLCertDecisionExpirationTimeKey, | |
187 base::Int64ToString(expiration_time.ToInternalValue())); | |
188 } | |
189 | |
190 // Extract the map of certificate fingerprints to errors from the setting. | |
191 base::DictionaryValue* cert_error_dict; // Will be owned by dict | |
192 if (expired || | |
193 !dict->GetDictionary(kSSLCertDecisionCertErrorMapKey, &cert_error_dict)) { | |
194 if (create_entries == DoNotCreateDictionaryEntries) | |
195 return NULL; | |
196 | |
197 cert_error_dict = new base::DictionaryValue(); | |
198 // dict takes ownership of cert_error_dict | |
199 dict->Set(kSSLCertDecisionCertErrorMapKey, cert_error_dict); | |
200 } | |
201 | |
202 return cert_error_dict; | |
203 } | |
204 | |
205 // If |should_remember_ssl_decisions_| is | |
206 // ForgetSSLExceptionDecisionsAtSessionEnd, that means that all invalid | |
207 // certificate proceed decisions should be forgotten when the session ends. At | |
208 // attempt is made in the destructor to remove the entries, but in the case that | |
209 // things didn't shut down cleanly, on start, Clear is called to guarantee a | |
210 // clean state. | |
211 ChromeSSLHostStateDecisions::ChromeSSLHostStateDecisions(Profile* profile) | |
212 : clock_(new base::DefaultClock()), profile_(profile) { | |
213 int64 expiration_delta = GetExpirationDelta(); | |
214 if (expiration_delta == kForgetAtSessionEndSwitchValue) { | |
215 should_remember_ssl_decisions_ = ForgetSSLExceptionDecisionsAtSessionEnd; | |
216 expiration_delta = 0; | |
217 Clear(); | |
218 } else { | |
219 should_remember_ssl_decisions_ = RememberSSLExceptionDecisionsForDelta; | |
220 } | |
221 default_ssl_cert_decision_expiration_delta_ = | |
222 base::TimeDelta::FromSeconds(expiration_delta); | |
223 } | |
224 | |
225 ChromeSSLHostStateDecisions::~ChromeSSLHostStateDecisions() { | |
226 if (should_remember_ssl_decisions_ == ForgetSSLExceptionDecisionsAtSessionEnd) | |
227 Clear(); | |
228 } | |
229 | |
230 void ChromeSSLHostStateDecisions::DenyCert(const GURL& url, | |
231 net::X509Certificate* cert, | |
232 net::CertStatus error) { | |
233 ChangeCertPolicy(url, cert, error, net::CertPolicy::DENIED); | |
234 } | |
235 | |
236 void ChromeSSLHostStateDecisions::AllowCert(const GURL& url, | |
237 net::X509Certificate* cert, | |
238 net::CertStatus error) { | |
239 ChangeCertPolicy(url, cert, error, net::CertPolicy::ALLOWED); | |
240 } | |
241 | |
242 void ChromeSSLHostStateDecisions::Clear() { | |
243 profile_->GetHostContentSettingsMap()->ClearSettingsForOneType( | |
244 CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS); | |
245 } | |
246 | |
247 net::CertPolicy::Judgment ChromeSSLHostStateDecisions::QueryPolicy( | |
248 const GURL& url, | |
249 net::X509Certificate* cert, | |
250 net::CertStatus error) { | |
251 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap(); | |
252 scoped_ptr<base::Value> value( | |
253 map->GetWebsiteSetting(url.GetWithEmptyPath(), | |
254 url.GetWithEmptyPath(), | |
255 CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, | |
256 std::string(), | |
257 NULL)); | |
258 | |
259 if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY)) | |
260 return net::CertPolicy::UNKNOWN; | |
261 | |
262 base::DictionaryValue* dict; // Owned by value | |
263 int policy_decision; | |
264 bool success = value->GetAsDictionary(&dict); | |
265 DCHECK(success); | |
266 | |
267 base::DictionaryValue* cert_error_dict; // Owned by value | |
268 cert_error_dict = | |
269 GetValidCertDecisionsDict(dict, DoNotCreateDictionaryEntries); | |
270 if (!cert_error_dict) | |
271 return net::CertPolicy::UNKNOWN; | |
272 | |
273 success = cert_error_dict->GetIntegerWithoutPathExpansion(GetKey(cert, error), | |
274 &policy_decision); | |
275 | |
276 if (!success) | |
277 return net::CertPolicy::Judgment::UNKNOWN; | |
278 | |
279 return static_cast<net::CertPolicy::Judgment>(policy_decision); | |
Ryan Sleevi
2014/07/31 00:31:28
SECURITY: Seems like you should bounds check this,
jww
2014/07/31 05:57:00
Done.
| |
280 } | |
281 | |
282 void ChromeSSLHostStateDecisions::RevokeAllowAndDenyPreferences( | |
283 const GURL& url) { | |
284 const ContentSettingsPattern pattern = | |
285 ContentSettingsPattern::FromURLNoWildcard(url.GetWithEmptyPath()); | |
286 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap(); | |
287 | |
288 map->SetWebsiteSetting(pattern, | |
289 pattern, | |
290 CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, | |
291 std::string(), | |
292 NULL); | |
293 } | |
294 | |
295 bool ChromeSSLHostStateDecisions::HasAllowedOrDeniedCert(const GURL& url) { | |
296 const ContentSettingsPattern pattern = | |
297 ContentSettingsPattern::FromURLNoWildcard(url.GetWithEmptyPath()); | |
298 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap(); | |
299 | |
300 scoped_ptr<base::Value> value( | |
301 map->GetWebsiteSetting(url.GetWithEmptyPath(), | |
302 url.GetWithEmptyPath(), | |
303 CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, | |
304 std::string(), | |
305 NULL)); | |
306 | |
307 if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY)) | |
308 return false; | |
309 | |
310 base::DictionaryValue* dict; // Owned by value | |
311 bool success = value->GetAsDictionary(&dict); | |
312 DCHECK(success); | |
313 | |
314 for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) { | |
315 int policy_decision; // Owned by dict | |
316 success = it.value().GetAsInteger(&policy_decision); | |
317 if (success && (static_cast<net::CertPolicy::Judgment>(policy_decision) != | |
318 net::CertPolicy::UNKNOWN)) | |
319 return true; | |
320 } | |
321 | |
322 return false; | |
323 } | |
324 | |
325 void ChromeSSLHostStateDecisions::ChangeCertPolicy( | |
326 const GURL& url, | |
327 net::X509Certificate* cert, | |
328 net::CertStatus error, | |
329 net::CertPolicy::Judgment judgment) { | |
330 const ContentSettingsPattern pattern = | |
331 ContentSettingsPattern::FromURLNoWildcard(url.GetWithEmptyPath()); | |
332 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap(); | |
333 scoped_ptr<base::Value> value( | |
334 map->GetWebsiteSetting(url.GetWithEmptyPath(), | |
335 url.GetWithEmptyPath(), | |
336 CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, | |
337 std::string(), | |
338 NULL)); | |
339 | |
340 if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY)) | |
341 value.reset(new base::DictionaryValue()); | |
342 | |
343 base::DictionaryValue* dict; | |
344 bool success = value->GetAsDictionary(&dict); | |
345 DCHECK(success); | |
346 | |
347 base::DictionaryValue* cert_dict = | |
348 GetValidCertDecisionsDict(dict, CreateDictionaryEntries); | |
349 // If a a valid certificate dictionary cannot be extracted from the content | |
350 // setting, that means it's in an unknown format. Unfortunately, there's | |
351 // nothing to be done in that case, so a silent fail is the only option. | |
352 if (!cert_dict) | |
353 return; | |
354 | |
355 dict->SetIntegerWithoutPathExpansion(kSSLCertDecisionVersionKey, | |
356 kDefaultSSLCertDecisionVersion); | |
357 cert_dict->SetIntegerWithoutPathExpansion(GetKey(cert, error), judgment); | |
358 | |
359 // The map takes ownership of the value, so it is released in the call to | |
360 // SetWebsiteSetting. | |
361 map->SetWebsiteSetting(pattern, | |
362 pattern, | |
363 CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, | |
364 std::string(), | |
365 value.release()); | |
366 } | |
OLD | NEW |