blob: 9133c4c5d86e303a78573d971eda92de590b301d [file] [log] [blame]
// Copyright 2017 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/ssl_error_assistant.h"
#include <memory>
#include "base/macros.h"
#include "build/build_config.h"
#include "chrome/common/buildflags.h"
#include "chrome/grit/browser_resources.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "third_party/protobuf/src/google/protobuf/io/zero_copy_stream_impl_lite.h"
#include "third_party/re2/src/re2/re2.h"
#include "ui/base/resource/resource_bundle.h"
namespace {
net::CertStatus MapToCertStatus(
chrome_browser_ssl::DynamicInterstitial::CertError error) {
switch (error) {
case chrome_browser_ssl::DynamicInterstitial::ERR_CERT_COMMON_NAME_INVALID:
return net::CERT_STATUS_COMMON_NAME_INVALID;
case chrome_browser_ssl::DynamicInterstitial::ERR_CERT_DATE_INVALID:
return net::CERT_STATUS_DATE_INVALID;
case chrome_browser_ssl::DynamicInterstitial::ERR_CERT_AUTHORITY_INVALID:
return net::CERT_STATUS_AUTHORITY_INVALID;
case chrome_browser_ssl::DynamicInterstitial::
ERR_CERT_NO_REVOCATION_MECHANISM:
return net::CERT_STATUS_NO_REVOCATION_MECHANISM;
case chrome_browser_ssl::DynamicInterstitial::
ERR_CERT_UNABLE_TO_CHECK_REVOCATION:
return net::CERT_STATUS_UNABLE_TO_CHECK_REVOCATION;
case chrome_browser_ssl::DynamicInterstitial::
ERR_CERTIFICATE_TRANSPARENCY_REQUIRED:
return net::CERT_STATUS_CERTIFICATE_TRANSPARENCY_REQUIRED;
case chrome_browser_ssl::DynamicInterstitial::ERR_CERT_SYMANTEC_LEGACY:
return net::CERT_STATUS_SYMANTEC_LEGACY;
case chrome_browser_ssl::DynamicInterstitial::ERR_CERT_REVOKED:
return net::CERT_STATUS_REVOKED;
case chrome_browser_ssl::DynamicInterstitial::ERR_CERT_INVALID:
return net::CERT_STATUS_INVALID;
case chrome_browser_ssl::DynamicInterstitial::
ERR_CERT_WEAK_SIGNATURE_ALGORITHM:
return net::CERT_STATUS_WEAK_SIGNATURE_ALGORITHM;
case chrome_browser_ssl::DynamicInterstitial::ERR_CERT_NON_UNIQUE_NAME:
return net::CERT_STATUS_NON_UNIQUE_NAME;
case chrome_browser_ssl::DynamicInterstitial::ERR_CERT_WEAK_KEY:
return net::CERT_STATUS_WEAK_KEY;
case chrome_browser_ssl::DynamicInterstitial::
ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN:
return net::CERT_STATUS_PINNED_KEY_MISSING;
case chrome_browser_ssl::DynamicInterstitial::
ERR_CERT_NAME_CONSTRAINT_VIOLATION:
return net::CERT_STATUS_NAME_CONSTRAINT_VIOLATION;
case chrome_browser_ssl::DynamicInterstitial::ERR_CERT_VALIDITY_TOO_LONG:
return net::CERT_STATUS_VALIDITY_TOO_LONG;
default:
return 0;
}
}
std::unordered_set<std::string> HashesFromDynamicInterstitial(
const chrome_browser_ssl::DynamicInterstitial& entry) {
std::unordered_set<std::string> hashes;
for (const std::string& hash : entry.sha256_hash())
hashes.insert(hash);
return hashes;
}
std::unique_ptr<std::unordered_set<std::string>> LoadCaptivePortalCertHashes(
const chrome_browser_ssl::SSLErrorAssistantConfig& proto) {
auto hashes = std::make_unique<std::unordered_set<std::string>>();
for (const chrome_browser_ssl::CaptivePortalCert& cert :
proto.captive_portal_cert()) {
hashes.get()->insert(cert.sha256_hash());
}
return hashes;
}
std::unique_ptr<std::vector<MITMSoftwareType>> LoadMITMSoftwareList(
const chrome_browser_ssl::SSLErrorAssistantConfig& proto) {
auto mitm_software_list = std::make_unique<std::vector<MITMSoftwareType>>();
for (const chrome_browser_ssl::MITMSoftware& proto_entry :
proto.mitm_software()) {
// The |name| field and at least one of the |issuer_common_name_regex| and
// |issuer_organization_regex| fields must be set.
DCHECK(!proto_entry.name().empty());
DCHECK(!(proto_entry.issuer_common_name_regex().empty() &&
proto_entry.issuer_organization_regex().empty()));
if (proto_entry.name().empty() ||
(proto_entry.issuer_common_name_regex().empty() &&
proto_entry.issuer_organization_regex().empty())) {
continue;
}
mitm_software_list.get()->push_back(MITMSoftwareType(
proto_entry.name(), proto_entry.issuer_common_name_regex(),
proto_entry.issuer_organization_regex()));
}
return mitm_software_list;
}
std::unique_ptr<std::vector<DynamicInterstitialInfo>>
LoadDynamicInterstitialList(
const chrome_browser_ssl::SSLErrorAssistantConfig& proto) {
auto dynamic_interstitial_list =
std::make_unique<std::vector<DynamicInterstitialInfo>>();
for (const chrome_browser_ssl::DynamicInterstitial& entry :
proto.dynamic_interstitial()) {
dynamic_interstitial_list.get()->push_back(DynamicInterstitialInfo(
HashesFromDynamicInterstitial(entry), entry.issuer_common_name_regex(),
entry.issuer_organization_regex(), entry.mitm_software_name(),
entry.interstitial_type(), MapToCertStatus(entry.cert_error()),
GURL(entry.support_url()),
entry.show_only_for_nonoverridable_errors()));
}
return dynamic_interstitial_list;
}
bool RegexMatchesAny(const std::vector<std::string>& organization_names,
const std::string& pattern) {
const re2::RE2 regex(pattern);
for (const std::string& organization_name : organization_names) {
if (re2::RE2::FullMatch(organization_name, regex)) {
return true;
}
}
return false;
}
// Returns true if a hash in |ssl_info| is found in |spki_hashes|, a list of
// hashes.
bool MatchSSLInfoWithHashes(const net::SSLInfo& ssl_info,
std::unordered_set<std::string> spki_hashes) {
for (const net::HashValue& hash_value : ssl_info.public_key_hashes) {
if (hash_value.tag() != net::HASH_VALUE_SHA256)
continue;
if (spki_hashes.find(hash_value.ToString()) != spki_hashes.end())
return true;
}
return false;
}
} // namespace
MITMSoftwareType::MITMSoftwareType(const std::string& name,
const std::string& issuer_common_name_regex,
const std::string& issuer_organization_regex)
: name(name),
issuer_common_name_regex(issuer_common_name_regex),
issuer_organization_regex(issuer_organization_regex) {}
DynamicInterstitialInfo::DynamicInterstitialInfo(
const std::unordered_set<std::string>& spki_hashes,
const std::string& issuer_common_name_regex,
const std::string& issuer_organization_regex,
const std::string& mitm_software_name,
chrome_browser_ssl::DynamicInterstitial::InterstitialPageType
interstitial_type,
int cert_error,
const GURL& support_url,
bool show_only_for_nonoverridable_errors)
: spki_hashes(spki_hashes),
issuer_common_name_regex(issuer_common_name_regex),
issuer_organization_regex(issuer_organization_regex),
mitm_software_name(mitm_software_name),
interstitial_type(interstitial_type),
cert_error(cert_error),
support_url(support_url),
show_only_for_nonoverridable_errors(show_only_for_nonoverridable_errors) {
}
DynamicInterstitialInfo::~DynamicInterstitialInfo() {}
DynamicInterstitialInfo::DynamicInterstitialInfo(
const DynamicInterstitialInfo& other) = default;
SSLErrorAssistant::SSLErrorAssistant() {}
SSLErrorAssistant::~SSLErrorAssistant() {}
bool SSLErrorAssistant::IsKnownCaptivePortalCertificate(
const net::SSLInfo& ssl_info) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
EnsureInitialized();
CHECK(error_assistant_proto_);
if (!captive_portal_spki_hashes_) {
captive_portal_spki_hashes_ =
LoadCaptivePortalCertHashes(*error_assistant_proto_);
}
return MatchSSLInfoWithHashes(ssl_info, *(captive_portal_spki_hashes_.get()));
}
base::Optional<DynamicInterstitialInfo>
SSLErrorAssistant::MatchDynamicInterstitial(const net::SSLInfo& ssl_info,
bool is_overridable) {
// Load the dynamic interstitial data from SSL error assistant proto if it's
// not already loaded.
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
EnsureInitialized();
CHECK(error_assistant_proto_);
if (!dynamic_interstitial_list_) {
DCHECK(error_assistant_proto_);
dynamic_interstitial_list_ =
LoadDynamicInterstitialList(*error_assistant_proto_);
}
for (const DynamicInterstitialInfo& data : *dynamic_interstitial_list_) {
if (data.cert_error && !(ssl_info.cert_status & data.cert_error))
continue;
if (!data.spki_hashes.empty() &&
!MatchSSLInfoWithHashes(ssl_info, data.spki_hashes)) {
continue;
}
if (!data.issuer_common_name_regex.empty() &&
!re2::RE2::FullMatch(ssl_info.cert->issuer().common_name,
re2::RE2(data.issuer_common_name_regex))) {
continue;
}
if (!data.issuer_organization_regex.empty() &&
!RegexMatchesAny(ssl_info.cert->issuer().organization_names,
data.issuer_organization_regex)) {
continue;
}
// Don't match the entry if it's only intended for non-overridable
// errors, but the current error is overridable.
if (data.show_only_for_nonoverridable_errors && is_overridable) {
continue;
}
return data;
}
return base::nullopt;
}
const std::string SSLErrorAssistant::MatchKnownMITMSoftware(
const scoped_refptr<net::X509Certificate>& cert) {
// Ignore if the certificate doesn't have an issuer common name or an
// organization name.
if (cert->issuer().common_name.empty() &&
cert->issuer().organization_names.size() == 0) {
return std::string();
}
EnsureInitialized();
CHECK(error_assistant_proto_);
// Load MITM software data from the SSL error assistant proto.
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!mitm_software_list_) {
DCHECK(error_assistant_proto_);
mitm_software_list_ = LoadMITMSoftwareList(*error_assistant_proto_);
}
for (const MITMSoftwareType& mitm_software : *mitm_software_list_) {
// At least one of the common name or organization name fields should be
// populated on the MITM software list entry.
DCHECK(!(mitm_software.issuer_common_name_regex.empty() &&
mitm_software.issuer_organization_regex.empty()));
if (mitm_software.issuer_common_name_regex.empty() &&
mitm_software.issuer_organization_regex.empty()) {
continue;
}
// If both |issuer_common_name_regex| and |issuer_organization_regex| are
// set, the certificate should match both regexes.
if (!mitm_software.issuer_common_name_regex.empty() &&
!mitm_software.issuer_organization_regex.empty()) {
if (re2::RE2::FullMatch(
cert->issuer().common_name,
re2::RE2(mitm_software.issuer_common_name_regex)) &&
RegexMatchesAny(cert->issuer().organization_names,
mitm_software.issuer_organization_regex)) {
return mitm_software.name;
}
// If only |issuer_organization_regex| is set, the certificate's issuer
// organization name should match.
} else if (!mitm_software.issuer_organization_regex.empty()) {
if (RegexMatchesAny(cert->issuer().organization_names,
mitm_software.issuer_organization_regex)) {
return mitm_software.name;
}
// If only |issuer_common_name_regex| is set, the certificate's issuer
// common name should match.
} else if (!mitm_software.issuer_common_name_regex.empty()) {
if (re2::RE2::FullMatch(
cert->issuer().common_name,
re2::RE2(mitm_software.issuer_common_name_regex))) {
return mitm_software.name;
}
}
}
return std::string();
}
// static
std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig>
SSLErrorAssistant::GetErrorAssistantProtoFromResourceBundle() {
// Reads the SSL error assistant configuration from the resource bundle.
auto proto = std::make_unique<chrome_browser_ssl::SSLErrorAssistantConfig>();
DCHECK(proto);
ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
base::StringPiece data =
bundle.GetRawDataResource(IDR_SSL_ERROR_ASSISTANT_PB);
google::protobuf::io::ArrayInputStream stream(data.data(), data.size());
return proto->ParseFromZeroCopyStream(&stream) ? std::move(proto) : nullptr;
}
void SSLErrorAssistant::SetErrorAssistantProto(
std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig> proto) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
CHECK(proto);
// Ignore versions that are not new. INT_MAX is used by tests, so always allow
// it.
if (error_assistant_proto_ && proto->version_id() != INT_MAX &&
proto->version_id() <= error_assistant_proto_->version_id()) {
return;
}
error_assistant_proto_ = std::move(proto);
mitm_software_list_ = LoadMITMSoftwareList(*error_assistant_proto_);
captive_portal_spki_hashes_ =
LoadCaptivePortalCertHashes(*error_assistant_proto_);
dynamic_interstitial_list_ =
LoadDynamicInterstitialList(*error_assistant_proto_);
}
void SSLErrorAssistant::EnsureInitialized() {
if (!error_assistant_proto_)
error_assistant_proto_ = GetErrorAssistantProtoFromResourceBundle();
}
void SSLErrorAssistant::ResetForTesting() {
error_assistant_proto_.reset();
mitm_software_list_.reset();
captive_portal_spki_hashes_.reset();
dynamic_interstitial_list_.reset();
}
int SSLErrorAssistant::GetErrorAssistantProtoVersionIdForTesting() const {
return error_assistant_proto_->version_id();
}