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