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/strings/string_number_conversions.h" | |
11 #include "base/time/default_clock.h" | |
12 #include "base/time/time.h" | |
13 #include "chrome/browser/content_settings/host_content_settings_map.h" | |
14 #include "chrome/browser/profiles/profile.h" | |
15 #include "chrome/common/chrome_switches.h" | |
16 #include "chrome/common/content_settings_types.h" | |
17 #include "url/gurl.h" | |
18 | |
19 namespace { | |
20 | |
21 // Time expiration values in seconds for the command line switches for turning | |
22 // on certificate error decision time based memory. | |
23 const int64 kForgetAtSessionEndSwitchValue = -1; | |
24 const int64 kExpirationZeroInSeconds = 0; | |
25 const int64 kExpirationOneDayInSeconds = 86400; | |
26 const int64 kExpirationThreeDaysInSeconds = 259200; | |
27 const int64 kExpirationOneWeekInSeconds = 604800; | |
28 const int64 kExpirationOneMonthInSeconds = 2592000; | |
29 | |
30 // Keys for the per-site error + certificate finger to judgement content | |
31 // settings map. | |
32 const char kSSLCertDecisionCertErrorMapKey[] = "cert_exceptions_map"; | |
33 const char kSSLCertDecisionExpirationTimeKey[] = "decision_expiration_time"; | |
34 const char kSSLCertDecisionVersionKey[] = "version"; | |
35 | |
36 const int kDefaultSSLCertDecisionVersion = 1; | |
37 | |
38 // This is a helper function that returns the length of time before a | |
39 // certificate decision expires based on the command line flags. Returns a | |
40 // non-negative value in seconds or a value of -1 indicating that decisions | |
41 // should not be remembered after the current session has ended (but should be | |
42 // remembered indefinitely as long as the session does not end), which is the | |
43 // "old" style of certificate decision memory. | |
44 int64 GetExpirationDelta() { | |
45 if (CommandLine::ForCurrentProcess()->HasSwitch( | |
46 switches::kRememberCertErrorDecisionsDisable)) { | |
47 return kForgetAtSessionEndSwitchValue; | |
48 } else if (CommandLine::ForCurrentProcess()->HasSwitch( | |
49 switches::kRememberCertErrorDecisionsNone)) { | |
50 return kExpirationZeroInSeconds; | |
51 } else if (CommandLine::ForCurrentProcess()->HasSwitch( | |
52 switches::kRememberCertErrorDecisionsOneDay)) { | |
53 return kExpirationOneDayInSeconds; | |
54 } else if (CommandLine::ForCurrentProcess()->HasSwitch( | |
55 switches::kRememberCertErrorDecisionsThreeDays)) { | |
56 return kExpirationThreeDaysInSeconds; | |
57 } else if (CommandLine::ForCurrentProcess()->HasSwitch( | |
58 switches::kRememberCertErrorDecisionsOneWeek)) { | |
59 return kExpirationOneWeekInSeconds; | |
60 } else if (CommandLine::ForCurrentProcess()->HasSwitch( | |
61 switches::kRememberCertErrorDecisionsOneMonth)) { | |
Ryan Sleevi
2014/07/17 23:19:35
Can we cap at a week? I'm a little nervous for a m
jww
2014/07/21 23:39:32
There's actually a good chance we want to do longe
| |
62 return kExpirationOneMonthInSeconds; | |
63 } | |
64 | |
65 return kForgetAtSessionEndSwitchValue; | |
66 } | |
67 | |
68 std::string GetKey(net::X509Certificate* cert, net::CertStatus error) { | |
69 net::SHA1HashValue fingerprint = cert->fingerprint(); | |
70 std::string base64_fingerprint; | |
71 base::Base64Encode( | |
72 base::StringPiece(reinterpret_cast<const char*>(fingerprint.data), | |
73 sizeof(fingerprint.data)), | |
74 &base64_fingerprint); | |
75 return base::UintToString(error) + base64_fingerprint; | |
76 } | |
77 | |
78 } // namespace | |
79 | |
80 // This helper function gets the dictionary of certificate fingerprints to | |
81 // errors of certificates that have been accepted by the user from the content | |
82 // dictionary that has been passed in. The returned pointer is owned by the the | |
83 // argument dict that is passed in. | |
84 // | |
85 // If create_entries is set to |DoNotCreateDictionaryEntries|, | |
86 // GetValidCertDecisionsDict will return NULL if there is anything invalid about | |
87 // the setting, such as an invalid version or invalid value types (in addition | |
88 // to there not be any values in the dictionary). If create_entries is set to | |
89 // |CreateDictionaryEntries|, if no dictionary is found or the decisions are | |
90 // expired, a new dictionary will be created | |
91 base::DictionaryValue* ChromeSSLHostStateDecisions::GetValidCertDecisionsDict( | |
92 base::DictionaryValue* dict, | |
93 CreateDictionaryEntriesDisposition create_entries) { | |
94 // Extract the version of the certificate decision structure from the content | |
95 // setting. | |
96 int version; | |
97 bool success = dict->GetInteger(kSSLCertDecisionVersionKey, &version); | |
98 if (!success) { | |
99 if (create_entries == DoNotCreateDictionaryEntries) | |
100 return NULL; | |
101 | |
102 dict->SetInteger(kSSLCertDecisionVersionKey, | |
103 kDefaultSSLCertDecisionVersion); | |
104 version = kDefaultSSLCertDecisionVersion; | |
105 } | |
106 | |
107 // If the version is somehow a newer version than Chrome can handle, there's | |
108 // really nothing to do other than fail silently and pretend it doesn't exist | |
109 // (or is malformed). | |
110 if (version > kDefaultSSLCertDecisionVersion) { | |
111 LOG(ERROR) << "Failed to parse a certificate error exception that is in a " | |
112 << "newer version format (" << version << ") than is supported (" | |
113 << kDefaultSSLCertDecisionVersion << ")"; | |
114 return NULL; | |
115 } | |
116 | |
117 // Extract the certificate decision's expiration time from the content | |
118 // setting. If there is no expiration time, that means it should never expire | |
119 // and it should resest only at session restart, so skip all of the expiration | |
Ryan Sleevi
2014/07/17 23:19:35
typo, s/resest/reset
jww
2014/07/21 23:39:32
Done.
| |
120 // checks. | |
121 bool expired = false; | |
122 base::Time now = clock_->Now(); | |
123 base::Time decision_expiration; | |
124 if (dict->HasKey(kSSLCertDecisionExpirationTimeKey)) { | |
125 std::string decision_expiration_string; | |
126 int64 decision_expiration_int64; | |
127 success = dict->GetString(kSSLCertDecisionExpirationTimeKey, | |
128 &decision_expiration_string); | |
129 if (!base::StringToInt64(base::StringPiece(decision_expiration_string), | |
130 &decision_expiration_int64)) { | |
131 LOG(ERROR) << "Failed to parse a certificate error exception that has a " | |
132 << "bad value for an expiration time: " | |
133 << decision_expiration_string; | |
134 return NULL; | |
135 } | |
136 decision_expiration = | |
137 base::Time::FromInternalValue(decision_expiration_int64); | |
138 } | |
139 | |
140 // Check to see if the user's certificate decision has expired. | |
141 // - Expired and |create_entries| is DoNotCreateDictionaryEntries, return | |
142 // NULL. | |
143 // - Expired and |create_entries| is CreateDictionaryEntries, update the | |
144 // expiration time. | |
145 if (should_remember_ssl_decisions_ != | |
146 ForgetSSLExceptionDecisionsAtSessionEnd && | |
147 decision_expiration.ToInternalValue() <= now.ToInternalValue()) { | |
148 if (create_entries == DoNotCreateDictionaryEntries) | |
149 return NULL; | |
150 | |
151 expired = true; | |
152 | |
153 base::Time expiration_time = | |
154 now + default_ssl_cert_decision_expiration_delta_; | |
155 // Unfortunately, JSON (and thus content settings) doesn't support int64 | |
156 // values, only doubles. Since this mildly depends on precision, it is | |
157 // better to store the value as a string. | |
158 dict->SetString(kSSLCertDecisionExpirationTimeKey, | |
159 base::Int64ToString(expiration_time.ToInternalValue())); | |
160 } | |
Ryan Sleevi
2014/07/17 23:19:35
What about if the user sets their clock a week ahe
jww
2014/07/21 23:39:32
They will get a longer expiration time. That is, i
| |
161 | |
162 // Extract the map of certificate fingerprints to errors from the setting. | |
163 base::DictionaryValue* cert_error_dict; // Will be owned by dict | |
164 if (expired || | |
165 !dict->GetDictionary(kSSLCertDecisionCertErrorMapKey, &cert_error_dict)) { | |
166 if (create_entries == DoNotCreateDictionaryEntries) | |
167 return NULL; | |
168 | |
169 cert_error_dict = new base::DictionaryValue(); | |
170 // dict takes ownership of cert_error_dict | |
171 dict->Set(kSSLCertDecisionCertErrorMapKey, cert_error_dict); | |
172 } | |
173 | |
174 return cert_error_dict; | |
175 } | |
176 | |
177 // If should_remember_ssl_decisions_ is ForgetSSLExceptionDecisionsAtSessionEnd, | |
Ryan Sleevi
2014/07/17 23:19:35
add ||
jww
2014/07/21 23:39:32
Done.
| |
178 // that means that all invalid certificate proceed decisions should be forgotten | |
179 // when the session ends. At attempt is made in the destructor to remove the | |
180 // entries, but in the case that things didn't shut down cleanly, on start, | |
181 // Clear is called to guarantee a clean state. | |
182 ChromeSSLHostStateDecisions::ChromeSSLHostStateDecisions(Profile* profile) | |
183 : clock_(new base::DefaultClock()), profile_(profile) { | |
184 int64 expiration_delta = GetExpirationDelta(); | |
185 if (expiration_delta == kForgetAtSessionEndSwitchValue) { | |
186 should_remember_ssl_decisions_ = ForgetSSLExceptionDecisionsAtSessionEnd; | |
187 expiration_delta = 0; | |
188 Clear(); | |
189 } else { | |
190 should_remember_ssl_decisions_ = RememberSSLExceptionDecisionsForDelta; | |
191 } | |
192 default_ssl_cert_decision_expiration_delta_ = | |
193 base::TimeDelta::FromSeconds(expiration_delta); | |
194 } | |
195 | |
196 ChromeSSLHostStateDecisions::~ChromeSSLHostStateDecisions() { | |
197 if (should_remember_ssl_decisions_ == ForgetSSLExceptionDecisionsAtSessionEnd) | |
198 Clear(); | |
199 } | |
200 | |
201 void ChromeSSLHostStateDecisions::DenyCert(const GURL& url, | |
202 net::X509Certificate* cert, | |
203 net::CertStatus error) { | |
204 ChangeCertPolicy(url, cert, error, net::CertPolicy::DENIED); | |
205 } | |
206 | |
207 void ChromeSSLHostStateDecisions::AllowCert(const GURL& url, | |
208 net::X509Certificate* cert, | |
209 net::CertStatus error) { | |
210 ChangeCertPolicy(url, cert, error, net::CertPolicy::ALLOWED); | |
211 } | |
212 | |
213 void ChromeSSLHostStateDecisions::Clear() { | |
214 profile_->GetHostContentSettingsMap()->ClearSettingsForOneType( | |
215 CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS); | |
216 } | |
217 | |
218 net::CertPolicy::Judgment ChromeSSLHostStateDecisions::QueryPolicy( | |
219 const GURL& url, | |
220 net::X509Certificate* cert, | |
221 net::CertStatus error) { | |
222 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap(); | |
223 scoped_ptr<base::Value> value(map->GetWebsiteSetting( | |
224 url, url, CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, std::string(), NULL)); | |
225 | |
226 if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY)) | |
227 return net::CertPolicy::UNKNOWN; | |
228 | |
229 base::DictionaryValue* dict; // Owned by value | |
230 int policy_decision; // Owned by dict | |
Ryan Sleevi
2014/07/17 23:19:35
This comment doesn't make sense.
jww
2014/07/21 23:39:32
Ha. Nope, it certainly does not. Removing.
| |
231 bool success = value->GetAsDictionary(&dict); | |
232 DCHECK(success); | |
233 | |
234 base::DictionaryValue* cert_error_dict; // Owned by value | |
235 cert_error_dict = | |
236 GetValidCertDecisionsDict(dict, DoNotCreateDictionaryEntries); | |
237 if (!cert_error_dict) | |
238 return net::CertPolicy::UNKNOWN; | |
239 | |
240 success = cert_error_dict->GetIntegerWithoutPathExpansion(GetKey(cert, error), | |
241 &policy_decision); | |
242 | |
243 if (!success) | |
244 return net::CertPolicy::Judgment::UNKNOWN; | |
245 | |
246 return static_cast<net::CertPolicy::Judgment>(policy_decision); | |
247 } | |
248 | |
249 void ChromeSSLHostStateDecisions::RevokeAllowAndDenyPreferences( | |
250 const GURL& url) { | |
251 const ContentSettingsPattern pattern = | |
252 ContentSettingsPattern::FromURLNoWildcard(url); | |
253 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap(); | |
254 | |
255 map->SetWebsiteSetting(pattern, | |
256 pattern, | |
257 CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, | |
258 std::string(), | |
259 NULL); | |
260 } | |
261 | |
262 bool ChromeSSLHostStateDecisions::HasAllowedOrDeniedCert(const GURL& url) { | |
263 const ContentSettingsPattern pattern = | |
264 ContentSettingsPattern::FromURLNoWildcard(url); | |
265 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap(); | |
266 | |
267 scoped_ptr<base::Value> value(map->GetWebsiteSetting( | |
268 url, url, CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, std::string(), NULL)); | |
269 | |
270 if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY)) | |
271 return false; | |
272 | |
273 base::DictionaryValue* dict; // Owned by value | |
274 bool success = value->GetAsDictionary(&dict); | |
275 DCHECK(success); | |
276 | |
277 for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) { | |
278 int policy_decision; // Owned by dict | |
279 success = it.value().GetAsInteger(&policy_decision); | |
280 if (success && (static_cast<net::CertPolicy::Judgment>(policy_decision) != | |
281 net::CertPolicy::UNKNOWN)) | |
282 return true; | |
283 } | |
284 | |
285 return false; | |
286 } | |
287 | |
288 void ChromeSSLHostStateDecisions::ChangeCertPolicy( | |
289 const GURL& url, | |
290 net::X509Certificate* cert, | |
291 net::CertStatus error, | |
292 net::CertPolicy::Judgment judgment) { | |
293 const ContentSettingsPattern pattern = | |
294 ContentSettingsPattern::FromURLNoWildcard(url); | |
295 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap(); | |
296 scoped_ptr<base::Value> value(map->GetWebsiteSetting( | |
297 url, url, CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, std::string(), NULL)); | |
298 | |
299 if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY)) | |
300 value.reset(new base::DictionaryValue()); | |
301 | |
302 base::DictionaryValue* dict; | |
303 bool success = value->GetAsDictionary(&dict); | |
304 DCHECK(success); | |
305 | |
306 base::DictionaryValue* cert_dict = | |
307 GetValidCertDecisionsDict(dict, CreateDictionaryEntries); | |
308 // If a a valid certificate dictionary cannot be extracted from the content | |
309 // setting, that means it's in an unknown format. Unfortunately, there's | |
310 // nothing to be done in that case, so a silent fail is the only option. | |
311 if (!cert_dict) | |
312 return; | |
313 | |
314 dict->SetIntegerWithoutPathExpansion(kSSLCertDecisionVersionKey, | |
315 kDefaultSSLCertDecisionVersion); | |
316 cert_dict->SetIntegerWithoutPathExpansion(GetKey(cert, error), judgment); | |
317 | |
318 // The map takes ownership of the value, so it is released in the call to | |
319 // SetWebsiteSetting. | |
320 map->SetWebsiteSetting(pattern, | |
321 pattern, | |
322 CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, | |
323 std::string(), | |
324 value.release()); | |
325 } | |
OLD | NEW |