blob: 00368ec74a86956bf2b012519f565aa3de20c172 [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 "components/security_state/core/security_state.h"
#include <stdint.h>
#include "base/command_line.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram_macros.h"
#include "components/security_state/core/switches.h"
#include "net/ssl/ssl_cipher_suite_names.h"
#include "net/ssl/ssl_connection_status_flags.h"
namespace security_state {
namespace {
// Do not change or reorder this enum, and add new values at the end. It is used
// in the MarkHttpAs histogram.
enum MarkHttpStatus {
NEUTRAL /* deprecated */,
NON_SECURE,
HTTP_SHOW_WARNING_ON_SENSITIVE_FIELDS,
LAST_STATUS
};
// If |switch_or_field_trial_group| corresponds to a valid
// MarkHttpAs group, sets |*level| and |*histogram_status| to the
// appropriate values and returns true. Otherwise, returns false.
bool GetSecurityLevelAndHistogramValueForNonSecureFieldTrial(
std::string switch_or_field_trial_group,
bool displayed_sensitive_input_on_http,
SecurityLevel* level,
MarkHttpStatus* histogram_status) {
if (switch_or_field_trial_group != switches::kMarkHttpAsDangerous)
return false;
*level = DANGEROUS;
*histogram_status = NON_SECURE;
return true;
}
SecurityLevel GetSecurityLevelForNonSecureFieldTrial(
bool displayed_sensitive_input_on_http) {
std::string choice =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kMarkHttpAs);
std::string group = base::FieldTrialList::FindFullName("MarkNonSecureAs");
const char kEnumeration[] = "SSL.MarkHttpAsStatus";
SecurityLevel level = NONE;
MarkHttpStatus status;
// If the command-line switch is set, then it takes precedence over
// the field trial group.
if (!GetSecurityLevelAndHistogramValueForNonSecureFieldTrial(
choice, displayed_sensitive_input_on_http, &level, &status)) {
if (!GetSecurityLevelAndHistogramValueForNonSecureFieldTrial(
group, displayed_sensitive_input_on_http, &level, &status)) {
status = HTTP_SHOW_WARNING_ON_SENSITIVE_FIELDS;
level = displayed_sensitive_input_on_http
? security_state::HTTP_SHOW_WARNING
: NONE;
}
}
UMA_HISTOGRAM_ENUMERATION(kEnumeration, status, LAST_STATUS);
return level;
}
ContentStatus GetContentStatus(bool displayed, bool ran) {
if (ran && displayed)
return CONTENT_STATUS_DISPLAYED_AND_RAN;
if (ran)
return CONTENT_STATUS_RAN;
if (displayed)
return CONTENT_STATUS_DISPLAYED;
return CONTENT_STATUS_NONE;
}
SecurityLevel GetSecurityLevelForRequest(
const VisibleSecurityState& visible_security_state,
bool used_policy_installed_certificate,
const IsOriginSecureCallback& is_origin_secure_callback,
bool sha1_in_chain,
ContentStatus mixed_content_status,
ContentStatus content_with_cert_errors_status) {
DCHECK(visible_security_state.connection_info_initialized ||
visible_security_state.malicious_content_status !=
MALICIOUS_CONTENT_STATUS_NONE);
// Override the connection security information if the website failed the
// browser's malware checks.
if (visible_security_state.malicious_content_status !=
MALICIOUS_CONTENT_STATUS_NONE) {
return DANGEROUS;
}
const GURL url = visible_security_state.url;
const bool is_cryptographic_with_certificate =
(url.SchemeIsCryptographic() && visible_security_state.certificate);
// Set the security level to DANGEROUS for major certificate errors.
if (is_cryptographic_with_certificate &&
net::IsCertStatusError(visible_security_state.cert_status) &&
!net::IsCertStatusMinorError(visible_security_state.cert_status)) {
return DANGEROUS;
}
// data: URLs don't define a secure context, and are a vector for spoofing.
// Display a "Not secure" badge for all data URLs, regardless of whether
// they show a password or credit card field.
if (url.SchemeIs(url::kDataScheme))
return SecurityLevel::HTTP_SHOW_WARNING;
// Choose the appropriate security level for requests to HTTP and remaining
// pseudo URLs (blob:, filesystem:). filesystem: is a standard scheme so does
// not need to be explicitly listed here.
// TODO(meacer): Remove special case for blob (crbug.com/684751).
if (!is_cryptographic_with_certificate) {
if (!is_origin_secure_callback.Run(url) &&
(url.IsStandard() || url.SchemeIs(url::kBlobScheme))) {
return GetSecurityLevelForNonSecureFieldTrial(
visible_security_state.displayed_password_field_on_http ||
visible_security_state.displayed_credit_card_field_on_http);
}
return NONE;
}
// Downgrade the security level for active insecure subresources.
if (mixed_content_status == CONTENT_STATUS_RAN ||
mixed_content_status == CONTENT_STATUS_DISPLAYED_AND_RAN ||
content_with_cert_errors_status == CONTENT_STATUS_RAN ||
content_with_cert_errors_status == CONTENT_STATUS_DISPLAYED_AND_RAN) {
return kRanInsecureContentLevel;
}
// Report if there is a policy cert first, before reporting any other
// authenticated-but-with-errors cases. A policy cert is a strong
// indicator of a MITM being present (the enterprise), while the
// other authenticated-but-with-errors indicate something may
// be wrong, or may be wrong in the future, but is unclear now.
if (used_policy_installed_certificate)
return SECURE_WITH_POLICY_INSTALLED_CERT;
// In most cases, SHA1 use is treated as a certificate error, in which case
// DANGEROUS will have been returned above. If SHA1 was permitted by policy,
// downgrade the security level to Neutral.
if (sha1_in_chain)
return NONE;
// Active mixed content is handled above.
DCHECK_NE(CONTENT_STATUS_RAN, mixed_content_status);
DCHECK_NE(CONTENT_STATUS_DISPLAYED_AND_RAN, mixed_content_status);
if (mixed_content_status == CONTENT_STATUS_DISPLAYED ||
content_with_cert_errors_status == CONTENT_STATUS_DISPLAYED) {
return kDisplayedInsecureContentLevel;
}
if (net::IsCertStatusError(visible_security_state.cert_status)) {
// Major cert errors are handled above.
DCHECK(net::IsCertStatusMinorError(visible_security_state.cert_status));
return NONE;
}
if ((visible_security_state.cert_status & net::CERT_STATUS_IS_EV) &&
visible_security_state.certificate) {
return EV_SECURE;
}
return SECURE;
}
void SecurityInfoForRequest(
const VisibleSecurityState& visible_security_state,
bool used_policy_installed_certificate,
const IsOriginSecureCallback& is_origin_secure_callback,
SecurityInfo* security_info) {
if (!visible_security_state.connection_info_initialized) {
*security_info = SecurityInfo();
security_info->malicious_content_status =
visible_security_state.malicious_content_status;
if (security_info->malicious_content_status !=
MALICIOUS_CONTENT_STATUS_NONE) {
security_info->security_level = GetSecurityLevelForRequest(
visible_security_state, used_policy_installed_certificate,
is_origin_secure_callback, false, CONTENT_STATUS_UNKNOWN,
CONTENT_STATUS_UNKNOWN);
}
return;
}
security_info->certificate = visible_security_state.certificate;
security_info->sha1_in_chain = visible_security_state.certificate &&
(visible_security_state.cert_status &
net::CERT_STATUS_SHA1_SIGNATURE_PRESENT);
security_info->mixed_content_status =
GetContentStatus(visible_security_state.displayed_mixed_content,
visible_security_state.ran_mixed_content);
security_info->content_with_cert_errors_status = GetContentStatus(
visible_security_state.displayed_content_with_cert_errors,
visible_security_state.ran_content_with_cert_errors);
security_info->security_bits = visible_security_state.security_bits;
security_info->connection_status = visible_security_state.connection_status;
security_info->key_exchange_group = visible_security_state.key_exchange_group;
security_info->cert_status = visible_security_state.cert_status;
security_info->scheme_is_cryptographic =
visible_security_state.url.SchemeIsCryptographic();
security_info->obsolete_ssl_status =
net::ObsoleteSSLStatus(security_info->connection_status);
security_info->pkp_bypassed = visible_security_state.pkp_bypassed;
security_info->sct_verify_statuses =
visible_security_state.sct_verify_statuses;
security_info->malicious_content_status =
visible_security_state.malicious_content_status;
security_info->displayed_password_field_on_http =
visible_security_state.displayed_password_field_on_http;
security_info->displayed_credit_card_field_on_http =
visible_security_state.displayed_credit_card_field_on_http;
if (visible_security_state.certificate) {
security_info->cert_missing_subject_alt_name =
!visible_security_state.certificate->GetSubjectAltName(nullptr,
nullptr);
}
security_info->security_level = GetSecurityLevelForRequest(
visible_security_state, used_policy_installed_certificate,
is_origin_secure_callback, security_info->sha1_in_chain,
security_info->mixed_content_status,
security_info->content_with_cert_errors_status);
}
} // namespace
const base::Feature kHttpFormWarningFeature{"HttpFormWarning",
base::FEATURE_DISABLED_BY_DEFAULT};
SecurityInfo::SecurityInfo()
: security_level(NONE),
malicious_content_status(MALICIOUS_CONTENT_STATUS_NONE),
sha1_in_chain(false),
mixed_content_status(CONTENT_STATUS_NONE),
content_with_cert_errors_status(CONTENT_STATUS_NONE),
scheme_is_cryptographic(false),
cert_status(0),
security_bits(-1),
connection_status(0),
key_exchange_group(0),
obsolete_ssl_status(net::OBSOLETE_SSL_NONE),
pkp_bypassed(false),
displayed_password_field_on_http(false),
displayed_credit_card_field_on_http(false),
cert_missing_subject_alt_name(false) {}
SecurityInfo::~SecurityInfo() {}
void GetSecurityInfo(
std::unique_ptr<VisibleSecurityState> visible_security_state,
bool used_policy_installed_certificate,
IsOriginSecureCallback is_origin_secure_callback,
SecurityInfo* result) {
SecurityInfoForRequest(*visible_security_state,
used_policy_installed_certificate,
is_origin_secure_callback, result);
}
bool IsHttpWarningInFormEnabled() {
return base::FeatureList::IsEnabled(kHttpFormWarningFeature);
}
VisibleSecurityState::VisibleSecurityState()
: malicious_content_status(MALICIOUS_CONTENT_STATUS_NONE),
connection_info_initialized(false),
cert_status(0),
connection_status(0),
key_exchange_group(0),
security_bits(-1),
displayed_mixed_content(false),
ran_mixed_content(false),
displayed_content_with_cert_errors(false),
ran_content_with_cert_errors(false),
pkp_bypassed(false),
displayed_password_field_on_http(false),
displayed_credit_card_field_on_http(false) {}
VisibleSecurityState::~VisibleSecurityState() {}
bool VisibleSecurityState::operator==(const VisibleSecurityState& other) const {
return (url == other.url &&
malicious_content_status == other.malicious_content_status &&
!!certificate == !!other.certificate &&
(certificate ? certificate->Equals(other.certificate.get()) : true) &&
connection_status == other.connection_status &&
key_exchange_group == other.key_exchange_group &&
security_bits == other.security_bits &&
sct_verify_statuses == other.sct_verify_statuses &&
displayed_mixed_content == other.displayed_mixed_content &&
ran_mixed_content == other.ran_mixed_content &&
displayed_content_with_cert_errors ==
other.displayed_content_with_cert_errors &&
ran_content_with_cert_errors == other.ran_content_with_cert_errors &&
pkp_bypassed == other.pkp_bypassed &&
displayed_password_field_on_http ==
other.displayed_password_field_on_http &&
displayed_credit_card_field_on_http ==
other.displayed_credit_card_field_on_http);
}
} // namespace security_state