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 enum CreateDictionaryEntriesDisposition { | |
22 CreateDictionaryEntries, | |
23 DoNotCreateDictionaryEntries | |
24 }; | |
Ryan Sleevi
2014/07/08 23:53:28
Duplicate definition as the .cc. What's up with th
jww
2014/07/11 00:08:40
This was actually fixed in the latest patch set th
| |
25 | |
26 // This is a helper function that returns the length of time before a | |
27 // certificate decision expires based on the command line flags. Returns a | |
28 // non-negative value in seconds, where a value of 0 specifically indicates that | |
29 // decisions should not be remembered after the current session has ended (but | |
30 // should be remembered indefinitely as long as the session does not end), which | |
31 // is the "old" style of certificate decision memory. Returns an int64 rather | |
32 // than uint64 (even though guaranteed to be non-negative) because that's what | |
33 // callers expect. | |
34 int64 GetExpirationDelta() { | |
35 if (!CommandLine::ForCurrentProcess()->HasSwitch( | |
36 switches::kRememberCertErrorDecisions)) | |
37 return 0; | |
Ryan Sleevi
2014/07/08 23:53:28
Braces since you wrap?
jww
2014/07/11 00:08:41
Done.
| |
38 | |
39 std::string switch_value = | |
40 CommandLine::ForCurrentProcess()->GetSwitchValueASCII( | |
41 switches::kRememberCertErrorDecisions); | |
42 // If the expiration is set to 0, that means cert proceed decisions should | |
43 // never expire, until a restart of the profile. | |
44 int64 expiration_delta; | |
45 if (!base::StringToInt64(switch_value, &expiration_delta)) { | |
46 LOG(ERROR) << "Failed to parse the certificate error decision " | |
47 << "memory length: " << switch_value; | |
Ryan Sleevi
2014/07/08 23:53:28
Seems to be spammy if the user didn't pass the swi
jww
2014/07/11 00:08:41
It shouldn't reach this if the user didn't include
| |
48 } else if (expiration_delta < 0) { | |
49 expiration_delta = 0; | |
50 LOG(ERROR) << "Invalid negative certificate error decision memory length: " | |
51 << switch_value; | |
52 } | |
53 | |
54 return expiration_delta; | |
55 } | |
56 | |
57 const char kSSLCertDecisionCertErrorMapKey[] = "cert_exceptions_map"; | |
58 const char kSSLCertDecisionExpirationTimeKey[] = "decision_expiration_time"; | |
59 const char kSSLCertDecisionVersionKey[] = "version"; | |
60 const int kDefaultSSLCertDecisionVersion = 1; | |
Ryan Sleevi
2014/07/08 23:53:28
anal retentive nit: Prefer constants declared firs
jww
2014/07/11 00:08:40
Done.
| |
61 | |
62 std::string GetKey(net::X509Certificate* cert, net::CertStatus error) { | |
63 net::SHA1HashValue fingerprint = cert->fingerprint(); | |
64 std::string base64_fingerprint; | |
65 base::Base64Encode( | |
66 base::StringPiece(reinterpret_cast<const char*>(fingerprint.data), | |
67 sizeof(fingerprint.data)), | |
68 &base64_fingerprint); | |
69 return base::UintToString(error) + base64_fingerprint; | |
70 } | |
71 | |
72 } // namespace | |
73 | |
74 // This helper function gets the dictionary of certificate fingerprints to | |
75 // errors of certificates that have been accepted by the user from the content | |
76 // dictionary that has been passed in. The returned pointer is owned by the the | |
77 // argument dict that is passed in. | |
78 // | |
79 // If create_entries is set to |DoNotCreateDictionaryEntries|, | |
80 // GetValidCertDecisionsDict will return NULL if there is anything invalid about | |
81 // the setting, such as an invalid version or invalid value types (in addition | |
82 // to there not be any values in the dictionary). If create_entries is set to | |
83 // |CreateDictionaryEntries|, if no dictionary is found or the decisions are | |
84 // expired, a new dictionary will be created | |
85 base::DictionaryValue* ChromeSSLHostStateDecisions::GetValidCertDecisionsDict( | |
86 base::DictionaryValue* dict, | |
87 CreateDictionaryEntriesDisposition create_entries) { | |
88 // Extract the version of the certificate decision structure from the content | |
89 // setting. | |
90 int version; | |
91 bool success = dict->GetInteger(kSSLCertDecisionVersionKey, &version); | |
92 if (!success) { | |
93 if (create_entries == DoNotCreateDictionaryEntries) | |
94 return NULL; | |
95 | |
96 dict->SetInteger(kSSLCertDecisionVersionKey, | |
97 kDefaultSSLCertDecisionVersion); | |
98 version = kDefaultSSLCertDecisionVersion; | |
99 } | |
100 | |
101 // If the version is somehow a newer version than Chrome can handle, there's | |
102 // really nothing to do other than fail silently and pretend it doesn't exist | |
103 // (or is malformed). | |
104 if (version > kDefaultSSLCertDecisionVersion) | |
105 return NULL; | |
106 | |
107 // Extract the certificate decision's expiration time from the content | |
108 // setting. If there is no expiration time, that means it should never expire | |
109 // and it should resest only at session restart, so skip all of the expiration | |
110 // checks. | |
111 bool expired = false; | |
112 double decision_expiration_dbl; | |
113 base::Time now = clock_->Now(); | |
114 base::Time decision_expiration; | |
115 if (dict->HasKey(kSSLCertDecisionExpirationTimeKey)) { | |
116 success = dict->GetDouble(kSSLCertDecisionExpirationTimeKey, | |
117 &decision_expiration_dbl); | |
118 decision_expiration = base::Time::FromDoubleT(decision_expiration_dbl); | |
119 } | |
120 | |
121 // Check if the user certificate decision has expired. If it has expired, and | |
122 // create_entries is |DoNotCreateDictionaryEntries|, treat the entry as if it | |
123 // doesn't exist and return |NULL|. If it has expired, and create_entries is | |
124 // |DoNotCreateDictionaryEntries|, set the entry's expiration date to the | |
125 // current time plus the expiration time delta. Also, set expired to |true| | |
126 // which will force the create of a new dictionary in the dictionary | |
127 // extraction pass. None of this should be done if expiration happens on | |
128 // session end (i.e. the expiration delta is 0). | |
Ryan Sleevi
2014/07/08 23:53:28
This feels like it's violating the cardinal rule o
jww
2014/07/11 00:08:41
Done.
| |
129 if (defaultSSLCertDecisionExpirationDelta_.InSeconds() > 0 && | |
130 decision_expiration < now) { | |
131 if (create_entries == DoNotCreateDictionaryEntries) | |
132 return NULL; | |
133 | |
134 expired = true; | |
135 | |
136 // If defaultSSLCertDecisionExpirationDelta_ is 0, that means that decisions | |
Ryan Sleevi
2014/07/08 23:53:28
bad variable name - default_ssl_cert_decision_expi
jww
2014/07/11 00:08:41
Done.
| |
137 // should never expire. Thus, in that case, no key should be set. | |
138 if (defaultSSLCertDecisionExpirationDelta_.InSeconds() != 0) { | |
139 base::Time expiration_time = now + defaultSSLCertDecisionExpirationDelta_; | |
140 dict->SetDouble(kSSLCertDecisionExpirationTimeKey, | |
141 expiration_time.ToDoubleT()); | |
142 } | |
143 } | |
144 | |
145 // Extract the map of certificate fingerprints to errors from the setting. | |
146 base::DictionaryValue* cert_error_dict; // Owned by dict | |
147 if (expired || | |
148 !dict->GetDictionary(kSSLCertDecisionCertErrorMapKey, &cert_error_dict)) { | |
149 if (create_entries == DoNotCreateDictionaryEntries) | |
150 return NULL; | |
151 | |
152 cert_error_dict = new base::DictionaryValue(); | |
153 // dict takes ownership of cert_error_dict | |
154 dict->Set(kSSLCertDecisionCertErrorMapKey, cert_error_dict); | |
155 } | |
156 | |
157 return cert_error_dict; | |
158 } | |
159 | |
160 // If defaultSSLCertDecisionExpirationDelta_ is 0, that means that all invalid | |
161 // certificate proceed decisions should be forgotten when the session ends. At | |
162 // attempt is made in the destructor to remove the entries, but in the case that | |
163 // things didn't shut down cleanly, on start, Clear is called to guarantee a | |
164 // clean state. | |
165 ChromeSSLHostStateDecisions::ChromeSSLHostStateDecisions(Profile* profile) | |
166 : clock_(new base::DefaultClock()), | |
167 defaultSSLCertDecisionExpirationDelta_( | |
168 base::TimeDelta::FromSeconds(GetExpirationDelta())), | |
169 profile_(profile) { | |
170 if (defaultSSLCertDecisionExpirationDelta_.InSeconds() == 0) | |
171 Clear(); | |
172 } | |
173 | |
174 ChromeSSLHostStateDecisions::~ChromeSSLHostStateDecisions() { | |
175 if (defaultSSLCertDecisionExpirationDelta_.InSeconds() == 0) | |
176 Clear(); | |
177 } | |
178 | |
179 void ChromeSSLHostStateDecisions::DenyCert(const GURL& url, | |
180 net::X509Certificate* cert, | |
181 net::CertStatus error) { | |
182 ChangeCertPolicy(url, cert, error, net::CertPolicy::DENIED); | |
183 } | |
184 | |
185 void ChromeSSLHostStateDecisions::AllowCert(const GURL& url, | |
186 net::X509Certificate* cert, | |
187 net::CertStatus error) { | |
188 ChangeCertPolicy(url, cert, error, net::CertPolicy::ALLOWED); | |
189 } | |
190 | |
191 void ChromeSSLHostStateDecisions::Clear() { | |
192 profile_->GetHostContentSettingsMap()->ClearSettingsForOneType( | |
193 CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS); | |
194 } | |
195 | |
196 net::CertPolicy::Judgment ChromeSSLHostStateDecisions::QueryPolicy( | |
197 const GURL& url, | |
198 net::X509Certificate* cert, | |
199 net::CertStatus error) { | |
200 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap(); | |
201 scoped_ptr<base::Value> value(map->GetWebsiteSetting( | |
202 url, url, CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, "", NULL)); | |
Ryan Sleevi
2014/07/08 23:53:28
std::string(), not ""
jww
2014/07/11 00:08:40
Done.
| |
203 | |
204 DCHECK(value.get()); | |
205 | |
206 if (!value->IsType(base::Value::TYPE_DICTIONARY)) | |
207 return net::CertPolicy::UNKNOWN; | |
208 | |
209 base::DictionaryValue* dict; // Owned by value | |
210 int policy_decision; // Owned by dict | |
211 bool success = value->GetAsDictionary(&dict); | |
212 DCHECK(success); | |
213 | |
214 // First, check that the version of the dictionary of decisions is a version | |
215 // that is known. Otherwise, fail. | |
216 base::DictionaryValue* cert_error_dict; // Owned by value | |
217 cert_error_dict = | |
218 GetValidCertDecisionsDict(dict, DoNotCreateDictionaryEntries); | |
219 if (!cert_error_dict) | |
220 return net::CertPolicy::UNKNOWN; | |
221 | |
222 success = cert_error_dict->GetIntegerWithoutPathExpansion(GetKey(cert, error), | |
223 &policy_decision); | |
224 | |
225 if (!success) | |
226 return net::CertPolicy::Judgment::UNKNOWN; | |
227 | |
228 return static_cast<net::CertPolicy::Judgment>(policy_decision); | |
229 } | |
230 | |
231 void ChromeSSLHostStateDecisions::RevokeAllowAndDenyPreferences( | |
232 const GURL& url) { | |
233 const ContentSettingsPattern pattern = | |
234 ContentSettingsPattern::FromURLNoWildcard(url); | |
235 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap(); | |
236 | |
237 map->SetWebsiteSetting( | |
238 pattern, pattern, CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, "", NULL); | |
Ryan Sleevi
2014/07/08 23:53:28
std::string() - and throughout rest of file.
jww
2014/07/11 00:08:41
Done.
| |
239 } | |
240 | |
241 bool ChromeSSLHostStateDecisions::HasAllowedOrDeniedCert(const GURL& url) { | |
242 const ContentSettingsPattern pattern = | |
243 ContentSettingsPattern::FromURLNoWildcard(url); | |
244 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap(); | |
245 | |
246 scoped_ptr<base::Value> value(map->GetWebsiteSetting( | |
247 url, url, CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, "", NULL)); | |
Ryan Sleevi
2014/07/08 23:53:28
Why don't you DCHECK here, but you do on line 204?
jww
2014/07/11 00:08:40
Good catch. The DCHECK on 204 was from testing ear
| |
248 | |
249 if (!value->IsType(base::Value::TYPE_DICTIONARY)) | |
250 return false; | |
251 | |
252 base::DictionaryValue* dict; // Owned by value | |
253 bool success = value->GetAsDictionary(&dict); | |
254 DCHECK(success); | |
255 | |
256 for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) { | |
257 int policy_decision; // Owned by dict | |
258 success = it.value().GetAsInteger(&policy_decision); | |
259 if (success && (static_cast<net::CertPolicy::Judgment>(policy_decision) != | |
260 net::CertPolicy::UNKNOWN)) | |
261 return true; | |
262 } | |
263 | |
264 return false; | |
265 } | |
266 | |
267 void ChromeSSLHostStateDecisions::ChangeCertPolicy( | |
268 const GURL& url, | |
269 net::X509Certificate* cert, | |
270 net::CertStatus error, | |
271 net::CertPolicy::Judgment judgment) { | |
272 const ContentSettingsPattern pattern = | |
273 ContentSettingsPattern::FromURLNoWildcard(url); | |
274 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap(); | |
275 scoped_ptr<base::Value> value(map->GetWebsiteSetting( | |
276 url, url, CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, "", NULL)); | |
Ryan Sleevi
2014/07/08 23:53:28
ditto DCHECK
jww
2014/07/11 00:08:41
See above.
| |
277 | |
278 if (!value->IsType(base::Value::TYPE_DICTIONARY)) | |
279 value.reset(new base::DictionaryValue()); | |
280 | |
281 base::DictionaryValue* dict; | |
282 bool success = value->GetAsDictionary(&dict); | |
283 DCHECK(success); | |
284 | |
285 base::DictionaryValue* cert_dict = | |
286 GetValidCertDecisionsDict(dict, CreateDictionaryEntries); | |
287 // If a a valid certificate dictionary cannot be extracted from the content | |
288 // setting, that means it's in an unknown format. Unfortunately, there's | |
289 // nothing to be done in that case, so a silent fail is the only option. | |
290 if (!cert_dict) | |
291 return; | |
292 | |
293 dict->SetIntegerWithoutPathExpansion(kSSLCertDecisionVersionKey, | |
294 kDefaultSSLCertDecisionVersion); | |
295 cert_dict->SetIntegerWithoutPathExpansion(GetKey(cert, error), judgment); | |
296 | |
297 // The map takes ownership of the value, so it is released in the call to | |
298 // SetWebsiteSetting. | |
299 map->SetWebsiteSetting(pattern, | |
300 pattern, | |
301 CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, | |
302 "", | |
303 value.release()); | |
304 } | |
OLD | NEW |