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

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

Powered by Google App Engine
This is Rietveld 408576698