Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(58)

Side by Side Diff: chrome/browser/ssl/chrome_ssl_host_state_decisions.cc

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

Powered by Google App Engine
This is Rietveld 408576698