base::Time multiplicative operator overloading
[chromium-blink-merge.git] / chrome / browser / ssl / chrome_ssl_host_state_delegate.cc
blobcf8f5209eda9f1cf7e7fa2d5efe7e85d91e104c2
1 // Copyright 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.
5 #include "chrome/browser/ssl/chrome_ssl_host_state_delegate.h"
7 #include <set>
9 #include "base/base64.h"
10 #include "base/bind.h"
11 #include "base/command_line.h"
12 #include "base/guid.h"
13 #include "base/logging.h"
14 #include "base/metrics/field_trial.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/time/clock.h"
17 #include "base/time/default_clock.h"
18 #include "base/time/time.h"
19 #include "base/values.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/common/chrome_switches.h"
22 #include "components/content_settings/core/browser/host_content_settings_map.h"
23 #include "components/content_settings/core/common/content_settings_types.h"
24 #include "components/variations/variations_associated_data.h"
25 #include "net/base/hash_value.h"
26 #include "net/base/net_util.h"
27 #include "net/cert/x509_certificate.h"
28 #include "net/http/http_transaction_factory.h"
29 #include "net/url_request/url_request_context.h"
30 #include "net/url_request/url_request_context_getter.h"
31 #include "url/gurl.h"
33 namespace {
35 // Switch value that specifies that certificate decisions should be forgotten at
36 // the end of the current session.
37 const int64 kForgetAtSessionEndSwitchValue = -1;
39 // Experiment information
40 const char kRememberCertificateErrorDecisionsFieldTrialName[] =
41 "RememberCertificateErrorDecisions";
42 const char kRememberCertificateErrorDecisionsFieldTrialDefaultGroup[] =
43 "Default";
44 const char kRememberCertificateErrorDecisionsFieldTrialLengthParam[] = "length";
46 // Keys for the per-site error + certificate finger to judgment content
47 // settings map.
48 const char kSSLCertDecisionCertErrorMapKey[] = "cert_exceptions_map";
49 const char kSSLCertDecisionExpirationTimeKey[] = "decision_expiration_time";
50 const char kSSLCertDecisionVersionKey[] = "version";
51 const char kSSLCertDecisionGUIDKey[] = "guid";
53 const int kDefaultSSLCertDecisionVersion = 1;
55 void CloseIdleConnections(
56 scoped_refptr<net::URLRequestContextGetter> url_request_context_getter) {
57 url_request_context_getter->
58 GetURLRequestContext()->
59 http_transaction_factory()->
60 GetSession()->
61 CloseIdleConnections();
64 // All SSL decisions are per host (and are shared arcoss schemes), so this
65 // canonicalizes all hosts into a secure scheme GURL to use with content
66 // settings. The returned GURL will be the passed in host with an empty path and
67 // https:// as the scheme.
68 GURL GetSecureGURLForHost(const std::string& host) {
69 std::string url = "https://" + host;
70 return GURL(url);
73 // This is a helper function that returns the length of time before a
74 // certificate decision expires based on the command line flags. Returns a
75 // non-negative value in seconds or a value of -1 indicating that decisions
76 // should not be remembered after the current session has ended (but should be
77 // remembered indefinitely as long as the session does not end), which is the
78 // "old" style of certificate decision memory. Uses the experimental group
79 // unless overridden by a command line flag.
80 int64 GetExpirationDelta() {
81 // Check command line flags first to give them priority, then check
82 // experimental groups.
83 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
84 switches::kRememberCertErrorDecisions)) {
85 std::string switch_value =
86 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
87 switches::kRememberCertErrorDecisions);
88 int64 expiration_delta;
89 if (!base::StringToInt64(base::StringPiece(switch_value),
90 &expiration_delta) ||
91 expiration_delta < kForgetAtSessionEndSwitchValue) {
92 LOG(ERROR) << "Failed to parse the certificate error decision "
93 << "memory length: " << switch_value;
94 return kForgetAtSessionEndSwitchValue;
97 return expiration_delta;
100 // If the user is in the field trial, set the expiration to the length
101 // associated with that experimental group. The default group cannot have
102 // parameters associated with it, so it needs to be handled explictly.
103 std::string group_name = base::FieldTrialList::FindFullName(
104 kRememberCertificateErrorDecisionsFieldTrialName);
105 if (!group_name.empty() &&
106 group_name.compare(
107 kRememberCertificateErrorDecisionsFieldTrialDefaultGroup) != 0) {
108 int64 field_trial_param_length;
109 std::string param = variations::GetVariationParamValue(
110 kRememberCertificateErrorDecisionsFieldTrialName,
111 kRememberCertificateErrorDecisionsFieldTrialLengthParam);
112 if (!param.empty() && base::StringToInt64(base::StringPiece(param),
113 &field_trial_param_length)) {
114 return field_trial_param_length;
118 return kForgetAtSessionEndSwitchValue;
121 std::string GetKey(const net::X509Certificate& cert, net::CertStatus error) {
122 // Since a security decision will be made based on the fingerprint, Chrome
123 // should use the SHA-256 fingerprint for the certificate.
124 net::SHA256HashValue fingerprint =
125 net::X509Certificate::CalculateChainFingerprint256(
126 cert.os_cert_handle(), cert.GetIntermediateCertificates());
127 std::string base64_fingerprint;
128 base::Base64Encode(
129 base::StringPiece(reinterpret_cast<const char*>(fingerprint.data),
130 sizeof(fingerprint.data)),
131 &base64_fingerprint);
132 return base::UintToString(error) + base64_fingerprint;
135 } // namespace
137 // This helper function gets the dictionary of certificate fingerprints to
138 // errors of certificates that have been accepted by the user from the content
139 // dictionary that has been passed in. The returned pointer is owned by the the
140 // argument dict that is passed in.
142 // If create_entries is set to |DO_NOT_CREATE_DICTIONARY_ENTRIES|,
143 // GetValidCertDecisionsDict will return NULL if there is anything invalid about
144 // the setting, such as an invalid version or invalid value types (in addition
145 // to there not being any values in the dictionary). If create_entries is set to
146 // |CREATE_DICTIONARY_ENTRIES|, if no dictionary is found or the decisions are
147 // expired, a new dictionary will be created.
148 base::DictionaryValue* ChromeSSLHostStateDelegate::GetValidCertDecisionsDict(
149 base::DictionaryValue* dict,
150 CreateDictionaryEntriesDisposition create_entries,
151 bool* expired_previous_decision) {
152 // This needs to be done first in case the method is short circuited by an
153 // early failure.
154 *expired_previous_decision = false;
156 // Extract the version of the certificate decision structure from the content
157 // setting.
158 int version;
159 bool success = dict->GetInteger(kSSLCertDecisionVersionKey, &version);
160 if (!success) {
161 if (create_entries == DO_NOT_CREATE_DICTIONARY_ENTRIES)
162 return NULL;
164 dict->SetInteger(kSSLCertDecisionVersionKey,
165 kDefaultSSLCertDecisionVersion);
166 version = kDefaultSSLCertDecisionVersion;
169 // If the version is somehow a newer version than Chrome can handle, there's
170 // really nothing to do other than fail silently and pretend it doesn't exist
171 // (or is malformed).
172 if (version > kDefaultSSLCertDecisionVersion) {
173 LOG(ERROR) << "Failed to parse a certificate error exception that is in a "
174 << "newer version format (" << version << ") than is supported ("
175 << kDefaultSSLCertDecisionVersion << ")";
176 return NULL;
179 // Extract the certificate decision's expiration time from the content
180 // setting. If there is no expiration time, that means it should never expire
181 // and it should reset only at session restart, so skip all of the expiration
182 // checks.
183 bool expired = false;
184 base::Time now = clock_->Now();
185 base::Time decision_expiration;
186 if (dict->HasKey(kSSLCertDecisionExpirationTimeKey)) {
187 std::string decision_expiration_string;
188 int64 decision_expiration_int64;
189 success = dict->GetString(kSSLCertDecisionExpirationTimeKey,
190 &decision_expiration_string);
191 if (!base::StringToInt64(base::StringPiece(decision_expiration_string),
192 &decision_expiration_int64)) {
193 LOG(ERROR) << "Failed to parse a certificate error exception that has a "
194 << "bad value for an expiration time: "
195 << decision_expiration_string;
196 return NULL;
198 decision_expiration =
199 base::Time::FromInternalValue(decision_expiration_int64);
202 // Check to see if the user's certificate decision has expired.
203 // - Expired and |create_entries| is DO_NOT_CREATE_DICTIONARY_ENTRIES, return
204 // NULL.
205 // - Expired and |create_entries| is CREATE_DICTIONARY_ENTRIES, update the
206 // expiration time.
207 if (should_remember_ssl_decisions_ !=
208 FORGET_SSL_EXCEPTION_DECISIONS_AT_SESSION_END &&
209 decision_expiration.ToInternalValue() <= now.ToInternalValue()) {
210 *expired_previous_decision = true;
212 if (create_entries == DO_NOT_CREATE_DICTIONARY_ENTRIES)
213 return NULL;
215 expired = true;
216 base::Time expiration_time =
217 now + default_ssl_cert_decision_expiration_delta_;
218 // Unfortunately, JSON (and thus content settings) doesn't support int64
219 // values, only doubles. Since this mildly depends on precision, it is
220 // better to store the value as a string.
221 dict->SetString(kSSLCertDecisionExpirationTimeKey,
222 base::Int64ToString(expiration_time.ToInternalValue()));
223 } else if (should_remember_ssl_decisions_ ==
224 FORGET_SSL_EXCEPTION_DECISIONS_AT_SESSION_END) {
225 if (dict->HasKey(kSSLCertDecisionGUIDKey)) {
226 std::string old_expiration_guid;
227 success = dict->GetString(kSSLCertDecisionGUIDKey, &old_expiration_guid);
228 if (old_expiration_guid.compare(current_expiration_guid_) != 0) {
229 *expired_previous_decision = true;
230 expired = true;
235 dict->SetString(kSSLCertDecisionGUIDKey, current_expiration_guid_);
237 // Extract the map of certificate fingerprints to errors from the setting.
238 base::DictionaryValue* cert_error_dict = NULL; // Will be owned by dict
239 if (expired ||
240 !dict->GetDictionary(kSSLCertDecisionCertErrorMapKey, &cert_error_dict)) {
241 if (create_entries == DO_NOT_CREATE_DICTIONARY_ENTRIES)
242 return NULL;
244 cert_error_dict = new base::DictionaryValue();
245 // dict takes ownership of cert_error_dict
246 dict->Set(kSSLCertDecisionCertErrorMapKey, cert_error_dict);
249 return cert_error_dict;
252 // If |should_remember_ssl_decisions_| is
253 // FORGET_SSL_EXCEPTION_DECISIONS_AT_SESSION_END, that means that all invalid
254 // certificate proceed decisions should be forgotten when the session ends. To
255 // simulate that, Chrome keeps track of a guid to represent the current browser
256 // session and stores it in decision entries. See the comment for
257 // |current_expiration_guid_| for more information.
258 ChromeSSLHostStateDelegate::ChromeSSLHostStateDelegate(Profile* profile)
259 : clock_(new base::DefaultClock()),
260 profile_(profile),
261 current_expiration_guid_(base::GenerateGUID()) {
262 int64 expiration_delta = GetExpirationDelta();
263 if (expiration_delta == kForgetAtSessionEndSwitchValue) {
264 should_remember_ssl_decisions_ =
265 FORGET_SSL_EXCEPTION_DECISIONS_AT_SESSION_END;
266 expiration_delta = 0;
267 } else {
268 should_remember_ssl_decisions_ = REMEMBER_SSL_EXCEPTION_DECISIONS_FOR_DELTA;
270 default_ssl_cert_decision_expiration_delta_ =
271 base::TimeDelta::FromSeconds(expiration_delta);
274 ChromeSSLHostStateDelegate::~ChromeSSLHostStateDelegate() {
277 void ChromeSSLHostStateDelegate::AllowCert(const std::string& host,
278 const net::X509Certificate& cert,
279 net::CertStatus error) {
280 GURL url = GetSecureGURLForHost(host);
281 const ContentSettingsPattern pattern =
282 ContentSettingsPattern::FromURLNoWildcard(url);
283 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap();
284 scoped_ptr<base::Value> value(map->GetWebsiteSetting(
285 url, url, CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, std::string(), NULL));
287 if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY))
288 value.reset(new base::DictionaryValue());
290 base::DictionaryValue* dict;
291 bool success = value->GetAsDictionary(&dict);
292 DCHECK(success);
294 bool expired_previous_decision; // unused value in this function
295 base::DictionaryValue* cert_dict = GetValidCertDecisionsDict(
296 dict, CREATE_DICTIONARY_ENTRIES, &expired_previous_decision);
297 // If a a valid certificate dictionary cannot be extracted from the content
298 // setting, that means it's in an unknown format. Unfortunately, there's
299 // nothing to be done in that case, so a silent fail is the only option.
300 if (!cert_dict)
301 return;
303 dict->SetIntegerWithoutPathExpansion(kSSLCertDecisionVersionKey,
304 kDefaultSSLCertDecisionVersion);
305 cert_dict->SetIntegerWithoutPathExpansion(GetKey(cert, error), ALLOWED);
307 // The map takes ownership of the value, so it is released in the call to
308 // SetWebsiteSetting.
309 map->SetWebsiteSetting(pattern,
310 pattern,
311 CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS,
312 std::string(),
313 value.release());
316 void ChromeSSLHostStateDelegate::Clear() {
317 profile_->GetHostContentSettingsMap()->ClearSettingsForOneType(
318 CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS);
321 content::SSLHostStateDelegate::CertJudgment
322 ChromeSSLHostStateDelegate::QueryPolicy(const std::string& host,
323 const net::X509Certificate& cert,
324 net::CertStatus error,
325 bool* expired_previous_decision) {
326 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap();
327 GURL url = GetSecureGURLForHost(host);
328 scoped_ptr<base::Value> value(map->GetWebsiteSetting(
329 url, url, CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, std::string(), NULL));
331 // Set a default value in case this method is short circuited and doesn't do a
332 // full query.
333 *expired_previous_decision = false;
335 // If the appropriate flag is set, let requests on localhost go
336 // through even if there are certificate errors. Errors on localhost
337 // are unlikely to indicate actual security problems.
338 bool allow_localhost = base::CommandLine::ForCurrentProcess()->HasSwitch(
339 switches::kAllowInsecureLocalhost);
340 if (allow_localhost && net::IsLocalhost(url.host()))
341 return ALLOWED;
343 if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY))
344 return DENIED;
346 base::DictionaryValue* dict; // Owned by value
347 int policy_decision;
348 bool success = value->GetAsDictionary(&dict);
349 DCHECK(success);
351 base::DictionaryValue* cert_error_dict; // Owned by value
352 cert_error_dict = GetValidCertDecisionsDict(
353 dict, DO_NOT_CREATE_DICTIONARY_ENTRIES, expired_previous_decision);
354 if (!cert_error_dict) {
355 // This revoke is necessary to clear any old expired setting that may be
356 // lingering in the case that an old decision expried.
357 RevokeUserAllowExceptions(host);
358 return DENIED;
361 success = cert_error_dict->GetIntegerWithoutPathExpansion(GetKey(cert, error),
362 &policy_decision);
364 // If a policy decision was successfully retrieved and it's a valid value of
365 // ALLOWED, return the valid value. Otherwise, return DENIED.
366 if (success && policy_decision == ALLOWED)
367 return ALLOWED;
369 return DENIED;
372 void ChromeSSLHostStateDelegate::RevokeUserAllowExceptions(
373 const std::string& host) {
374 GURL url = GetSecureGURLForHost(host);
375 const ContentSettingsPattern pattern =
376 ContentSettingsPattern::FromURLNoWildcard(url);
377 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap();
379 map->SetWebsiteSetting(pattern,
380 pattern,
381 CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS,
382 std::string(),
383 NULL);
386 // TODO(jww): This will revoke all of the decisions in the browser context.
387 // However, the networking stack actually keeps track of its own list of
388 // exceptions per-HttpNetworkTransaction in the SSLConfig structure (see the
389 // allowed_bad_certs Vector in net/ssl/ssl_config.h). This dual-tracking of
390 // exceptions introduces a problem where the browser context can revoke a
391 // certificate, but if a transaction reuses a cached version of the SSLConfig
392 // (probably from a pooled socket), it may bypass the intestitial layer.
394 // Over time, the cached versions should expire and it should converge on
395 // showing the interstitial. We probably need to introduce into the networking
396 // stack a way revoke SSLConfig's allowed_bad_certs lists per socket.
398 // For now, RevokeUserAllowExceptionsHard is our solution for the rare case
399 // where it is necessary to revoke the preferences immediately. It does so by
400 // flushing idle sockets, thus it is a big hammer and should be wielded with
401 // extreme caution as it can have a big, negative impact on network performance.
402 void ChromeSSLHostStateDelegate::RevokeUserAllowExceptionsHard(
403 const std::string& host) {
404 RevokeUserAllowExceptions(host);
405 scoped_refptr<net::URLRequestContextGetter> getter(
406 profile_->GetRequestContext());
407 getter->GetNetworkTaskRunner()->PostTask(
408 FROM_HERE, base::Bind(&CloseIdleConnections, getter));
411 bool ChromeSSLHostStateDelegate::HasAllowException(
412 const std::string& host) const {
413 GURL url = GetSecureGURLForHost(host);
414 const ContentSettingsPattern pattern =
415 ContentSettingsPattern::FromURLNoWildcard(url);
416 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap();
418 scoped_ptr<base::Value> value(map->GetWebsiteSetting(
419 url, url, CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, std::string(), NULL));
421 if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY))
422 return false;
424 base::DictionaryValue* dict; // Owned by value
425 bool success = value->GetAsDictionary(&dict);
426 DCHECK(success);
428 for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) {
429 int policy_decision; // Owned by dict
430 success = it.value().GetAsInteger(&policy_decision);
431 if (success && (static_cast<CertJudgment>(policy_decision) == ALLOWED))
432 return true;
435 return false;
438 void ChromeSSLHostStateDelegate::HostRanInsecureContent(const std::string& host,
439 int pid) {
440 ran_insecure_content_hosts_.insert(BrokenHostEntry(host, pid));
443 bool ChromeSSLHostStateDelegate::DidHostRunInsecureContent(
444 const std::string& host,
445 int pid) const {
446 return !!ran_insecure_content_hosts_.count(BrokenHostEntry(host, pid));
448 void ChromeSSLHostStateDelegate::SetClock(scoped_ptr<base::Clock> clock) {
449 clock_.reset(clock.release());