blob: 72bf6d6ff15f81942c291172560dd0695b76be45 [file] [log] [blame]
// Copyright 2015 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_security_state_model_client.h"
#include <vector>
#include "base/command_line.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/chromeos/policy/policy_cert_service.h"
#include "chrome/browser/chromeos/policy/policy_cert_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/grit/generated_resources.h"
#include "content/public/browser/cert_store.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/security_style_explanation.h"
#include "content/public/browser/security_style_explanations.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/origin_util.h"
#include "content/public/common/ssl_status.h"
#include "net/base/net_errors.h"
#include "net/cert/x509_certificate.h"
#include "net/ssl/ssl_cipher_suite_names.h"
#include "net/ssl/ssl_connection_status_flags.h"
#include "ui/base/l10n/l10n_util.h"
DEFINE_WEB_CONTENTS_USER_DATA_KEY(ChromeSecurityStateModelClient);
using security_state::SecurityStateModel;
namespace {
// Converts a content::SecurityStyle (an indicator of a request's
// overall security level computed by //content) into a
// SecurityStateModel::SecurityLevel (a finer-grained SecurityStateModel
// concept that can express all of SecurityStateModel's policies that
// //content doesn't necessarily know about).
SecurityStateModel::SecurityLevel GetSecurityLevelForSecurityStyle(
content::SecurityStyle style) {
switch (style) {
case content::SECURITY_STYLE_UNKNOWN:
NOTREACHED();
return SecurityStateModel::NONE;
case content::SECURITY_STYLE_UNAUTHENTICATED:
return SecurityStateModel::NONE;
case content::SECURITY_STYLE_AUTHENTICATION_BROKEN:
return SecurityStateModel::SECURITY_ERROR;
case content::SECURITY_STYLE_WARNING:
// content currently doesn't use this style.
NOTREACHED();
return SecurityStateModel::SECURITY_WARNING;
case content::SECURITY_STYLE_AUTHENTICATED:
return SecurityStateModel::SECURE;
}
return SecurityStateModel::NONE;
}
// Note: This is a lossy operation. Not all of the policies that can be
// expressed by a SecurityLevel (a //chrome concept) can be expressed by
// a content::SecurityStyle.
content::SecurityStyle SecurityLevelToSecurityStyle(
SecurityStateModel::SecurityLevel security_level) {
switch (security_level) {
case SecurityStateModel::NONE:
return content::SECURITY_STYLE_UNAUTHENTICATED;
case SecurityStateModel::SECURITY_WARNING:
case SecurityStateModel::SECURITY_POLICY_WARNING:
return content::SECURITY_STYLE_WARNING;
case SecurityStateModel::EV_SECURE:
case SecurityStateModel::SECURE:
return content::SECURITY_STYLE_AUTHENTICATED;
case SecurityStateModel::SECURITY_ERROR:
return content::SECURITY_STYLE_AUTHENTICATION_BROKEN;
}
NOTREACHED();
return content::SECURITY_STYLE_UNKNOWN;
}
void AddConnectionExplanation(
const security_state::SecurityStateModel::SecurityInfo& security_info,
content::SecurityStyleExplanations* security_style_explanations) {
// Avoid showing TLS details when we couldn't even establish a TLS connection
// (e.g. for net errors) or if there was no real connection (some tests). We
// check the |cert_id| to see if there was a connection.
if (security_info.cert_id == 0 || security_info.connection_status == 0) {
return;
}
int ssl_version =
net::SSLConnectionStatusToVersion(security_info.connection_status);
const char* protocol;
net::SSLVersionToString(&protocol, ssl_version);
const char* key_exchange;
const char* cipher;
const char* mac;
bool is_aead;
uint16_t cipher_suite =
net::SSLConnectionStatusToCipherSuite(security_info.connection_status);
net::SSLCipherSuiteToStrings(&key_exchange, &cipher, &mac, &is_aead,
cipher_suite);
base::string16 protocol_name = base::ASCIIToUTF16(protocol);
base::string16 key_exchange_name = base::ASCIIToUTF16(key_exchange);
const base::string16 cipher_name =
(mac == NULL) ? base::ASCIIToUTF16(cipher)
: l10n_util::GetStringFUTF16(IDS_CIPHER_WITH_MAC,
base::ASCIIToUTF16(cipher),
base::ASCIIToUTF16(mac));
if (security_info.obsolete_ssl_status == net::OBSOLETE_SSL_NONE) {
security_style_explanations->secure_explanations.push_back(
content::SecurityStyleExplanation(
l10n_util::GetStringUTF8(IDS_STRONG_SSL_SUMMARY),
l10n_util::GetStringFUTF8(IDS_STRONG_SSL_DESCRIPTION, protocol_name,
key_exchange_name, cipher_name)));
return;
}
std::vector<base::string16> description_replacements;
int status = security_info.obsolete_ssl_status;
int str_id;
str_id = (status & net::OBSOLETE_SSL_MASK_PROTOCOL)
? IDS_SSL_AN_OBSOLETE_PROTOCOL
: IDS_SSL_A_STRONG_PROTOCOL;
description_replacements.push_back(l10n_util::GetStringUTF16(str_id));
description_replacements.push_back(protocol_name);
str_id = (status & net::OBSOLETE_SSL_MASK_KEY_EXCHANGE)
? IDS_SSL_AN_OBSOLETE_KEY_EXCHANGE
: IDS_SSL_A_STRONG_KEY_EXCHANGE;
description_replacements.push_back(l10n_util::GetStringUTF16(str_id));
description_replacements.push_back(key_exchange_name);
str_id = (status & net::OBSOLETE_SSL_MASK_CIPHER) ? IDS_SSL_AN_OBSOLETE_CIPHER
: IDS_SSL_A_STRONG_CIPHER;
description_replacements.push_back(l10n_util::GetStringUTF16(str_id));
description_replacements.push_back(cipher_name);
security_style_explanations->info_explanations.push_back(
content::SecurityStyleExplanation(
l10n_util::GetStringUTF8(IDS_OBSOLETE_SSL_SUMMARY),
base::UTF16ToUTF8(
l10n_util::GetStringFUTF16(IDS_OBSOLETE_SSL_DESCRIPTION,
description_replacements, nullptr))));
}
} // namespace
ChromeSecurityStateModelClient::ChromeSecurityStateModelClient(
content::WebContents* web_contents)
: web_contents_(web_contents),
security_state_model_(new SecurityStateModel()) {
security_state_model_->SetClient(this);
}
ChromeSecurityStateModelClient::~ChromeSecurityStateModelClient() {}
// static
content::SecurityStyle ChromeSecurityStateModelClient::GetSecurityStyle(
const security_state::SecurityStateModel::SecurityInfo& security_info,
content::SecurityStyleExplanations* security_style_explanations) {
const content::SecurityStyle security_style =
SecurityLevelToSecurityStyle(security_info.security_level);
security_style_explanations->ran_insecure_content_style =
SecurityLevelToSecurityStyle(
SecurityStateModel::kRanInsecureContentLevel);
security_style_explanations->displayed_insecure_content_style =
SecurityLevelToSecurityStyle(
SecurityStateModel::kDisplayedInsecureContentLevel);
// Check if the page is HTTP; if so, no explanations are needed. Note
// that SECURITY_STYLE_UNAUTHENTICATED does not necessarily mean that
// the page is loaded over HTTP, because the security style merely
// represents how the embedder wishes to display the security state of
// the page, and the embedder can choose to display HTTPS page as HTTP
// if it wants to (for example, displaying deprecated crypto
// algorithms with the same UI treatment as HTTP pages).
security_style_explanations->scheme_is_cryptographic =
security_info.scheme_is_cryptographic;
if (!security_info.scheme_is_cryptographic) {
return security_style;
}
if (security_info.sha1_deprecation_status ==
SecurityStateModel::DEPRECATED_SHA1_MAJOR) {
security_style_explanations->broken_explanations.push_back(
content::SecurityStyleExplanation(
l10n_util::GetStringUTF8(IDS_MAJOR_SHA1),
l10n_util::GetStringUTF8(IDS_MAJOR_SHA1_DESCRIPTION),
security_info.cert_id));
} else if (security_info.sha1_deprecation_status ==
SecurityStateModel::DEPRECATED_SHA1_MINOR) {
security_style_explanations->unauthenticated_explanations.push_back(
content::SecurityStyleExplanation(
l10n_util::GetStringUTF8(IDS_MINOR_SHA1),
l10n_util::GetStringUTF8(IDS_MINOR_SHA1_DESCRIPTION),
security_info.cert_id));
}
security_style_explanations->ran_insecure_content =
security_info.mixed_content_status ==
SecurityStateModel::CONTENT_STATUS_RAN ||
security_info.mixed_content_status ==
SecurityStateModel::CONTENT_STATUS_DISPLAYED_AND_RAN;
security_style_explanations->displayed_insecure_content =
security_info.mixed_content_status ==
SecurityStateModel::CONTENT_STATUS_DISPLAYED ||
security_info.mixed_content_status ==
SecurityStateModel::CONTENT_STATUS_DISPLAYED_AND_RAN;
if (net::IsCertStatusError(security_info.cert_status)) {
base::string16 error_string = base::UTF8ToUTF16(net::ErrorToString(
net::MapCertStatusToNetError(security_info.cert_status)));
content::SecurityStyleExplanation explanation(
l10n_util::GetStringUTF8(IDS_CERTIFICATE_CHAIN_ERROR),
l10n_util::GetStringFUTF8(
IDS_CERTIFICATE_CHAIN_ERROR_DESCRIPTION_FORMAT, error_string),
security_info.cert_id);
if (net::IsCertStatusMinorError(security_info.cert_status))
security_style_explanations->unauthenticated_explanations.push_back(
explanation);
else
security_style_explanations->broken_explanations.push_back(explanation);
} else {
// If the certificate does not have errors and is not using
// deprecated SHA1, then add an explanation that the certificate is
// valid.
if (security_info.sha1_deprecation_status ==
SecurityStateModel::NO_DEPRECATED_SHA1) {
security_style_explanations->secure_explanations.push_back(
content::SecurityStyleExplanation(
l10n_util::GetStringUTF8(IDS_VALID_SERVER_CERTIFICATE),
l10n_util::GetStringUTF8(
IDS_VALID_SERVER_CERTIFICATE_DESCRIPTION),
security_info.cert_id));
}
}
AddConnectionExplanation(security_info, security_style_explanations);
security_style_explanations->pkp_bypassed = security_info.pkp_bypassed;
if (security_info.pkp_bypassed) {
security_style_explanations->info_explanations.push_back(
content::SecurityStyleExplanation(
"Public-Key Pinning Bypassed",
"Public-key pinning was bypassed by a local root certificate."));
}
return security_style;
}
const SecurityStateModel::SecurityInfo&
ChromeSecurityStateModelClient::GetSecurityInfo() const {
return security_state_model_->GetSecurityInfo();
}
bool ChromeSecurityStateModelClient::RetrieveCert(
scoped_refptr<net::X509Certificate>* cert) {
content::NavigationEntry* entry =
web_contents_->GetController().GetVisibleEntry();
if (!entry)
return false;
return content::CertStore::GetInstance()->RetrieveCert(
entry->GetSSL().cert_id, cert);
}
bool ChromeSecurityStateModelClient::UsedPolicyInstalledCertificate() {
#if defined(OS_CHROMEOS)
policy::PolicyCertService* service =
policy::PolicyCertServiceFactory::GetForProfile(
Profile::FromBrowserContext(web_contents_->GetBrowserContext()));
if (service && service->UsedPolicyCertificates())
return true;
#endif
return false;
}
bool ChromeSecurityStateModelClient::IsOriginSecure(const GURL& url) {
return content::IsOriginSecure(url);
}
void ChromeSecurityStateModelClient::GetVisibleSecurityState(
SecurityStateModel::VisibleSecurityState* state) {
content::NavigationEntry* entry =
web_contents_->GetController().GetVisibleEntry();
if (!entry ||
entry->GetSSL().security_style == content::SECURITY_STYLE_UNKNOWN) {
*state = SecurityStateModel::VisibleSecurityState();
return;
}
state->connection_info_initialized = true;
state->url = entry->GetURL();
const content::SSLStatus& ssl = entry->GetSSL();
state->initial_security_level =
GetSecurityLevelForSecurityStyle(ssl.security_style);
state->cert_id = ssl.cert_id;
state->cert_status = ssl.cert_status;
state->connection_status = ssl.connection_status;
state->security_bits = ssl.security_bits;
state->pkp_bypassed = ssl.pkp_bypassed;
state->sct_verify_statuses.clear();
state->sct_verify_statuses.insert(state->sct_verify_statuses.begin(),
ssl.sct_statuses.begin(),
ssl.sct_statuses.end());
state->displayed_mixed_content =
!!(ssl.content_status & content::SSLStatus::DISPLAYED_INSECURE_CONTENT);
state->ran_mixed_content =
!!(ssl.content_status & content::SSLStatus::RAN_INSECURE_CONTENT);
state->displayed_content_with_cert_errors =
!!(ssl.content_status &
content::SSLStatus::DISPLAYED_CONTENT_WITH_CERT_ERRORS);
state->ran_content_with_cert_errors =
!!(ssl.content_status & content::SSLStatus::RAN_CONTENT_WITH_CERT_ERRORS);
}