Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / chrome / browser / ssl / chrome_ssl_host_state_delegate.cc
blobec225d3f6043d996481514a631980ae183dbfd78
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 // The default expiration is one week, unless overidden by a field trial group.
36 // See https://crbug.com/487270.
37 const uint64_t kDeltaDefaultExpirationInSeconds = UINT64_C(604800);
39 // Field trial information
40 const char kRevertCertificateErrorDecisionsFieldTrialName[] =
41 "RevertCertificateErrorDecisions";
42 const char kForgetAtSessionEndGroup[] = "Session";
44 // Keys for the per-site error + certificate finger to judgment content
45 // settings map.
46 const char kSSLCertDecisionCertErrorMapKey[] = "cert_exceptions_map";
47 const char kSSLCertDecisionExpirationTimeKey[] = "decision_expiration_time";
48 const char kSSLCertDecisionVersionKey[] = "version";
49 const char kSSLCertDecisionGUIDKey[] = "guid";
51 const int kDefaultSSLCertDecisionVersion = 1;
53 void CloseIdleConnections(
54 scoped_refptr<net::URLRequestContextGetter> url_request_context_getter) {
55 url_request_context_getter->
56 GetURLRequestContext()->
57 http_transaction_factory()->
58 GetSession()->
59 CloseIdleConnections();
62 // All SSL decisions are per host (and are shared arcoss schemes), so this
63 // canonicalizes all hosts into a secure scheme GURL to use with content
64 // settings. The returned GURL will be the passed in host with an empty path and
65 // https:// as the scheme.
66 GURL GetSecureGURLForHost(const std::string& host) {
67 std::string url = "https://" + host;
68 return GURL(url);
71 // By default, certificate exception decisions are remembered for one week.
72 // However, there is a field trial group for the "old" style of certificate
73 // decision memory that expires decisions at session end. ExpireAtSessionEnd()
74 // returns |true| if and only if the user is in that field trial group.
75 bool ExpireAtSessionEnd() {
76 std::string group_name = base::FieldTrialList::FindFullName(
77 kRevertCertificateErrorDecisionsFieldTrialName);
78 return !group_name.empty() &&
79 group_name.compare(kForgetAtSessionEndGroup) == 0;
82 std::string GetKey(const net::X509Certificate& cert, net::CertStatus error) {
83 // Since a security decision will be made based on the fingerprint, Chrome
84 // should use the SHA-256 fingerprint for the certificate.
85 net::SHA256HashValue fingerprint =
86 net::X509Certificate::CalculateChainFingerprint256(
87 cert.os_cert_handle(), cert.GetIntermediateCertificates());
88 std::string base64_fingerprint;
89 base::Base64Encode(
90 base::StringPiece(reinterpret_cast<const char*>(fingerprint.data),
91 sizeof(fingerprint.data)),
92 &base64_fingerprint);
93 return base::UintToString(error) + base64_fingerprint;
96 } // namespace
98 // This helper function gets the dictionary of certificate fingerprints to
99 // errors of certificates that have been accepted by the user from the content
100 // dictionary that has been passed in. The returned pointer is owned by the the
101 // argument dict that is passed in.
103 // If create_entries is set to |DO_NOT_CREATE_DICTIONARY_ENTRIES|,
104 // GetValidCertDecisionsDict will return NULL if there is anything invalid about
105 // the setting, such as an invalid version or invalid value types (in addition
106 // to there not being any values in the dictionary). If create_entries is set to
107 // |CREATE_DICTIONARY_ENTRIES|, if no dictionary is found or the decisions are
108 // expired, a new dictionary will be created.
109 base::DictionaryValue* ChromeSSLHostStateDelegate::GetValidCertDecisionsDict(
110 base::DictionaryValue* dict,
111 CreateDictionaryEntriesDisposition create_entries,
112 bool* expired_previous_decision) {
113 // This needs to be done first in case the method is short circuited by an
114 // early failure.
115 *expired_previous_decision = false;
117 // Extract the version of the certificate decision structure from the content
118 // setting.
119 int version;
120 bool success = dict->GetInteger(kSSLCertDecisionVersionKey, &version);
121 if (!success) {
122 if (create_entries == DO_NOT_CREATE_DICTIONARY_ENTRIES)
123 return NULL;
125 dict->SetInteger(kSSLCertDecisionVersionKey,
126 kDefaultSSLCertDecisionVersion);
127 version = kDefaultSSLCertDecisionVersion;
130 // If the version is somehow a newer version than Chrome can handle, there's
131 // really nothing to do other than fail silently and pretend it doesn't exist
132 // (or is malformed).
133 if (version > kDefaultSSLCertDecisionVersion) {
134 LOG(ERROR) << "Failed to parse a certificate error exception that is in a "
135 << "newer version format (" << version << ") than is supported ("
136 << kDefaultSSLCertDecisionVersion << ")";
137 return NULL;
140 // Extract the certificate decision's expiration time from the content
141 // setting. If there is no expiration time, that means it should never expire
142 // and it should reset only at session restart, so skip all of the expiration
143 // checks.
144 bool expired = false;
145 base::Time now = clock_->Now();
146 base::Time decision_expiration;
147 if (dict->HasKey(kSSLCertDecisionExpirationTimeKey)) {
148 std::string decision_expiration_string;
149 int64 decision_expiration_int64;
150 success = dict->GetString(kSSLCertDecisionExpirationTimeKey,
151 &decision_expiration_string);
152 if (!base::StringToInt64(base::StringPiece(decision_expiration_string),
153 &decision_expiration_int64)) {
154 LOG(ERROR) << "Failed to parse a certificate error exception that has a "
155 << "bad value for an expiration time: "
156 << decision_expiration_string;
157 return NULL;
159 decision_expiration =
160 base::Time::FromInternalValue(decision_expiration_int64);
163 // Check to see if the user's certificate decision has expired.
164 // - Expired and |create_entries| is DO_NOT_CREATE_DICTIONARY_ENTRIES, return
165 // NULL.
166 // - Expired and |create_entries| is CREATE_DICTIONARY_ENTRIES, update the
167 // expiration time.
168 if (should_remember_ssl_decisions_ !=
169 FORGET_SSL_EXCEPTION_DECISIONS_AT_SESSION_END &&
170 decision_expiration.ToInternalValue() <= now.ToInternalValue()) {
171 *expired_previous_decision = true;
173 if (create_entries == DO_NOT_CREATE_DICTIONARY_ENTRIES)
174 return NULL;
176 expired = true;
177 base::Time expiration_time =
178 now + base::TimeDelta::FromSeconds(kDeltaDefaultExpirationInSeconds);
179 // Unfortunately, JSON (and thus content settings) doesn't support int64
180 // values, only doubles. Since this mildly depends on precision, it is
181 // better to store the value as a string.
182 dict->SetString(kSSLCertDecisionExpirationTimeKey,
183 base::Int64ToString(expiration_time.ToInternalValue()));
184 } else if (should_remember_ssl_decisions_ ==
185 FORGET_SSL_EXCEPTION_DECISIONS_AT_SESSION_END) {
186 if (dict->HasKey(kSSLCertDecisionGUIDKey)) {
187 std::string old_expiration_guid;
188 success = dict->GetString(kSSLCertDecisionGUIDKey, &old_expiration_guid);
189 if (old_expiration_guid.compare(current_expiration_guid_) != 0) {
190 *expired_previous_decision = true;
191 expired = true;
196 dict->SetString(kSSLCertDecisionGUIDKey, current_expiration_guid_);
198 // Extract the map of certificate fingerprints to errors from the setting.
199 base::DictionaryValue* cert_error_dict = NULL; // Will be owned by dict
200 if (expired ||
201 !dict->GetDictionary(kSSLCertDecisionCertErrorMapKey, &cert_error_dict)) {
202 if (create_entries == DO_NOT_CREATE_DICTIONARY_ENTRIES)
203 return NULL;
205 cert_error_dict = new base::DictionaryValue();
206 // dict takes ownership of cert_error_dict
207 dict->Set(kSSLCertDecisionCertErrorMapKey, cert_error_dict);
210 return cert_error_dict;
213 // If |should_remember_ssl_decisions_| is
214 // FORGET_SSL_EXCEPTION_DECISIONS_AT_SESSION_END, that means that all invalid
215 // certificate proceed decisions should be forgotten when the session ends. To
216 // simulate that, Chrome keeps track of a guid to represent the current browser
217 // session and stores it in decision entries. See the comment for
218 // |current_expiration_guid_| for more information.
219 ChromeSSLHostStateDelegate::ChromeSSLHostStateDelegate(Profile* profile)
220 : clock_(new base::DefaultClock()),
221 profile_(profile),
222 current_expiration_guid_(base::GenerateGUID()) {
223 if (ExpireAtSessionEnd())
224 should_remember_ssl_decisions_ =
225 FORGET_SSL_EXCEPTION_DECISIONS_AT_SESSION_END;
226 else
227 should_remember_ssl_decisions_ = REMEMBER_SSL_EXCEPTION_DECISIONS_FOR_DELTA;
230 ChromeSSLHostStateDelegate::~ChromeSSLHostStateDelegate() {
233 void ChromeSSLHostStateDelegate::AllowCert(const std::string& host,
234 const net::X509Certificate& cert,
235 net::CertStatus error) {
236 GURL url = GetSecureGURLForHost(host);
237 const ContentSettingsPattern pattern =
238 ContentSettingsPattern::FromURLNoWildcard(url);
239 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap();
240 scoped_ptr<base::Value> value(map->GetWebsiteSetting(
241 url, url, CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, std::string(), NULL));
243 if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY))
244 value.reset(new base::DictionaryValue());
246 base::DictionaryValue* dict;
247 bool success = value->GetAsDictionary(&dict);
248 DCHECK(success);
250 bool expired_previous_decision; // unused value in this function
251 base::DictionaryValue* cert_dict = GetValidCertDecisionsDict(
252 dict, CREATE_DICTIONARY_ENTRIES, &expired_previous_decision);
253 // If a a valid certificate dictionary cannot be extracted from the content
254 // setting, that means it's in an unknown format. Unfortunately, there's
255 // nothing to be done in that case, so a silent fail is the only option.
256 if (!cert_dict)
257 return;
259 dict->SetIntegerWithoutPathExpansion(kSSLCertDecisionVersionKey,
260 kDefaultSSLCertDecisionVersion);
261 cert_dict->SetIntegerWithoutPathExpansion(GetKey(cert, error), ALLOWED);
263 // The map takes ownership of the value, so it is released in the call to
264 // SetWebsiteSetting.
265 map->SetWebsiteSetting(pattern,
266 pattern,
267 CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS,
268 std::string(),
269 value.release());
272 void ChromeSSLHostStateDelegate::Clear() {
273 profile_->GetHostContentSettingsMap()->ClearSettingsForOneType(
274 CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS);
277 content::SSLHostStateDelegate::CertJudgment
278 ChromeSSLHostStateDelegate::QueryPolicy(const std::string& host,
279 const net::X509Certificate& cert,
280 net::CertStatus error,
281 bool* expired_previous_decision) {
282 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap();
283 GURL url = GetSecureGURLForHost(host);
284 scoped_ptr<base::Value> value(map->GetWebsiteSetting(
285 url, url, CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, std::string(), NULL));
287 // Set a default value in case this method is short circuited and doesn't do a
288 // full query.
289 *expired_previous_decision = false;
291 // If the appropriate flag is set, let requests on localhost go
292 // through even if there are certificate errors. Errors on localhost
293 // are unlikely to indicate actual security problems.
294 bool allow_localhost = base::CommandLine::ForCurrentProcess()->HasSwitch(
295 switches::kAllowInsecureLocalhost);
296 if (allow_localhost && net::IsLocalhost(url.host()))
297 return ALLOWED;
299 if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY))
300 return DENIED;
302 base::DictionaryValue* dict; // Owned by value
303 int policy_decision;
304 bool success = value->GetAsDictionary(&dict);
305 DCHECK(success);
307 base::DictionaryValue* cert_error_dict; // Owned by value
308 cert_error_dict = GetValidCertDecisionsDict(
309 dict, DO_NOT_CREATE_DICTIONARY_ENTRIES, expired_previous_decision);
310 if (!cert_error_dict) {
311 // This revoke is necessary to clear any old expired setting that may be
312 // lingering in the case that an old decision expried.
313 RevokeUserAllowExceptions(host);
314 return DENIED;
317 success = cert_error_dict->GetIntegerWithoutPathExpansion(GetKey(cert, error),
318 &policy_decision);
320 // If a policy decision was successfully retrieved and it's a valid value of
321 // ALLOWED, return the valid value. Otherwise, return DENIED.
322 if (success && policy_decision == ALLOWED)
323 return ALLOWED;
325 return DENIED;
328 void ChromeSSLHostStateDelegate::RevokeUserAllowExceptions(
329 const std::string& host) {
330 GURL url = GetSecureGURLForHost(host);
331 const ContentSettingsPattern pattern =
332 ContentSettingsPattern::FromURLNoWildcard(url);
333 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap();
335 map->SetWebsiteSetting(pattern,
336 pattern,
337 CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS,
338 std::string(),
339 NULL);
342 // TODO(jww): This will revoke all of the decisions in the browser context.
343 // However, the networking stack actually keeps track of its own list of
344 // exceptions per-HttpNetworkTransaction in the SSLConfig structure (see the
345 // allowed_bad_certs Vector in net/ssl/ssl_config.h). This dual-tracking of
346 // exceptions introduces a problem where the browser context can revoke a
347 // certificate, but if a transaction reuses a cached version of the SSLConfig
348 // (probably from a pooled socket), it may bypass the intestitial layer.
350 // Over time, the cached versions should expire and it should converge on
351 // showing the interstitial. We probably need to introduce into the networking
352 // stack a way revoke SSLConfig's allowed_bad_certs lists per socket.
354 // For now, RevokeUserAllowExceptionsHard is our solution for the rare case
355 // where it is necessary to revoke the preferences immediately. It does so by
356 // flushing idle sockets, thus it is a big hammer and should be wielded with
357 // extreme caution as it can have a big, negative impact on network performance.
358 void ChromeSSLHostStateDelegate::RevokeUserAllowExceptionsHard(
359 const std::string& host) {
360 RevokeUserAllowExceptions(host);
361 scoped_refptr<net::URLRequestContextGetter> getter(
362 profile_->GetRequestContext());
363 getter->GetNetworkTaskRunner()->PostTask(
364 FROM_HERE, base::Bind(&CloseIdleConnections, getter));
367 bool ChromeSSLHostStateDelegate::HasAllowException(
368 const std::string& host) const {
369 GURL url = GetSecureGURLForHost(host);
370 const ContentSettingsPattern pattern =
371 ContentSettingsPattern::FromURLNoWildcard(url);
372 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap();
374 scoped_ptr<base::Value> value(map->GetWebsiteSetting(
375 url, url, CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, std::string(), NULL));
377 if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY))
378 return false;
380 base::DictionaryValue* dict; // Owned by value
381 bool success = value->GetAsDictionary(&dict);
382 DCHECK(success);
384 for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) {
385 int policy_decision; // Owned by dict
386 success = it.value().GetAsInteger(&policy_decision);
387 if (success && (static_cast<CertJudgment>(policy_decision) == ALLOWED))
388 return true;
391 return false;
394 void ChromeSSLHostStateDelegate::HostRanInsecureContent(const std::string& host,
395 int pid) {
396 ran_insecure_content_hosts_.insert(BrokenHostEntry(host, pid));
399 bool ChromeSSLHostStateDelegate::DidHostRunInsecureContent(
400 const std::string& host,
401 int pid) const {
402 return !!ran_insecure_content_hosts_.count(BrokenHostEntry(host, pid));
404 void ChromeSSLHostStateDelegate::SetClock(scoped_ptr<base::Clock> clock) {
405 clock_.reset(clock.release());