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