| Index: chrome/browser/ssl/chrome_ssl_host_state_delegate.cc
|
| diff --git a/chrome/browser/ssl/chrome_ssl_host_state_delegate.cc b/chrome/browser/ssl/chrome_ssl_host_state_delegate.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..edee05d7f96382ceff0f962c57f8f644b7ce703e
|
| --- /dev/null
|
| +++ b/chrome/browser/ssl/chrome_ssl_host_state_delegate.cc
|
| @@ -0,0 +1,377 @@
|
| +// 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_delegate.h"
|
| +
|
| +#include "base/base64.h"
|
| +#include "base/command_line.h"
|
| +#include "base/logging.h"
|
| +#include "base/metrics/field_trial.h"
|
| +#include "base/strings/string_number_conversions.h"
|
| +#include "base/time/clock.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 "components/content_settings/core/common/content_settings_types.h"
|
| +#include "components/variations/variations_associated_data.h"
|
| +#include "net/base/hash_value.h"
|
| +#include "net/cert/x509_certificate.h"
|
| +#include "url/gurl.h"
|
| +
|
| +namespace {
|
| +
|
| +// Switch value that specifies that certificate decisions should be forgotten at
|
| +// the end of the current session.
|
| +const int64 kForgetAtSessionEndSwitchValue = -1;
|
| +
|
| +// Experiment information
|
| +const char kRememberCertificateErrorDecisionsFieldTrialName[] =
|
| + "RememberCertificateErrorDecisions";
|
| +const char kRememberCertificateErrorDecisionsFieldTrialDefaultGroup[] =
|
| + "Default";
|
| +const char kRememberCertificateErrorDecisionsFieldTrialLengthParam[] = "length";
|
| +
|
| +// 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;
|
| +
|
| +// All SSL decisions are per host (and are shared arcoss schemes), so this
|
| +// canonicalizes all hosts into a secure scheme GURL to use with content
|
| +// settings. The returned GURL will be the passed in host with an empty path and
|
| +// https:// as the scheme.
|
| +GURL GetSecureGURLForHost(const std::string& host) {
|
| + std::string url = "https://" + host;
|
| + return GURL(url);
|
| +}
|
| +
|
| +// 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. Uses the experimental group
|
| +// unless overridden by a command line flag.
|
| +int64 GetExpirationDelta() {
|
| + // Check command line flags first to give them priority, then check
|
| + // experimental groups.
|
| + if (CommandLine::ForCurrentProcess()->HasSwitch(
|
| + switches::kRememberCertErrorDecisions)) {
|
| + std::string switch_value =
|
| + CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
|
| + switches::kRememberCertErrorDecisions);
|
| + int64 expiration_delta;
|
| + if (!base::StringToInt64(base::StringPiece(switch_value),
|
| + &expiration_delta) ||
|
| + expiration_delta < kForgetAtSessionEndSwitchValue) {
|
| + LOG(ERROR) << "Failed to parse the certificate error decision "
|
| + << "memory length: " << switch_value;
|
| + return kForgetAtSessionEndSwitchValue;
|
| + }
|
| +
|
| + return expiration_delta;
|
| + }
|
| +
|
| + // If the user is in the field trial, set the expiration to the length
|
| + // associated with that experimental group. The default group cannot have
|
| + // parameters associated with it, so it needs to be handled explictly.
|
| + std::string group_name = base::FieldTrialList::FindFullName(
|
| + kRememberCertificateErrorDecisionsFieldTrialName);
|
| + if (!group_name.empty() &&
|
| + group_name.compare(
|
| + kRememberCertificateErrorDecisionsFieldTrialDefaultGroup) != 0) {
|
| + int64 field_trial_param_length;
|
| + std::string param = variations::GetVariationParamValue(
|
| + kRememberCertificateErrorDecisionsFieldTrialName,
|
| + kRememberCertificateErrorDecisionsFieldTrialLengthParam);
|
| + if (!param.empty() && base::StringToInt64(base::StringPiece(param),
|
| + &field_trial_param_length)) {
|
| + return field_trial_param_length;
|
| + }
|
| + }
|
| +
|
| + return kForgetAtSessionEndSwitchValue;
|
| +}
|
| +
|
| +std::string GetKey(net::X509Certificate* cert, net::CertStatus error) {
|
| + // Since a security decision will be made based on the fingerprint, Chrome
|
| + // should use the SHA-256 fingerprint for the certificate.
|
| + net::SHA256HashValue fingerprint =
|
| + net::X509Certificate::CalculateChainFingerprint256(
|
| + cert->os_cert_handle(), cert->GetIntermediateCertificates());
|
| + 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* ChromeSSLHostStateDelegate::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 reset only at session restart, so skip all of the expiration
|
| + // 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()));
|
| + }
|
| +
|
| + // Extract the map of certificate fingerprints to errors from the setting.
|
| + base::DictionaryValue* cert_error_dict = NULL; // 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, 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.
|
| +ChromeSSLHostStateDelegate::ChromeSSLHostStateDelegate(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);
|
| +}
|
| +
|
| +ChromeSSLHostStateDelegate::~ChromeSSLHostStateDelegate() {
|
| + if (should_remember_ssl_decisions_ == ForgetSSLExceptionDecisionsAtSessionEnd)
|
| + Clear();
|
| +}
|
| +
|
| +void ChromeSSLHostStateDelegate::DenyCert(const std::string& host,
|
| + net::X509Certificate* cert,
|
| + net::CertStatus error) {
|
| + ChangeCertPolicy(host, cert, error, net::CertPolicy::DENIED);
|
| +}
|
| +
|
| +void ChromeSSLHostStateDelegate::AllowCert(const std::string& host,
|
| + net::X509Certificate* cert,
|
| + net::CertStatus error) {
|
| + ChangeCertPolicy(host, cert, error, net::CertPolicy::ALLOWED);
|
| +}
|
| +
|
| +void ChromeSSLHostStateDelegate::Clear() {
|
| + profile_->GetHostContentSettingsMap()->ClearSettingsForOneType(
|
| + CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS);
|
| +}
|
| +
|
| +net::CertPolicy::Judgment ChromeSSLHostStateDelegate::QueryPolicy(
|
| + const std::string& host,
|
| + net::X509Certificate* cert,
|
| + net::CertStatus error) {
|
| + HostContentSettingsMap* map = profile_->GetHostContentSettingsMap();
|
| + GURL url = GetSecureGURLForHost(host);
|
| + 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;
|
| + 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 a policy decision was successfully retrieved and it's a valid value of
|
| + // ALLOWED or DENIED, return the valid value. Otherwise, return UNKNOWN.
|
| + if (success && policy_decision == net::CertPolicy::Judgment::ALLOWED)
|
| + return net::CertPolicy::Judgment::ALLOWED;
|
| + else if (success && policy_decision == net::CertPolicy::Judgment::DENIED)
|
| + return net::CertPolicy::Judgment::DENIED;
|
| +
|
| + return net::CertPolicy::Judgment::UNKNOWN;
|
| +}
|
| +
|
| +void ChromeSSLHostStateDelegate::RevokeAllowAndDenyPreferences(
|
| + const std::string& host) {
|
| + GURL url = GetSecureGURLForHost(host);
|
| + const ContentSettingsPattern pattern =
|
| + ContentSettingsPattern::FromURLNoWildcard(url);
|
| + HostContentSettingsMap* map = profile_->GetHostContentSettingsMap();
|
| +
|
| + map->SetWebsiteSetting(pattern,
|
| + pattern,
|
| + CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS,
|
| + std::string(),
|
| + NULL);
|
| +}
|
| +
|
| +bool ChromeSSLHostStateDelegate::HasAllowedOrDeniedCert(
|
| + const std::string& host) {
|
| + GURL url = GetSecureGURLForHost(host);
|
| + 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 ChromeSSLHostStateDelegate::SetClock(scoped_ptr<base::Clock> clock) {
|
| + clock_.reset(clock.release());
|
| +}
|
| +
|
| +void ChromeSSLHostStateDelegate::ChangeCertPolicy(
|
| + const std::string& host,
|
| + net::X509Certificate* cert,
|
| + net::CertStatus error,
|
| + net::CertPolicy::Judgment judgment) {
|
| + GURL url = GetSecureGURLForHost(host);
|
| + 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());
|
| +}
|
|
|