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