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