OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 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 |
| 24 namespace { |
| 25 |
| 26 // Switch value that specifies that certificate decisions should be forgotten at |
| 27 // the end of the current session. |
| 28 const int64 kForgetAtSessionEndSwitchValue = -1; |
| 29 |
| 30 // Experiment information |
| 31 const char kRememberCertificateErrorDecisionsFieldTrialName[] = |
| 32 "RememberCertificateErrorDecisions"; |
| 33 const char kRememberCertificateErrorDecisionsFieldTrialDefaultGroup[] = |
| 34 "Default"; |
| 35 const char kRememberCertificateErrorDecisionsFieldTrialLengthParam[] = "length"; |
| 36 |
| 37 // Keys for the per-site error + certificate finger to judgement content |
| 38 // settings map. |
| 39 const char kSSLCertDecisionCertErrorMapKey[] = "cert_exceptions_map"; |
| 40 const char kSSLCertDecisionExpirationTimeKey[] = "decision_expiration_time"; |
| 41 const char kSSLCertDecisionVersionKey[] = "version"; |
| 42 |
| 43 const 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. |
| 49 GURL 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. |
| 61 int64 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 |
| 102 std::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 |
| 129 base::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. |
| 221 ChromeSSLHostStateDelegate::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 |
| 235 ChromeSSLHostStateDelegate::~ChromeSSLHostStateDelegate() { |
| 236 if (should_remember_ssl_decisions_ == ForgetSSLExceptionDecisionsAtSessionEnd) |
| 237 Clear(); |
| 238 } |
| 239 |
| 240 void ChromeSSLHostStateDelegate::DenyCert(const std::string& host, |
| 241 net::X509Certificate* cert, |
| 242 net::CertStatus error) { |
| 243 ChangeCertPolicy(host, cert, error, net::CertPolicy::DENIED); |
| 244 } |
| 245 |
| 246 void ChromeSSLHostStateDelegate::AllowCert(const std::string& host, |
| 247 net::X509Certificate* cert, |
| 248 net::CertStatus error) { |
| 249 ChangeCertPolicy(host, cert, error, net::CertPolicy::ALLOWED); |
| 250 } |
| 251 |
| 252 void ChromeSSLHostStateDelegate::Clear() { |
| 253 profile_->GetHostContentSettingsMap()->ClearSettingsForOneType( |
| 254 CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS); |
| 255 } |
| 256 |
| 257 net::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 |
| 293 void 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 |
| 307 bool 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 |
| 335 void ChromeSSLHostStateDelegate::SetClock(scoped_ptr<base::Clock> clock) { |
| 336 clock_.reset(clock.release()); |
| 337 } |
| 338 |
| 339 void 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 } |
OLD | NEW |