| 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..c0a92d9d08c4af7a3711c463074ed5cf89d60c58
|
| --- /dev/null
|
| +++ b/chrome/browser/ssl/chrome_ssl_host_state_decisions.cc
|
| @@ -0,0 +1,310 @@
|
| +// 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 {
|
| +
|
| +const int64 kForgetAtSessionEndSwitchValue = -1;
|
| +const int64 kExpirationZeroInSeconds = 0;
|
| +const int64 kExpirationOneDayInSeconds = 86400;
|
| +const int64 kExpirationThreeDaysInSeconds = 259200;
|
| +const int64 kExpirationOneWeekInSeconds = 604800;
|
| +const int64 kExpirationOneMonthInSeconds = 2592000;
|
| +
|
| +// 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))
|
| + return kExpirationOneMonthInSeconds;
|
| +
|
| + return kForgetAtSessionEndSwitchValue;
|
| +}
|
| +
|
| +const char kSSLCertDecisionCertErrorMapKey[] = "cert_exceptions_map";
|
| +const char kSSLCertDecisionExpirationTimeKey[] = "decision_expiration_time";
|
| +const char kSSLCertDecisionVersionKey[] = "version";
|
| +const int kDefaultSSLCertDecisionVersion = 1;
|
| +
|
| +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
|
| + // checks.
|
| + bool expired = false;
|
| + double decision_expiration_dbl;
|
| + base::Time now = clock_->Now();
|
| + base::Time decision_expiration;
|
| + if (dict->HasKey(kSSLCertDecisionExpirationTimeKey)) {
|
| + success = dict->GetDouble(kSSLCertDecisionExpirationTimeKey,
|
| + &decision_expiration_dbl);
|
| + decision_expiration = base::Time::FromDoubleT(decision_expiration_dbl);
|
| + }
|
| +
|
| + // Check if the user certificate decision has expired. If it has expired, and
|
| + // create_entries is |DoNotCreateDictionaryEntries|, treat the entry as if it
|
| + // doesn't exist and return |NULL|. If it has expired, and create_entries is
|
| + // |DoNotCreateDictionaryEntries|, set the entry's expiration date to the
|
| + // current time plus the expiration time delta. Also, set expired to |true|
|
| + // which will force the creation of a new dictionary in the dictionary
|
| + // extraction pass. None of this should be done if expiration happens on
|
| + // session end (i.e. shouldRememberSSLDecisions_ is set to
|
| + // ForgetSSLExceptionDecisionsAtSessionEnd).
|
| + if (shouldRememberSSLDecisions_ != ForgetSSLExceptionDecisionsAtSessionEnd &&
|
| + decision_expiration <= now) {
|
| + if (create_entries == DoNotCreateDictionaryEntries)
|
| + return NULL;
|
| +
|
| + expired = true;
|
| +
|
| + base::Time expiration_time = now + defaultSSLCertDecisionExpirationDelta_;
|
| + dict->SetDouble(kSSLCertDecisionExpirationTimeKey,
|
| + expiration_time.ToDoubleT());
|
| + }
|
| +
|
| + // Extract the map of certificate fingerprints to errors from the setting.
|
| + base::DictionaryValue* cert_error_dict; // 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 shouldRememberSSLDecisions_ is ForgetSSLExceptionDecisionsAtSessionEnd,
|
| +// 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) {
|
| + shouldRememberSSLDecisions_ = ForgetSSLExceptionDecisionsAtSessionEnd;
|
| + expiration_delta = 0;
|
| + Clear();
|
| + } else {
|
| + shouldRememberSSLDecisions_ = RememberSSLExceptionDecisionsForDelta;
|
| + }
|
| + defaultSSLCertDecisionExpirationDelta_ =
|
| + base::TimeDelta::FromSeconds(expiration_delta);
|
| +}
|
| +
|
| +ChromeSSLHostStateDecisions::~ChromeSSLHostStateDecisions() {
|
| + if (shouldRememberSSLDecisions_ == 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, "", NULL));
|
| +
|
| + DCHECK(value.get());
|
| +
|
| + if (!value->IsType(base::Value::TYPE_DICTIONARY))
|
| + return net::CertPolicy::UNKNOWN;
|
| +
|
| + base::DictionaryValue* dict; // Owned by value
|
| + int policy_decision; // Owned by dict
|
| + bool success = value->GetAsDictionary(&dict);
|
| + DCHECK(success);
|
| +
|
| + // First, check that the version of the dictionary of decisions is a version
|
| + // that is known. Otherwise, fail.
|
| + 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, "", 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, "", NULL));
|
| +
|
| + if (!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, "", NULL));
|
| +
|
| + if (!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,
|
| + "",
|
| + value.release());
|
| +}
|
|
|