// 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/build_time.h" #include "base/command_line.h" #include "base/i18n/rtl.h" #include "base/i18n/time_formatting.h" #include "base/metrics/field_trial.h" #include "base/metrics/histogram.h" #include "base/process/launch.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/history/history_service_factory.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/renderer_preferences_util.h" #include "chrome/browser/ssl/ssl_error_classification.h" #include "chrome/browser/ssl/ssl_error_info.h" #include "chrome/common/chrome_switches.h" #include "chrome/grit/chromium_strings.h" #include "chrome/grit/generated_resources.h" #include "components/google/core/browser/google_util.h" #include "content/public/browser/cert_store.h" #include "content/public/browser/interstitial_page.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/web_contents.h" #include "content/public/common/ssl_status.h" #include "grit/browser_resources.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" #include "ui/base/resource/resource_bundle.h" #include "ui/base/webui/jstemplate_builder.h" #include "ui/base/webui/web_ui_util.h" #if defined(ENABLE_EXTENSIONS) #include "chrome/browser/extensions/api/experience_sampling_private/experience_sampling.h" #endif #if defined(OS_WIN) #include "base/base_paths_win.h" #include "base/path_service.h" #include "base/strings/string16.h" #include "base/win/windows_version.h" #endif #if defined(OS_CHROMEOS) #include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/ui/chrome_pages.h" #include "chrome/common/url_constants.h" #endif using base::ASCIIToUTF16; using base::TimeTicks; using content::InterstitialPage; using content::NavigationController; using content::NavigationEntry; #if defined(ENABLE_EXTENSIONS) using extensions::ExperienceSamplingEvent; #endif namespace { // URL for help page. const char kHelpURL[] = "https://support.google.com/chrome/answer/4454607"; // Constants for the Experience Sampling instrumentation. #if defined(ENABLE_EXTENSIONS) const char kEventNameBase[] = "ssl_interstitial_"; const char kEventNotOverridable[] = "notoverridable_"; const char kEventOverridable[] = "overridable_"; #endif // Events for UMA. Do not reorder or change! enum SSLBlockingPageEvent { SHOW_ALL, SHOW_OVERRIDABLE, PROCEED_OVERRIDABLE, PROCEED_NAME, PROCEED_DATE, PROCEED_AUTHORITY, DONT_PROCEED_OVERRIDABLE, DONT_PROCEED_NAME, DONT_PROCEED_DATE, DONT_PROCEED_AUTHORITY, MORE, SHOW_UNDERSTAND, // Used by the summer 2013 Finch trial. Deprecated. SHOW_INTERNAL_HOSTNAME, PROCEED_INTERNAL_HOSTNAME, SHOW_NEW_SITE, PROCEED_NEW_SITE, PROCEED_MANUAL_NONOVERRIDABLE, // Captive Portal errors moved to ssl_error_classification. DEPRECATED_CAPTIVE_PORTAL_DETECTION_ENABLED, DEPRECATED_CAPTIVE_PORTAL_DETECTION_ENABLED_OVERRIDABLE, DEPRECATED_CAPTIVE_PORTAL_PROBE_COMPLETED, DEPRECATED_CAPTIVE_PORTAL_PROBE_COMPLETED_OVERRIDABLE, DEPRECATED_CAPTIVE_PORTAL_NO_RESPONSE, DEPRECATED_CAPTIVE_PORTAL_NO_RESPONSE_OVERRIDABLE, DEPRECATED_CAPTIVE_PORTAL_DETECTED, DEPRECATED_CAPTIVE_PORTAL_DETECTED_OVERRIDABLE, UNUSED_BLOCKING_PAGE_EVENT, }; // 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, }; void RecordSSLBlockingPageEventStats(SSLBlockingPageEvent event) { UMA_HISTOGRAM_ENUMERATION("interstitial.ssl", event, UNUSED_BLOCKING_PAGE_EVENT); } 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); } } void RecordSSLBlockingPageDetailedStats(bool proceed, int cert_error, bool overridable, bool internal, int num_visits, bool expired_but_previously_allowed) { UMA_HISTOGRAM_ENUMERATION("interstitial.ssl_error_type", SSLErrorInfo::NetErrorToErrorType(cert_error), SSLErrorInfo::END_OF_ENUM); RecordSSLExpirationPageEventState( expired_but_previously_allowed, proceed, overridable); if (!overridable) { if (proceed) { RecordSSLBlockingPageEventStats(PROCEED_MANUAL_NONOVERRIDABLE); } // Overridable is false if the user didn't have any option except to turn // back. If that's the case, don't record some of the metrics. return; } if (num_visits == 0) RecordSSLBlockingPageEventStats(SHOW_NEW_SITE); if (proceed) { RecordSSLBlockingPageEventStats(PROCEED_OVERRIDABLE); if (internal) RecordSSLBlockingPageEventStats(PROCEED_INTERNAL_HOSTNAME); if (num_visits == 0) RecordSSLBlockingPageEventStats(PROCEED_NEW_SITE); } else if (!proceed) { RecordSSLBlockingPageEventStats(DONT_PROCEED_OVERRIDABLE); } SSLErrorInfo::ErrorType type = SSLErrorInfo::NetErrorToErrorType(cert_error); switch (type) { case SSLErrorInfo::CERT_COMMON_NAME_INVALID: { if (proceed) RecordSSLBlockingPageEventStats(PROCEED_NAME); else RecordSSLBlockingPageEventStats(DONT_PROCEED_NAME); break; } case SSLErrorInfo::CERT_DATE_INVALID: { if (proceed) RecordSSLBlockingPageEventStats(PROCEED_DATE); else RecordSSLBlockingPageEventStats(DONT_PROCEED_DATE); break; } case SSLErrorInfo::CERT_AUTHORITY_INVALID: { if (proceed) RecordSSLBlockingPageEventStats(PROCEED_AUTHORITY); else RecordSSLBlockingPageEventStats(DONT_PROCEED_AUTHORITY); break; } default: { break; } } } void LaunchDateAndTimeSettings() { #if defined(OS_CHROMEOS) std::string sub_page = std::string(chrome::kSearchSubPage) + "#" + l10n_util::GetStringUTF8(IDS_OPTIONS_SETTINGS_SECTION_TITLE_DATETIME); chrome::ShowSettingsSubPageForProfile( ProfileManager::GetActiveUserProfile(), sub_page); return; #elif defined(OS_ANDROID) CommandLine command(base::FilePath("/system/bin/am")); command.AppendArg("start"); command.AppendArg( "'com.android.settings/.Settings$DateTimeSettingsActivity'"); #elif defined(OS_IOS) // Apparently, iOS really does not have a way to launch the date and time // settings. Weird. TODO(palmer): Do something more graceful than ignoring // the user's click! crbug.com/394993 return; #elif defined(OS_LINUX) struct ClockCommand { const char* pathname; const char* argument; }; static const ClockCommand kClockCommands[] = { // GNOME // // NOTE: On old Ubuntu, naming control panels doesn't work, so it // opens the overview. This will have to be good enough. { "/usr/bin/gnome-control-center", "datetime" }, { "/usr/local/bin/gnome-control-center", "datetime" }, { "/opt/bin/gnome-control-center", "datetime" }, // KDE { "/usr/bin/kcmshell4", "clock" }, { "/usr/local/bin/kcmshell4", "clock" }, { "/opt/bin/kcmshell4", "clock" }, }; CommandLine command(base::FilePath("")); for (size_t i = 0; i < arraysize(kClockCommands); ++i) { base::FilePath pathname(kClockCommands[i].pathname); if (base::PathExists(pathname)) { command.SetProgram(pathname); command.AppendArg(kClockCommands[i].argument); break; } } if (command.GetProgram().empty()) { // Alas, there is nothing we can do. return; } #elif defined(OS_MACOSX) CommandLine command(base::FilePath("/usr/bin/open")); command.AppendArg("/System/Library/PreferencePanes/DateAndTime.prefPane"); #elif defined(OS_WIN) base::FilePath path; PathService::Get(base::DIR_SYSTEM, &path); static const base::char16 kControlPanelExe[] = L"control.exe"; path = path.Append(base::string16(kControlPanelExe)); CommandLine command(path); command.AppendArg(std::string("/name")); command.AppendArg(std::string("Microsoft.DateAndTime")); #else return; #endif #if !defined(OS_CHROMEOS) base::LaunchOptions options; options.wait = false; #if defined(OS_LINUX) options.allow_new_privs = true; #endif base::LaunchProcess(command, options, NULL); #endif } bool IsErrorDueToBadClock(const base::Time& now, int error) { if (SSLErrorInfo::NetErrorToErrorType(error) != SSLErrorInfo::CERT_DATE_INVALID) { return false; } return SSLErrorClassification::IsUserClockInThePast(now) || SSLErrorClassification::IsUserClockInTheFuture(now); } } // namespace // 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::Callback& callback) : callback_(callback), web_contents_(web_contents), cert_error_(cert_error), ssl_info_(ssl_info), request_url_(request_url), overridable_(options_mask & OVERRIDABLE && !(options_mask & STRICT_ENFORCEMENT)), strict_enforcement_((options_mask & STRICT_ENFORCEMENT) != 0), interstitial_page_(NULL), internal_(false), num_visits_(-1), expired_but_previously_allowed_( (options_mask & EXPIRED_BUT_PREVIOUSLY_ALLOWED) != 0) { Profile* profile = Profile::FromBrowserContext( web_contents->GetBrowserContext()); // For UMA stats. if (net::IsHostnameNonUnique(request_url_.HostNoBrackets())) internal_ = true; RecordSSLBlockingPageEventStats(SHOW_ALL); if (overridable_) { RecordSSLBlockingPageEventStats(SHOW_OVERRIDABLE); if (internal_) RecordSSLBlockingPageEventStats(SHOW_INTERNAL_HOSTNAME); HistoryService* history_service = HistoryServiceFactory::GetForProfile( profile, Profile::EXPLICIT_ACCESS); if (history_service) { history_service->GetVisibleVisitCountToHost( request_url_, base::Bind(&SSLBlockingPage::OnGotHistoryCount, base::Unretained(this)), &request_tracker_); } } ssl_error_classification_.reset(new SSLErrorClassification( web_contents_, base::Time::NowFromSystemTime(), request_url_, cert_error_, *ssl_info_.cert.get())); ssl_error_classification_->RecordUMAStatistics(overridable_); #if defined(ENABLE_CAPTIVE_PORTAL_DETECTION) ssl_error_classification_->RecordCaptivePortalUMAStatistics(overridable_); #endif #if defined(ENABLE_EXTENSIONS) // ExperienceSampling: Set up new sampling event for this interstitial. std::string event_name(kEventNameBase); if (overridable_ && !strict_enforcement_) event_name.append(kEventOverridable); else event_name.append(kEventNotOverridable); event_name.append(net::ErrorToString(cert_error_)); sampling_event_.reset(new ExperienceSamplingEvent( event_name, request_url_, web_contents_->GetLastCommittedURL(), web_contents_->GetBrowserContext())); #endif // Creating an interstitial without showing (e.g. from chrome://interstitials) // it leaks memory, so don't create it here. } SSLBlockingPage::~SSLBlockingPage() { // InvalidCommonNameSeverityScore() and InvalidDateSeverityScore() are in the // destructor because they depend on knowing whether captive portal detection // happened before the user made a decision. SSLErrorInfo::ErrorType type = SSLErrorInfo::NetErrorToErrorType(cert_error_); switch (type) { case SSLErrorInfo::CERT_DATE_INVALID: ssl_error_classification_->InvalidDateSeverityScore(); break; case SSLErrorInfo::CERT_COMMON_NAME_INVALID: ssl_error_classification_->InvalidCommonNameSeverityScore(); break; default: break; } if (!callback_.is_null()) { RecordSSLBlockingPageDetailedStats(false, cert_error_, overridable_, internal_, num_visits_, expired_but_previously_allowed_); // The page is closed without the user having chosen what to do, default to // deny. NotifyDenyCertificate(); } } void SSLBlockingPage::Show() { DCHECK(!interstitial_page_); interstitial_page_ = InterstitialPage::Create( web_contents_, true, request_url_, this); interstitial_page_->Show(); } std::string SSLBlockingPage::GetHTMLContents() { base::DictionaryValue load_time_data; base::string16 url(ASCIIToUTF16(request_url_.host())); if (base::i18n::IsRTL()) base::i18n::WrapStringWithLTRFormatting(&url); webui::SetFontAndTextDirection(&load_time_data); // Shared values for both the overridable and non-overridable versions. load_time_data.SetString("type", "SSL"); load_time_data.SetBoolean("overridable", overridable_); load_time_data.SetString( "tabTitle", l10n_util::GetStringUTF16(IDS_SSL_V2_TITLE)); load_time_data.SetString( "heading", l10n_util::GetStringUTF16(IDS_SSL_V2_HEADING)); base::Time now = base::Time::NowFromSystemTime(); bool bad_clock = IsErrorDueToBadClock(now, cert_error_); if (bad_clock) { load_time_data.SetString("primaryParagraph", l10n_util::GetStringFUTF16( IDS_SSL_CLOCK_ERROR, url, base::TimeFormatShortDate(now))); } else { load_time_data.SetString( "primaryParagraph", l10n_util::GetStringFUTF16(IDS_SSL_V2_PRIMARY_PARAGRAPH, url)); } load_time_data.SetString( "openDetails", l10n_util::GetStringUTF16(IDS_SSL_V2_OPEN_DETAILS_BUTTON)); load_time_data.SetString( "closeDetails", l10n_util::GetStringUTF16(IDS_SSL_V2_CLOSE_DETAILS_BUTTON)); load_time_data.SetString("errorCode", net::ErrorToString(cert_error_)); if (overridable_) { SSLErrorInfo error_info = SSLErrorInfo::CreateError( SSLErrorInfo::NetErrorToErrorType(cert_error_), ssl_info_.cert.get(), request_url_); if (bad_clock) { load_time_data.SetString("explanationParagraph", l10n_util::GetStringFUTF16( IDS_SSL_CLOCK_ERROR_EXPLANATION, url)); } else { 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 { SSLErrorInfo::ErrorType type = SSLErrorInfo::NetErrorToErrorType(cert_error_); if (type == SSLErrorInfo::CERT_INVALID && SSLErrorClassification:: MaybeWindowsLacksSHA256Support()) { load_time_data.SetString( "explanationParagraph", l10n_util::GetStringFUTF16( IDS_SSL_NONOVERRIDABLE_MORE_INVALID_SP3, url)); } else if (bad_clock) { load_time_data.SetString("explanationParagraph", l10n_util::GetStringFUTF16( IDS_SSL_CLOCK_ERROR_EXPLANATION, 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_NONOVERRIDABLE_RELOAD_BUTTON)); // 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 SSLErrorInfo::CERT_REVOKED: help_string = IDS_SSL_NONOVERRIDABLE_REVOKED; break; case SSLErrorInfo::CERT_PINNED_KEY_MISSING: help_string = IDS_SSL_NONOVERRIDABLE_PINNED; break; case SSLErrorInfo::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(now)); std::vector encoded_chain; ssl_info_.cert->GetPEMEncodedChain(&encoded_chain); load_time_data.SetString("pem", JoinString(encoded_chain, std::string())); base::StringPiece html( ResourceBundle::GetSharedInstance().GetRawDataResource( IRD_SECURITY_INTERSTITIAL_HTML)); return webui::GetI18nTemplateHtml(html, &load_time_data); } void SSLBlockingPage::OverrideEntry(NavigationEntry* entry) { int cert_id = content::CertStore::GetInstance()->StoreCert( ssl_info_.cert.get(), web_contents_->GetRenderProcessHost()->GetID()); DCHECK(cert_id); entry->GetSSL().security_style = content::SECURITY_STYLE_AUTHENTICATION_BROKEN; entry->GetSSL().cert_id = cert_id; entry->GetSSL().cert_status = ssl_info_.cert_status; entry->GetSSL().security_bits = ssl_info_.security_bits; } // This handles the commands sent from the interstitial JavaScript. They are // defined in chrome/browser/resources/ssl/ssl_errors_common.js. // DO NOT reorder or change this logic without also changing the JavaScript! void SSLBlockingPage::CommandReceived(const std::string& command) { int cmd = 0; bool retval = base::StringToInt(command, &cmd); DCHECK(retval); switch (cmd) { case CMD_DONT_PROCEED: { interstitial_page_->DontProceed(); break; } case CMD_PROCEED: { interstitial_page_->Proceed(); break; } case CMD_MORE: { RecordSSLBlockingPageEventStats(MORE); #if defined(ENABLE_EXTENSIONS) if (sampling_event_.get()) sampling_event_->set_has_viewed_details(true); #endif break; } case CMD_RELOAD: { // The interstitial can't refresh itself. web_contents_->GetController().Reload(true); break; } case CMD_HELP: { content::NavigationController::LoadURLParams help_page_params( google_util::AppendGoogleLocaleParam( GURL(kHelpURL), g_browser_process->GetApplicationLocale())); #if defined(ENABLE_EXTENSIONS) if (sampling_event_.get()) sampling_event_->set_has_viewed_learn_more(true); #endif web_contents_->GetController().LoadURLWithParams(help_page_params); break; } case CMD_CLOCK: { LaunchDateAndTimeSettings(); break; } default: { NOTREACHED(); } } } void SSLBlockingPage::OverrideRendererPrefs( content::RendererPreferences* prefs) { Profile* profile = Profile::FromBrowserContext( web_contents_->GetBrowserContext()); renderer_preferences_util::UpdateFromSystemSettings(prefs, profile); } void SSLBlockingPage::OnProceed() { RecordSSLBlockingPageDetailedStats(true, cert_error_, overridable_, internal_, num_visits_, expired_but_previously_allowed_); #if defined(ENABLE_EXTENSIONS) // ExperienceSampling: Notify that user decided to proceed. if (sampling_event_.get()) sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kProceed); #endif // Accepting the certificate resumes the loading of the page. NotifyAllowCertificate(); } void SSLBlockingPage::OnDontProceed() { RecordSSLBlockingPageDetailedStats(false, cert_error_, overridable_, internal_, num_visits_, expired_but_previously_allowed_); #if defined(ENABLE_EXTENSIONS) // ExperienceSampling: Notify that user decided to not proceed. // This also occurs if the user navigates away or closes the tab. if (sampling_event_.get()) sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kDeny); #endif 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(); } // static void SSLBlockingPage::SetExtraInfo( base::DictionaryValue* strings, const std::vector& extra_info) { DCHECK_LT(extra_info.size(), 5U); // We allow 5 paragraphs max. const char* keys[5] = { "moreInfo1", "moreInfo2", "moreInfo3", "moreInfo4", "moreInfo5" }; int i; for (i = 0; i < static_cast(extra_info.size()); i++) { strings->SetString(keys[i], extra_info[i]); } for (; i < 5; i++) { strings->SetString(keys[i], std::string()); } } void SSLBlockingPage::OnGotHistoryCount(bool success, int num_visits, base::Time first_visit) { num_visits_ = num_visits; }