Chromium Code Reviews| 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_decisions.h" | |
| 6 | |
| 7 #include "base/base64.h" | |
| 8 #include "base/command_line.h" | |
| 9 #include "base/logging.h" | |
| 10 #include "base/strings/string_number_conversions.h" | |
| 11 #include "base/time/default_clock.h" | |
| 12 #include "base/time/time.h" | |
| 13 #include "chrome/browser/content_settings/host_content_settings_map.h" | |
| 14 #include "chrome/browser/profiles/profile.h" | |
| 15 #include "chrome/common/chrome_switches.h" | |
| 16 #include "chrome/common/content_settings_types.h" | |
| 17 #include "url/gurl.h" | |
| 18 | |
| 19 namespace { | |
| 20 | |
| 21 // This is a helper function that returns the length of time before a | |
| 22 // certificate decision expires based on the command line flags. Returns a value | |
| 23 // in seconds, where a value of 0 specifically indicates that decisions should | |
| 24 // not be remembered after the current session has ended (but should be | |
| 25 // remembered indefinitely as long as the session does not end), which is the | |
| 26 // "old" style of certificate decision memory. | |
|
felt
2014/07/07 22:26:39
Might you want to use -1 instead of 0 for the "def
jww
2014/07/08 17:35:41
I'm not sure I understand. The number is in second
felt
2014/07/08 17:52:49
I agree, 0 seconds means "no memory at all." But h
jww
2014/07/08 23:49:01
Fair enough. I've changed this to your suggestion.
| |
| 27 int64 GetExpirationDelta() { | |
| 28 if (!CommandLine::ForCurrentProcess()->HasSwitch( | |
| 29 switches::kRememberCertErrorDecisions)) | |
| 30 return 0; | |
| 31 | |
| 32 std::string switch_value = | |
| 33 CommandLine::ForCurrentProcess()->GetSwitchValueASCII( | |
| 34 switches::kRememberCertErrorDecisions); | |
| 35 // If the expiration is set to 0, that means cert proceed decisions should | |
| 36 // never expire, until a restart of the profile. | |
| 37 int64 expiration_delta; | |
| 38 if (!base::StringToInt64(switch_value, &expiration_delta)) { | |
|
felt
2014/07/07 22:26:39
should this be a DCHECK?
jww
2014/07/08 17:35:41
I don't think so. A user can manually specify a co
| |
| 39 LOG(ERROR) << "Failed to parse the certificate error decision " | |
| 40 << "memory length: " << switch_value; | |
| 41 } | |
| 42 | |
| 43 return expiration_delta; | |
| 44 } | |
| 45 | |
| 46 const char kSSLCertDecisionCertErrorMapKey[] = "cert_exceptions_map"; | |
| 47 const char kSSLCertDecisionExpirationTimeKey[] = "decision_expiration_time"; | |
| 48 const char kSSLCertDecisionVersionKey[] = "version"; | |
| 49 const int kDefaultSSLCertDecisionVersion = 1; | |
| 50 | |
| 51 std::string GetKey(net::X509Certificate* cert, net::CertStatus error) { | |
| 52 net::SHA1HashValue fingerprint = cert->fingerprint(); | |
| 53 std::string base64_fingerprint; | |
| 54 base::Base64Encode( | |
| 55 base::StringPiece(reinterpret_cast<const char*>(fingerprint.data), | |
| 56 sizeof(fingerprint.data)), | |
| 57 &base64_fingerprint); | |
| 58 return base::UintToString(error) + base64_fingerprint; | |
| 59 } | |
| 60 | |
| 61 } // namespace | |
| 62 | |
| 63 // This helper function gets the dictionary of certificate fingerprints to | |
| 64 // errors of certificates that have been accepted by the user from the content | |
| 65 // dictionary that has been passed in. The returned pointer is owned by the the | |
| 66 // argument dict that is passed in. | |
| 67 // | |
| 68 // If create_entries is set to |false|, GetValidCertDecisionsDict will return | |
| 69 // NULL if there is anything invalid about the setting, such as an invalid | |
| 70 // version or invalid value types (in addition to there not be any values in the | |
| 71 // dictionary). If create_entries is set to |true|, if no dictionary is found or | |
| 72 // the decisions are expired, a new dictionary will be created | |
|
felt
2014/07/07 22:26:39
why do you need to support both cases for |create_
jww
2014/07/08 17:35:41
There are two uses of GetValidCertDecisionsDict. T
| |
| 73 base::DictionaryValue* ChromeSSLHostStateDecisions::GetValidCertDecisionsDict( | |
| 74 base::DictionaryValue* dict, | |
| 75 bool create_entries) { | |
| 76 // Extract the version of the certificate decision structure from the content | |
| 77 // setting. | |
| 78 int version; | |
| 79 bool success = dict->GetInteger(kSSLCertDecisionVersionKey, &version); | |
| 80 if (!success) { | |
| 81 if (!create_entries) | |
| 82 return NULL; | |
| 83 | |
| 84 dict->SetInteger(kSSLCertDecisionVersionKey, | |
| 85 kDefaultSSLCertDecisionVersion); | |
| 86 version = kDefaultSSLCertDecisionVersion; | |
| 87 } | |
| 88 | |
| 89 // If the version is somehow a newer version than Chrome can handle, there's | |
| 90 // really nothing to do other than fail silently and pretend it doesn't exist | |
| 91 // (or is malformed). | |
| 92 if (version > kDefaultSSLCertDecisionVersion) | |
| 93 return NULL; | |
| 94 | |
| 95 // Extract the certificate decision's expiration time from the content | |
| 96 // setting. If there is no expiration time, that means it should never expire | |
| 97 // and it should resest only at session restart, so skip all of the expiration | |
| 98 // checks. | |
| 99 bool expired = false; | |
| 100 double decision_expiration_dbl; | |
| 101 base::Time now = clock_->Now(); | |
| 102 base::Time decision_expiration; | |
| 103 if (dict->HasKey(kSSLCertDecisionExpirationTimeKey)) { | |
| 104 success = dict->GetDouble(kSSLCertDecisionExpirationTimeKey, | |
| 105 &decision_expiration_dbl); | |
| 106 decision_expiration = base::Time::FromDoubleT(decision_expiration_dbl); | |
| 107 } | |
| 108 | |
| 109 // Check if the user certificate decision has expired. If it has expired, and | |
| 110 // create_entries is |false|, treat the entry as if it doesn't exist and | |
| 111 // return |NULL|. If it has expired, and create_entries is |true|, set the | |
| 112 // entry's expiration date to the current time plus the expiration time delta. | |
| 113 // Also, set expired to |true| which will force the create of a new dictionary | |
| 114 // in the dictionary extraction pass. None of this should be done if | |
| 115 // expiration happens on session end (i.e. the expiration delta is 0). | |
| 116 if (defaultSSLCertDecisionExpirationDelta_.InSeconds() > 0 && | |
| 117 decision_expiration < now) { | |
| 118 if (!create_entries) | |
| 119 return NULL; | |
| 120 | |
| 121 expired = true; | |
| 122 | |
| 123 // If defaultSSLCertDecisionExpirationDelta_ is 0, that means that decisions | |
| 124 // should never expire. Thus, in that case, no key should be set. | |
| 125 if (defaultSSLCertDecisionExpirationDelta_.InSeconds() != 0) { | |
| 126 base::Time expiration_time = now + defaultSSLCertDecisionExpirationDelta_; | |
| 127 dict->SetDouble(kSSLCertDecisionExpirationTimeKey, | |
| 128 expiration_time.ToDoubleT()); | |
| 129 } | |
| 130 } | |
| 131 | |
| 132 // Extract the map of certificate fingerprints to errors from the setting. | |
| 133 base::DictionaryValue* cert_error_dict; // Owned by dict | |
| 134 if (expired || | |
| 135 !dict->GetDictionary(kSSLCertDecisionCertErrorMapKey, &cert_error_dict)) { | |
| 136 if (!create_entries) | |
| 137 return NULL; | |
| 138 | |
| 139 cert_error_dict = new base::DictionaryValue(); | |
| 140 // dict takes ownership of cert_error_dict | |
| 141 dict->Set(kSSLCertDecisionCertErrorMapKey, cert_error_dict); | |
| 142 } | |
| 143 | |
| 144 return cert_error_dict; | |
| 145 } | |
| 146 | |
| 147 // If defaultSSLCertDecisionExpirationDelta_ is 0, that means that all invalid | |
| 148 // certificate proceed decisions should be forgotten when the session ends. At | |
| 149 // attempt is made in the destructor to remove the entries, but in the case that | |
| 150 // things didn't shut down cleanly, on start, Clear is called to guarantee a | |
| 151 // clean state. | |
| 152 ChromeSSLHostStateDecisions::ChromeSSLHostStateDecisions(Profile* profile) | |
| 153 : clock_(new base::DefaultClock()), | |
| 154 defaultSSLCertDecisionExpirationDelta_( | |
| 155 base::TimeDelta::FromSeconds(GetExpirationDelta())), | |
| 156 profile_(profile) { | |
| 157 if (defaultSSLCertDecisionExpirationDelta_.InSeconds() == 0) | |
| 158 Clear(); | |
| 159 } | |
| 160 | |
| 161 ChromeSSLHostStateDecisions::~ChromeSSLHostStateDecisions() { | |
| 162 if (defaultSSLCertDecisionExpirationDelta_.InSeconds() == 0) | |
| 163 Clear(); | |
| 164 } | |
| 165 | |
| 166 void ChromeSSLHostStateDecisions::DenyCert(const GURL& url, | |
| 167 net::X509Certificate* cert, | |
| 168 net::CertStatus error) { | |
| 169 ChangeCertPolicy(url, cert, error, net::CertPolicy::DENIED); | |
| 170 } | |
| 171 | |
| 172 void ChromeSSLHostStateDecisions::AllowCert(const GURL& url, | |
| 173 net::X509Certificate* cert, | |
| 174 net::CertStatus error) { | |
| 175 ChangeCertPolicy(url, cert, error, net::CertPolicy::ALLOWED); | |
| 176 } | |
| 177 | |
| 178 void ChromeSSLHostStateDecisions::Clear() { | |
| 179 profile_->GetHostContentSettingsMap()->ClearSettingsForOneType( | |
| 180 CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS); | |
| 181 } | |
| 182 | |
| 183 net::CertPolicy::Judgment ChromeSSLHostStateDecisions::QueryPolicy( | |
| 184 const GURL& url, | |
| 185 net::X509Certificate* cert, | |
| 186 net::CertStatus error) { | |
| 187 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap(); | |
| 188 scoped_ptr<base::Value> value(map->GetWebsiteSetting( | |
| 189 url, url, CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, "", NULL)); | |
| 190 | |
| 191 DCHECK(value.get()); | |
| 192 | |
| 193 if (!value->IsType(base::Value::TYPE_DICTIONARY)) | |
| 194 return net::CertPolicy::UNKNOWN; | |
| 195 | |
| 196 base::DictionaryValue* dict; // Owned by value | |
| 197 int policy_decision; // Owned by dict | |
| 198 bool success = value->GetAsDictionary(&dict); | |
| 199 DCHECK(success); | |
| 200 | |
| 201 // First, check that the version of the dictionary of decisions is a version | |
| 202 // that is known. Otherwise, fail. | |
| 203 base::DictionaryValue* cert_error_dict; // Owned by value | |
| 204 cert_error_dict = GetValidCertDecisionsDict(dict, false); | |
| 205 if (!cert_error_dict) | |
| 206 return net::CertPolicy::UNKNOWN; | |
| 207 | |
| 208 success = cert_error_dict->GetIntegerWithoutPathExpansion(GetKey(cert, error), | |
| 209 &policy_decision); | |
| 210 | |
| 211 if (!success) | |
| 212 return net::CertPolicy::Judgment::UNKNOWN; | |
| 213 | |
| 214 return static_cast<net::CertPolicy::Judgment>(policy_decision); | |
| 215 } | |
| 216 | |
| 217 void ChromeSSLHostStateDecisions::RevokeAllowAndDenyPreferences( | |
| 218 const GURL& url) { | |
| 219 const ContentSettingsPattern pattern = | |
| 220 ContentSettingsPattern::FromURLNoWildcard(url); | |
| 221 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap(); | |
| 222 | |
| 223 map->SetWebsiteSetting( | |
| 224 pattern, pattern, CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, "", NULL); | |
| 225 } | |
| 226 | |
| 227 bool ChromeSSLHostStateDecisions::HasAllowedOrDeniedCert(const GURL& url) { | |
| 228 const ContentSettingsPattern pattern = | |
| 229 ContentSettingsPattern::FromURLNoWildcard(url); | |
| 230 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap(); | |
| 231 | |
| 232 scoped_ptr<base::Value> value(map->GetWebsiteSetting( | |
| 233 url, url, CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, "", NULL)); | |
| 234 | |
| 235 if (!value->IsType(base::Value::TYPE_DICTIONARY)) | |
| 236 return false; | |
| 237 | |
| 238 base::DictionaryValue* dict; // Owned by value | |
| 239 bool success = value->GetAsDictionary(&dict); | |
| 240 DCHECK(success); | |
| 241 | |
| 242 for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) { | |
| 243 int policy_decision; // Owned by dict | |
| 244 success = it.value().GetAsInteger(&policy_decision); | |
| 245 if (success && (static_cast<net::CertPolicy::Judgment>(policy_decision) != | |
| 246 net::CertPolicy::UNKNOWN)) | |
| 247 return true; | |
| 248 } | |
| 249 | |
| 250 return false; | |
| 251 } | |
| 252 | |
| 253 void ChromeSSLHostStateDecisions::ChangeCertPolicy( | |
| 254 const GURL& url, | |
| 255 net::X509Certificate* cert, | |
| 256 net::CertStatus error, | |
| 257 net::CertPolicy::Judgment judgment) { | |
| 258 const ContentSettingsPattern pattern = | |
| 259 ContentSettingsPattern::FromURLNoWildcard(url); | |
| 260 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap(); | |
| 261 scoped_ptr<base::Value> value(map->GetWebsiteSetting( | |
| 262 url, url, CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, "", NULL)); | |
| 263 | |
| 264 if (!value->IsType(base::Value::TYPE_DICTIONARY)) | |
| 265 value.reset(new base::DictionaryValue()); | |
| 266 | |
| 267 base::DictionaryValue* dict; | |
| 268 bool success = value->GetAsDictionary(&dict); | |
| 269 DCHECK(success); | |
| 270 | |
| 271 base::DictionaryValue* cert_dict = GetValidCertDecisionsDict(dict, true); | |
| 272 // If a a valid certificate dictionary cannot be extracted from the content | |
| 273 // setting, that means it's in an unknown format. Unfortunately, there's | |
| 274 // nothing to be done in that case, so a silent fail is the only option. | |
| 275 if (!cert_dict) | |
| 276 return; | |
| 277 | |
| 278 dict->SetIntegerWithoutPathExpansion(kSSLCertDecisionVersionKey, | |
| 279 kDefaultSSLCertDecisionVersion); | |
| 280 cert_dict->SetIntegerWithoutPathExpansion(GetKey(cert, error), judgment); | |
| 281 | |
| 282 // The map takes ownership of the value, so it is released in the call to | |
| 283 // SetWebsiteSetting. | |
| 284 map->SetWebsiteSetting(pattern, | |
| 285 pattern, | |
| 286 CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, | |
| 287 "", | |
| 288 value.release()); | |
| 289 } | |
| OLD | NEW |