blob: 721df330a20067565798da191bc5eaf73c3992e9 [file] [log] [blame]
// Copyright (c) 2012 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_blocking_page.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback_helpers.h"
#include "base/i18n/rtl.h"
#include "base/i18n/time_formatting.h"
#include "base/metrics/histogram.h"
#include "base/prefs/pref_service.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/interstitials/chrome_metrics_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/renderer_preferences_util.h"
#include "chrome/browser/ssl/cert_report_helper.h"
#include "chrome/browser/ssl/ssl_cert_reporter.h"
#include "chrome/browser/ssl/ssl_error_classification.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/chromium_strings.h"
#include "chrome/grit/generated_resources.h"
#include "components/google/core/browser/google_util.h"
#include "components/ssl_errors/error_info.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/cert_store.h"
#include "content/public/browser/interstitial_page.h"
#include "content/public/browser/interstitial_page_delegate.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/signed_certificate_timestamp_store.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/renderer_preferences.h"
#include "content/public/common/ssl_status.h"
#include "grit/browser_resources.h"
#include "grit/components_strings.h"
#include "net/base/hash_value.h"
#include "net/base/net_errors.h"
#include "net/base/net_util.h"
#include "ui/base/l10n/l10n_util.h"
using base::ASCIIToUTF16;
using base::TimeTicks;
using content::InterstitialPage;
using content::InterstitialPageDelegate;
using content::NavigationController;
using content::NavigationEntry;
namespace {
// URL for help page.
const char kHelpURL[] = "https://support.google.com/chrome/answer/4454607";
// Constants for the Experience Sampling instrumentation.
const char kEventNameBase[] = "ssl_interstitial_";
const char kEventNotOverridable[] = "notoverridable_";
const char kEventOverridable[] = "overridable_";
// Events for UMA. Do not reorder or change!
enum SSLExpirationAndDecision {
EXPIRED_AND_PROCEED,
EXPIRED_AND_DO_NOT_PROCEED,
NOT_EXPIRED_AND_PROCEED,
NOT_EXPIRED_AND_DO_NOT_PROCEED,
END_OF_SSL_EXPIRATION_AND_DECISION,
};
// Rappor prefix, which is used for both overridable and non-overridable
// interstitials so we don't leak the "overridable" bit.
const char kSSLRapporPrefix[] = "ssl2";
void RecordSSLExpirationPageEventState(bool expired_but_previously_allowed,
bool proceed,
bool overridable) {
SSLExpirationAndDecision event;
if (expired_but_previously_allowed && proceed)
event = EXPIRED_AND_PROCEED;
else if (expired_but_previously_allowed && !proceed)
event = EXPIRED_AND_DO_NOT_PROCEED;
else if (!expired_but_previously_allowed && proceed)
event = NOT_EXPIRED_AND_PROCEED;
else
event = NOT_EXPIRED_AND_DO_NOT_PROCEED;
if (overridable) {
UMA_HISTOGRAM_ENUMERATION(
"interstitial.ssl.expiration_and_decision.overridable",
event,
END_OF_SSL_EXPIRATION_AND_DECISION);
} else {
UMA_HISTOGRAM_ENUMERATION(
"interstitial.ssl.expiration_and_decision.nonoverridable",
event,
END_OF_SSL_EXPIRATION_AND_DECISION);
}
}
} // namespace
// static
InterstitialPageDelegate::TypeID SSLBlockingPage::kTypeForTesting =
&SSLBlockingPage::kTypeForTesting;
// Note that we always create a navigation entry with SSL errors.
// No error happening loading a sub-resource triggers an interstitial so far.
SSLBlockingPage::SSLBlockingPage(content::WebContents* web_contents,
int cert_error,
const net::SSLInfo& ssl_info,
const GURL& request_url,
int options_mask,
const base::Time& time_triggered,
scoped_ptr<SSLCertReporter> ssl_cert_reporter,
const base::Callback<void(bool)>& callback)
: SecurityInterstitialPage(web_contents, request_url),
callback_(callback),
cert_error_(cert_error),
ssl_info_(ssl_info),
overridable_(IsOverridable(
options_mask,
Profile::FromBrowserContext(web_contents->GetBrowserContext()))),
danger_overridable_(DoesPolicyAllowDangerOverride(
Profile::FromBrowserContext(web_contents->GetBrowserContext()))),
strict_enforcement_((options_mask & STRICT_ENFORCEMENT) != 0),
expired_but_previously_allowed_(
(options_mask & EXPIRED_BUT_PREVIOUSLY_ALLOWED) != 0),
time_triggered_(time_triggered) {
security_interstitials::MetricsHelper::ReportDetails reporting_info;
reporting_info.metric_prefix = GetUmaHistogramPrefix();
reporting_info.rappor_prefix = kSSLRapporPrefix;
reporting_info.rappor_report_type = rappor::UMA_RAPPOR_TYPE;
scoped_ptr<ChromeMetricsHelper> chrome_metrics_helper(new ChromeMetricsHelper(
web_contents, request_url, reporting_info, GetSamplingEventName()));
chrome_metrics_helper->StartRecordingCaptivePortalMetrics(overridable_);
set_metrics_helper(chrome_metrics_helper.Pass());
metrics_helper()->RecordUserDecision(
security_interstitials::MetricsHelper::SHOW);
metrics_helper()->RecordUserInteraction(
security_interstitials::MetricsHelper::TOTAL_VISITS);
cert_report_helper_.reset(new CertReportHelper(
ssl_cert_reporter.Pass(), web_contents, request_url, ssl_info,
certificate_reporting::ErrorReport::INTERSTITIAL_SSL, overridable_,
metrics_helper()));
SSLErrorClassification error_classification(
time_triggered_, request_url, cert_error_, *ssl_info_.cert.get());
error_classification.RecordUMAStatistics(overridable_);
// Creating an interstitial without showing (e.g. from chrome://interstitials)
// it leaks memory, so don't create it here.
}
bool SSLBlockingPage::ShouldCreateNewNavigation() const {
return true;
}
InterstitialPageDelegate::TypeID SSLBlockingPage::GetTypeForTesting() const {
return SSLBlockingPage::kTypeForTesting;
}
SSLBlockingPage::~SSLBlockingPage() {
metrics_helper()->RecordShutdownMetrics();
if (!callback_.is_null()) {
// The page is closed without the user having chosen what to do, default to
// deny.
metrics_helper()->RecordUserDecision(
security_interstitials::MetricsHelper::DONT_PROCEED);
RecordSSLExpirationPageEventState(
expired_but_previously_allowed_, false, overridable_);
NotifyDenyCertificate();
}
}
void SSLBlockingPage::PopulateInterstitialStrings(
base::DictionaryValue* load_time_data) {
CHECK(load_time_data);
base::string16 url(GetFormattedHostName());
// Shared values for both the overridable and non-overridable versions.
load_time_data->SetString("type", "SSL");
load_time_data->SetBoolean("bad_clock", false);
// Shared UI configuration for all SSL interstitials.
load_time_data->SetString("errorCode", net::ErrorToString(cert_error_));
load_time_data->SetString(
"openDetails",
l10n_util::GetStringUTF16(IDS_SSL_OPEN_DETAILS_BUTTON));
load_time_data->SetString(
"closeDetails",
l10n_util::GetStringUTF16(IDS_SSL_CLOSE_DETAILS_BUTTON));
load_time_data->SetString("tabTitle",
l10n_util::GetStringUTF16(IDS_SSL_V2_TITLE));
load_time_data->SetString("heading",
l10n_util::GetStringUTF16(IDS_SSL_V2_HEADING));
load_time_data->SetString(
"primaryParagraph",
l10n_util::GetStringFUTF16(IDS_SSL_V2_PRIMARY_PARAGRAPH, url));
if (overridable_) {
load_time_data->SetBoolean("overridable", true);
ssl_errors::ErrorInfo error_info = ssl_errors::ErrorInfo::CreateError(
ssl_errors::ErrorInfo::NetErrorToErrorType(cert_error_),
ssl_info_.cert.get(), request_url());
load_time_data->SetString("explanationParagraph", error_info.details());
load_time_data->SetString(
"primaryButtonText",
l10n_util::GetStringUTF16(IDS_SSL_OVERRIDABLE_SAFETY_BUTTON));
load_time_data->SetString(
"finalParagraph",
l10n_util::GetStringFUTF16(IDS_SSL_OVERRIDABLE_PROCEED_PARAGRAPH, url));
} else {
load_time_data->SetBoolean("overridable", false);
ssl_errors::ErrorInfo::ErrorType type =
ssl_errors::ErrorInfo::NetErrorToErrorType(cert_error_);
if (type == ssl_errors::ErrorInfo::CERT_INVALID &&
SSLErrorClassification::MaybeWindowsLacksSHA256Support()) {
load_time_data->SetString(
"explanationParagraph",
l10n_util::GetStringFUTF16(IDS_SSL_NONOVERRIDABLE_MORE_INVALID_SP3,
url));
} else {
load_time_data->SetString(
"explanationParagraph",
l10n_util::GetStringFUTF16(IDS_SSL_NONOVERRIDABLE_MORE, url));
}
load_time_data->SetString("primaryButtonText",
l10n_util::GetStringUTF16(IDS_SSL_RELOAD));
// Customize the help link depending on the specific error type.
// Only mark as HSTS if none of the more specific error types apply,
// and use INVALID as a fallback if no other string is appropriate.
load_time_data->SetInteger("errorType", type);
int help_string = IDS_SSL_NONOVERRIDABLE_INVALID;
switch (type) {
case ssl_errors::ErrorInfo::CERT_REVOKED:
help_string = IDS_SSL_NONOVERRIDABLE_REVOKED;
break;
case ssl_errors::ErrorInfo::CERT_PINNED_KEY_MISSING:
help_string = IDS_SSL_NONOVERRIDABLE_PINNED;
break;
case ssl_errors::ErrorInfo::CERT_INVALID:
help_string = IDS_SSL_NONOVERRIDABLE_INVALID;
break;
default:
if (strict_enforcement_)
help_string = IDS_SSL_NONOVERRIDABLE_HSTS;
}
load_time_data->SetString("finalParagraph",
l10n_util::GetStringFUTF16(help_string, url));
}
// Set debugging information at the bottom of the warning.
load_time_data->SetString(
"subject", ssl_info_.cert->subject().GetDisplayName());
load_time_data->SetString(
"issuer", ssl_info_.cert->issuer().GetDisplayName());
load_time_data->SetString(
"expirationDate",
base::TimeFormatShortDate(ssl_info_.cert->valid_expiry()));
load_time_data->SetString(
"currentDate", base::TimeFormatShortDate(time_triggered_));
std::vector<std::string> encoded_chain;
ssl_info_.cert->GetPEMEncodedChain(
&encoded_chain);
load_time_data->SetString(
"pem", base::JoinString(encoded_chain, base::StringPiece()));
cert_report_helper_->PopulateExtendedReportingOption(load_time_data);
}
void SSLBlockingPage::OverrideEntry(NavigationEntry* entry) {
const int process_id = web_contents()->GetRenderProcessHost()->GetID();
const int cert_id = content::CertStore::GetInstance()->StoreCert(
ssl_info_.cert.get(), process_id);
DCHECK(cert_id);
content::SignedCertificateTimestampStore* sct_store(
content::SignedCertificateTimestampStore::GetInstance());
content::SignedCertificateTimestampIDStatusList sct_ids;
for (const auto& sct_and_status : ssl_info_.signed_certificate_timestamps) {
const int sct_id(sct_store->Store(sct_and_status.sct.get(), process_id));
DCHECK(sct_id);
sct_ids.push_back(content::SignedCertificateTimestampIDAndStatus(
sct_id, sct_and_status.status));
}
entry->GetSSL() =
content::SSLStatus(content::SECURITY_STYLE_AUTHENTICATION_BROKEN, cert_id,
sct_ids, ssl_info_);
}
void SSLBlockingPage::SetSSLCertReporterForTesting(
scoped_ptr<SSLCertReporter> ssl_cert_reporter) {
cert_report_helper_->SetSSLCertReporterForTesting(ssl_cert_reporter.Pass());
}
// This handles the commands sent from the interstitial JavaScript.
// DO NOT reorder or change this logic without also changing the JavaScript!
void SSLBlockingPage::CommandReceived(const std::string& command) {
if (command == "\"pageLoadComplete\"") {
// content::WaitForRenderFrameReady sends this message when the page
// load completes. Ignore it.
return;
}
int cmd = 0;
bool retval = base::StringToInt(command, &cmd);
DCHECK(retval);
switch (cmd) {
case CMD_DONT_PROCEED: {
interstitial_page()->DontProceed();
break;
}
case CMD_PROCEED: {
if (danger_overridable_) {
interstitial_page()->Proceed();
}
break;
}
case CMD_DO_REPORT: {
SetReportingPreference(true);
break;
}
case CMD_DONT_REPORT: {
SetReportingPreference(false);
break;
}
case CMD_SHOW_MORE_SECTION: {
metrics_helper()->RecordUserInteraction(
security_interstitials::MetricsHelper::SHOW_ADVANCED);
break;
}
case CMD_OPEN_HELP_CENTER: {
metrics_helper()->RecordUserInteraction(
security_interstitials::MetricsHelper::SHOW_LEARN_MORE);
content::NavigationController::LoadURLParams help_page_params(
google_util::AppendGoogleLocaleParam(
GURL(kHelpURL), g_browser_process->GetApplicationLocale()));
web_contents()->GetController().LoadURLWithParams(help_page_params);
break;
}
case CMD_RELOAD: {
metrics_helper()->RecordUserInteraction(
security_interstitials::MetricsHelper::RELOAD);
// The interstitial can't refresh itself.
web_contents()->GetController().Reload(true);
break;
}
case CMD_OPEN_REPORTING_PRIVACY:
OpenExtendedReportingPrivacyPolicy();
break;
case CMD_OPEN_DATE_SETTINGS:
case CMD_OPEN_DIAGNOSTIC:
// Commands not supported by the SSL interstitial.
NOTREACHED() << "Unexpected command: " << command;
}
}
void SSLBlockingPage::OverrideRendererPrefs(
content::RendererPreferences* prefs) {
Profile* profile = Profile::FromBrowserContext(
web_contents()->GetBrowserContext());
renderer_preferences_util::UpdateFromSystemSettings(
prefs, profile, web_contents());
}
void SSLBlockingPage::OnProceed() {
metrics_helper()->RecordUserDecision(
security_interstitials::MetricsHelper::PROCEED);
// Finish collecting information about invalid certificates, if the
// user opted in to.
cert_report_helper_->FinishCertCollection(
certificate_reporting::ErrorReport::USER_PROCEEDED);
RecordSSLExpirationPageEventState(
expired_but_previously_allowed_, true, overridable_);
// Accepting the certificate resumes the loading of the page.
NotifyAllowCertificate();
}
void SSLBlockingPage::OnDontProceed() {
metrics_helper()->RecordUserDecision(
security_interstitials::MetricsHelper::DONT_PROCEED);
// Finish collecting information about invalid certificates, if the
// user opted in to.
cert_report_helper_->FinishCertCollection(
certificate_reporting::ErrorReport::USER_DID_NOT_PROCEED);
RecordSSLExpirationPageEventState(
expired_but_previously_allowed_, false, overridable_);
NotifyDenyCertificate();
}
void SSLBlockingPage::NotifyDenyCertificate() {
// It's possible that callback_ may not exist if the user clicks "Proceed"
// followed by pressing the back button before the interstitial is hidden.
// In that case the certificate will still be treated as allowed.
if (callback_.is_null())
return;
callback_.Run(false);
callback_.Reset();
}
void SSLBlockingPage::NotifyAllowCertificate() {
DCHECK(!callback_.is_null());
callback_.Run(true);
callback_.Reset();
}
std::string SSLBlockingPage::GetUmaHistogramPrefix() const {
return overridable_ ? "ssl_overridable" : "ssl_nonoverridable";
}
std::string SSLBlockingPage::GetSamplingEventName() const {
std::string event_name(kEventNameBase);
if (overridable_)
event_name.append(kEventOverridable);
else
event_name.append(kEventNotOverridable);
event_name.append(net::ErrorToString(cert_error_));
return event_name;
}
// static
bool SSLBlockingPage::IsOverridable(int options_mask,
const Profile* const profile) {
const bool is_overridable =
(options_mask & SSLBlockingPage::OVERRIDABLE) &&
!(options_mask & SSLBlockingPage::STRICT_ENFORCEMENT) &&
profile->GetPrefs()->GetBoolean(prefs::kSSLErrorOverrideAllowed);
return is_overridable;
}
// static
bool SSLBlockingPage::DoesPolicyAllowDangerOverride(
const Profile* const profile) {
return profile->GetPrefs()->GetBoolean(prefs::kSSLErrorOverrideAllowed);
}