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