Chromium Code Reviews| Index: chrome/browser/ssl/chrome_ssl_host_state_decisions.cc |
| diff --git a/chrome/browser/ssl/chrome_ssl_host_state_decisions.cc b/chrome/browser/ssl/chrome_ssl_host_state_decisions.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..edab4a10f2f6555b9779d2f38dc12ebbf7de8ffb |
| --- /dev/null |
| +++ b/chrome/browser/ssl/chrome_ssl_host_state_decisions.cc |
| @@ -0,0 +1,325 @@ |
| +// Copyright (c) 2014 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "chrome/browser/ssl/chrome_ssl_host_state_decisions.h" |
| + |
| +#include "base/base64.h" |
| +#include "base/command_line.h" |
| +#include "base/logging.h" |
| +#include "base/strings/string_number_conversions.h" |
| +#include "base/time/default_clock.h" |
| +#include "base/time/time.h" |
| +#include "chrome/browser/content_settings/host_content_settings_map.h" |
| +#include "chrome/browser/profiles/profile.h" |
| +#include "chrome/common/chrome_switches.h" |
| +#include "chrome/common/content_settings_types.h" |
| +#include "url/gurl.h" |
| + |
| +namespace { |
| + |
| +// Time expiration values in seconds for the command line switches for turning |
| +// on certificate error decision time based memory. |
| +const int64 kForgetAtSessionEndSwitchValue = -1; |
| +const int64 kExpirationZeroInSeconds = 0; |
| +const int64 kExpirationOneDayInSeconds = 86400; |
| +const int64 kExpirationThreeDaysInSeconds = 259200; |
| +const int64 kExpirationOneWeekInSeconds = 604800; |
| +const int64 kExpirationOneMonthInSeconds = 2592000; |
| + |
| +// Keys for the per-site error + certificate finger to judgement content |
| +// settings map. |
| +const char kSSLCertDecisionCertErrorMapKey[] = "cert_exceptions_map"; |
| +const char kSSLCertDecisionExpirationTimeKey[] = "decision_expiration_time"; |
| +const char kSSLCertDecisionVersionKey[] = "version"; |
| + |
| +const int kDefaultSSLCertDecisionVersion = 1; |
| + |
| +// This is a helper function that returns the length of time before a |
| +// certificate decision expires based on the command line flags. Returns a |
| +// non-negative value in seconds or a value of -1 indicating that decisions |
| +// should not be remembered after the current session has ended (but should be |
| +// remembered indefinitely as long as the session does not end), which is the |
| +// "old" style of certificate decision memory. |
| +int64 GetExpirationDelta() { |
| + if (CommandLine::ForCurrentProcess()->HasSwitch( |
| + switches::kRememberCertErrorDecisionsDisable)) { |
| + return kForgetAtSessionEndSwitchValue; |
| + } else if (CommandLine::ForCurrentProcess()->HasSwitch( |
| + switches::kRememberCertErrorDecisionsNone)) { |
| + return kExpirationZeroInSeconds; |
| + } else if (CommandLine::ForCurrentProcess()->HasSwitch( |
| + switches::kRememberCertErrorDecisionsOneDay)) { |
| + return kExpirationOneDayInSeconds; |
| + } else if (CommandLine::ForCurrentProcess()->HasSwitch( |
| + switches::kRememberCertErrorDecisionsThreeDays)) { |
| + return kExpirationThreeDaysInSeconds; |
| + } else if (CommandLine::ForCurrentProcess()->HasSwitch( |
| + switches::kRememberCertErrorDecisionsOneWeek)) { |
| + return kExpirationOneWeekInSeconds; |
| + } else if (CommandLine::ForCurrentProcess()->HasSwitch( |
| + switches::kRememberCertErrorDecisionsOneMonth)) { |
|
Ryan Sleevi
2014/07/17 23:19:35
Can we cap at a week? I'm a little nervous for a m
jww
2014/07/21 23:39:32
There's actually a good chance we want to do longe
|
| + return kExpirationOneMonthInSeconds; |
| + } |
| + |
| + return kForgetAtSessionEndSwitchValue; |
| +} |
| + |
| +std::string GetKey(net::X509Certificate* cert, net::CertStatus error) { |
| + net::SHA1HashValue fingerprint = cert->fingerprint(); |
| + std::string base64_fingerprint; |
| + base::Base64Encode( |
| + base::StringPiece(reinterpret_cast<const char*>(fingerprint.data), |
| + sizeof(fingerprint.data)), |
| + &base64_fingerprint); |
| + return base::UintToString(error) + base64_fingerprint; |
| +} |
| + |
| +} // namespace |
| + |
| +// This helper function gets the dictionary of certificate fingerprints to |
| +// errors of certificates that have been accepted by the user from the content |
| +// dictionary that has been passed in. The returned pointer is owned by the the |
| +// argument dict that is passed in. |
| +// |
| +// If create_entries is set to |DoNotCreateDictionaryEntries|, |
| +// GetValidCertDecisionsDict will return NULL if there is anything invalid about |
| +// the setting, such as an invalid version or invalid value types (in addition |
| +// to there not be any values in the dictionary). If create_entries is set to |
| +// |CreateDictionaryEntries|, if no dictionary is found or the decisions are |
| +// expired, a new dictionary will be created |
| +base::DictionaryValue* ChromeSSLHostStateDecisions::GetValidCertDecisionsDict( |
| + base::DictionaryValue* dict, |
| + CreateDictionaryEntriesDisposition create_entries) { |
| + // Extract the version of the certificate decision structure from the content |
| + // setting. |
| + int version; |
| + bool success = dict->GetInteger(kSSLCertDecisionVersionKey, &version); |
| + if (!success) { |
| + if (create_entries == DoNotCreateDictionaryEntries) |
| + return NULL; |
| + |
| + dict->SetInteger(kSSLCertDecisionVersionKey, |
| + kDefaultSSLCertDecisionVersion); |
| + version = kDefaultSSLCertDecisionVersion; |
| + } |
| + |
| + // If the version is somehow a newer version than Chrome can handle, there's |
| + // really nothing to do other than fail silently and pretend it doesn't exist |
| + // (or is malformed). |
| + if (version > kDefaultSSLCertDecisionVersion) { |
| + LOG(ERROR) << "Failed to parse a certificate error exception that is in a " |
| + << "newer version format (" << version << ") than is supported (" |
| + << kDefaultSSLCertDecisionVersion << ")"; |
| + return NULL; |
| + } |
| + |
| + // Extract the certificate decision's expiration time from the content |
| + // setting. If there is no expiration time, that means it should never expire |
| + // and it should resest only at session restart, so skip all of the expiration |
|
Ryan Sleevi
2014/07/17 23:19:35
typo, s/resest/reset
jww
2014/07/21 23:39:32
Done.
|
| + // checks. |
| + bool expired = false; |
| + base::Time now = clock_->Now(); |
| + base::Time decision_expiration; |
| + if (dict->HasKey(kSSLCertDecisionExpirationTimeKey)) { |
| + std::string decision_expiration_string; |
| + int64 decision_expiration_int64; |
| + success = dict->GetString(kSSLCertDecisionExpirationTimeKey, |
| + &decision_expiration_string); |
| + if (!base::StringToInt64(base::StringPiece(decision_expiration_string), |
| + &decision_expiration_int64)) { |
| + LOG(ERROR) << "Failed to parse a certificate error exception that has a " |
| + << "bad value for an expiration time: " |
| + << decision_expiration_string; |
| + return NULL; |
| + } |
| + decision_expiration = |
| + base::Time::FromInternalValue(decision_expiration_int64); |
| + } |
| + |
| + // Check to see if the user's certificate decision has expired. |
| + // - Expired and |create_entries| is DoNotCreateDictionaryEntries, return |
| + // NULL. |
| + // - Expired and |create_entries| is CreateDictionaryEntries, update the |
| + // expiration time. |
| + if (should_remember_ssl_decisions_ != |
| + ForgetSSLExceptionDecisionsAtSessionEnd && |
| + decision_expiration.ToInternalValue() <= now.ToInternalValue()) { |
| + if (create_entries == DoNotCreateDictionaryEntries) |
| + return NULL; |
| + |
| + expired = true; |
| + |
| + base::Time expiration_time = |
| + now + default_ssl_cert_decision_expiration_delta_; |
| + // Unfortunately, JSON (and thus content settings) doesn't support int64 |
| + // values, only doubles. Since this mildly depends on precision, it is |
| + // better to store the value as a string. |
| + dict->SetString(kSSLCertDecisionExpirationTimeKey, |
| + base::Int64ToString(expiration_time.ToInternalValue())); |
| + } |
|
Ryan Sleevi
2014/07/17 23:19:35
What about if the user sets their clock a week ahe
jww
2014/07/21 23:39:32
They will get a longer expiration time. That is, i
|
| + |
| + // Extract the map of certificate fingerprints to errors from the setting. |
| + base::DictionaryValue* cert_error_dict; // Will be owned by dict |
| + if (expired || |
| + !dict->GetDictionary(kSSLCertDecisionCertErrorMapKey, &cert_error_dict)) { |
| + if (create_entries == DoNotCreateDictionaryEntries) |
| + return NULL; |
| + |
| + cert_error_dict = new base::DictionaryValue(); |
| + // dict takes ownership of cert_error_dict |
| + dict->Set(kSSLCertDecisionCertErrorMapKey, cert_error_dict); |
| + } |
| + |
| + return cert_error_dict; |
| +} |
| + |
| +// If should_remember_ssl_decisions_ is ForgetSSLExceptionDecisionsAtSessionEnd, |
|
Ryan Sleevi
2014/07/17 23:19:35
add ||
jww
2014/07/21 23:39:32
Done.
|
| +// that means that all invalid certificate proceed decisions should be forgotten |
| +// when the session ends. At attempt is made in the destructor to remove the |
| +// entries, but in the case that things didn't shut down cleanly, on start, |
| +// Clear is called to guarantee a clean state. |
| +ChromeSSLHostStateDecisions::ChromeSSLHostStateDecisions(Profile* profile) |
| + : clock_(new base::DefaultClock()), profile_(profile) { |
| + int64 expiration_delta = GetExpirationDelta(); |
| + if (expiration_delta == kForgetAtSessionEndSwitchValue) { |
| + should_remember_ssl_decisions_ = ForgetSSLExceptionDecisionsAtSessionEnd; |
| + expiration_delta = 0; |
| + Clear(); |
| + } else { |
| + should_remember_ssl_decisions_ = RememberSSLExceptionDecisionsForDelta; |
| + } |
| + default_ssl_cert_decision_expiration_delta_ = |
| + base::TimeDelta::FromSeconds(expiration_delta); |
| +} |
| + |
| +ChromeSSLHostStateDecisions::~ChromeSSLHostStateDecisions() { |
| + if (should_remember_ssl_decisions_ == ForgetSSLExceptionDecisionsAtSessionEnd) |
| + Clear(); |
| +} |
| + |
| +void ChromeSSLHostStateDecisions::DenyCert(const GURL& url, |
| + net::X509Certificate* cert, |
| + net::CertStatus error) { |
| + ChangeCertPolicy(url, cert, error, net::CertPolicy::DENIED); |
| +} |
| + |
| +void ChromeSSLHostStateDecisions::AllowCert(const GURL& url, |
| + net::X509Certificate* cert, |
| + net::CertStatus error) { |
| + ChangeCertPolicy(url, cert, error, net::CertPolicy::ALLOWED); |
| +} |
| + |
| +void ChromeSSLHostStateDecisions::Clear() { |
| + profile_->GetHostContentSettingsMap()->ClearSettingsForOneType( |
| + CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS); |
| +} |
| + |
| +net::CertPolicy::Judgment ChromeSSLHostStateDecisions::QueryPolicy( |
| + const GURL& url, |
| + net::X509Certificate* cert, |
| + net::CertStatus error) { |
| + HostContentSettingsMap* map = profile_->GetHostContentSettingsMap(); |
| + scoped_ptr<base::Value> value(map->GetWebsiteSetting( |
| + url, url, CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, std::string(), NULL)); |
| + |
| + if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY)) |
| + return net::CertPolicy::UNKNOWN; |
| + |
| + base::DictionaryValue* dict; // Owned by value |
| + int policy_decision; // Owned by dict |
|
Ryan Sleevi
2014/07/17 23:19:35
This comment doesn't make sense.
jww
2014/07/21 23:39:32
Ha. Nope, it certainly does not. Removing.
|
| + bool success = value->GetAsDictionary(&dict); |
| + DCHECK(success); |
| + |
| + base::DictionaryValue* cert_error_dict; // Owned by value |
| + cert_error_dict = |
| + GetValidCertDecisionsDict(dict, DoNotCreateDictionaryEntries); |
| + if (!cert_error_dict) |
| + return net::CertPolicy::UNKNOWN; |
| + |
| + success = cert_error_dict->GetIntegerWithoutPathExpansion(GetKey(cert, error), |
| + &policy_decision); |
| + |
| + if (!success) |
| + return net::CertPolicy::Judgment::UNKNOWN; |
| + |
| + return static_cast<net::CertPolicy::Judgment>(policy_decision); |
| +} |
| + |
| +void ChromeSSLHostStateDecisions::RevokeAllowAndDenyPreferences( |
| + const GURL& url) { |
| + const ContentSettingsPattern pattern = |
| + ContentSettingsPattern::FromURLNoWildcard(url); |
| + HostContentSettingsMap* map = profile_->GetHostContentSettingsMap(); |
| + |
| + map->SetWebsiteSetting(pattern, |
| + pattern, |
| + CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, |
| + std::string(), |
| + NULL); |
| +} |
| + |
| +bool ChromeSSLHostStateDecisions::HasAllowedOrDeniedCert(const GURL& url) { |
| + const ContentSettingsPattern pattern = |
| + ContentSettingsPattern::FromURLNoWildcard(url); |
| + HostContentSettingsMap* map = profile_->GetHostContentSettingsMap(); |
| + |
| + scoped_ptr<base::Value> value(map->GetWebsiteSetting( |
| + url, url, CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, std::string(), NULL)); |
| + |
| + if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY)) |
| + return false; |
| + |
| + base::DictionaryValue* dict; // Owned by value |
| + bool success = value->GetAsDictionary(&dict); |
| + DCHECK(success); |
| + |
| + for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) { |
| + int policy_decision; // Owned by dict |
| + success = it.value().GetAsInteger(&policy_decision); |
| + if (success && (static_cast<net::CertPolicy::Judgment>(policy_decision) != |
| + net::CertPolicy::UNKNOWN)) |
| + return true; |
| + } |
| + |
| + return false; |
| +} |
| + |
| +void ChromeSSLHostStateDecisions::ChangeCertPolicy( |
| + const GURL& url, |
| + net::X509Certificate* cert, |
| + net::CertStatus error, |
| + net::CertPolicy::Judgment judgment) { |
| + const ContentSettingsPattern pattern = |
| + ContentSettingsPattern::FromURLNoWildcard(url); |
| + HostContentSettingsMap* map = profile_->GetHostContentSettingsMap(); |
| + scoped_ptr<base::Value> value(map->GetWebsiteSetting( |
| + url, url, CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, std::string(), NULL)); |
| + |
| + if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY)) |
| + value.reset(new base::DictionaryValue()); |
| + |
| + base::DictionaryValue* dict; |
| + bool success = value->GetAsDictionary(&dict); |
| + DCHECK(success); |
| + |
| + base::DictionaryValue* cert_dict = |
| + GetValidCertDecisionsDict(dict, CreateDictionaryEntries); |
| + // If a a valid certificate dictionary cannot be extracted from the content |
| + // setting, that means it's in an unknown format. Unfortunately, there's |
| + // nothing to be done in that case, so a silent fail is the only option. |
| + if (!cert_dict) |
| + return; |
| + |
| + dict->SetIntegerWithoutPathExpansion(kSSLCertDecisionVersionKey, |
| + kDefaultSSLCertDecisionVersion); |
| + cert_dict->SetIntegerWithoutPathExpansion(GetKey(cert, error), judgment); |
| + |
| + // The map takes ownership of the value, so it is released in the call to |
| + // SetWebsiteSetting. |
| + map->SetWebsiteSetting(pattern, |
| + pattern, |
| + CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, |
| + std::string(), |
| + value.release()); |
| +} |