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