blob: cd5edb148a749ebeb771fcd6c2ad87a1507f7160 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ssl/https_only_mode_navigation_throttle.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_functions.h"
#include "base/time/time.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ssl/https_only_mode_tab_helper.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/security_interstitials/content/security_interstitial_tab_helper.h"
#include "components/security_interstitials/core/https_only_mode_metrics.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents.h"
#include "net/base/net_errors.h"
using security_interstitials::https_only_mode::Event;
using security_interstitials::https_only_mode::RecordHttpsFirstModeNavigation;
namespace {
// Time that the throttle will wait before canceling the upgraded navigation and
// showing the HTTPS-Only Mode interstitial.
base::TimeDelta g_fallback_delay = base::Seconds(3);
} // namespace
// static
std::unique_ptr<HttpsOnlyModeNavigationThrottle>
HttpsOnlyModeNavigationThrottle::MaybeCreateThrottleFor(
content::NavigationHandle* handle,
std::unique_ptr<SecurityBlockingPageFactory> blocking_page_factory,
PrefService* prefs) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// HTTPS-Only Mode is only relevant for primary main-frame HTTP(S)
// navigations.
if (!handle->GetURL().SchemeIsHTTPOrHTTPS() ||
!handle->IsInPrimaryMainFrame() || handle->IsSameDocument()) {
return nullptr;
}
if (!base::FeatureList::IsEnabled(features::kHttpsOnlyMode) || !prefs ||
!prefs->GetBoolean(prefs::kHttpsOnlyModeEnabled)) {
return nullptr;
}
// Ensure that the HttpsOnlyModeTabHelper has been created (this does nothing
// if it has already been created for the WebContents). There are cases where
// the tab helper won't get created by the initialization in
// chrome/browser/ui/tab_helpers.cc but the criteria for adding the throttle
// are still met (see crbug.com/1233889 for one example).
HttpsOnlyModeTabHelper::CreateForWebContents(handle->GetWebContents());
return std::make_unique<HttpsOnlyModeNavigationThrottle>(
handle, std::move(blocking_page_factory));
}
HttpsOnlyModeNavigationThrottle::HttpsOnlyModeNavigationThrottle(
content::NavigationHandle* handle,
std::unique_ptr<SecurityBlockingPageFactory> blocking_page_factory)
: content::NavigationThrottle(handle),
blocking_page_factory_(std::move(blocking_page_factory)) {}
HttpsOnlyModeNavigationThrottle::~HttpsOnlyModeNavigationThrottle() = default;
// Called if there is a non-OK net::Error in the completion status.
content::NavigationThrottle::ThrottleCheckResult
HttpsOnlyModeNavigationThrottle::WillFailRequest() {
auto* handle = navigation_handle();
// If there was no certificate error, SSLInfo will be empty.
const net::SSLInfo info = handle->GetSSLInfo().value_or(net::SSLInfo());
int cert_status = info.cert_status;
if (!net::IsCertStatusError(cert_status) &&
handle->GetNetErrorCode() == net::OK) {
// Don't fallback.
return content::NavigationThrottle::PROCEED;
}
// Only show the interstitial if the Interceptor attempted to upgrade the
// navigation.
auto* contents = handle->GetWebContents();
auto* tab_helper = HttpsOnlyModeTabHelper::FromWebContents(contents);
if (tab_helper->is_navigation_upgraded()) {
// Record failure type metrics for upgraded navigations.
RecordHttpsFirstModeNavigation(
Event::kUpgradeFailed,
security_interstitials::https_only_mode::HttpInterstitialState{});
if (net::IsCertificateError(handle->GetNetErrorCode())) {
RecordHttpsFirstModeNavigation(
Event::kUpgradeCertError,
security_interstitials::https_only_mode::HttpInterstitialState{});
} else if (handle->GetNetErrorCode() == net::ERR_TIMED_OUT) {
RecordHttpsFirstModeNavigation(
Event::kUpgradeTimedOut,
security_interstitials::https_only_mode::HttpInterstitialState{});
} else {
RecordHttpsFirstModeNavigation(
Event::kUpgradeNetError,
security_interstitials::https_only_mode::HttpInterstitialState{});
}
std::unique_ptr<security_interstitials::HttpsOnlyModeBlockingPage>
blocking_page = blocking_page_factory_->CreateHttpsOnlyModeBlockingPage(
contents, handle->GetURL(),
security_interstitials::https_only_mode::HttpInterstitialState());
std::string interstitial_html = blocking_page->GetHTMLContents();
security_interstitials::SecurityInterstitialTabHelper::
AssociateBlockingPage(handle, std::move(blocking_page));
return content::NavigationThrottle::ThrottleCheckResult(
content::NavigationThrottle::CANCEL, net::ERR_BLOCKED_BY_CLIENT,
interstitial_html);
}
return content::NavigationThrottle::PROCEED;
}
content::NavigationThrottle::ThrottleCheckResult
HttpsOnlyModeNavigationThrottle::WillRedirectRequest() {
// If the navigation was upgraded by the Interceptor, then the Throttle's
// WillRedirectRequest() will get triggered by the artificial redirect to
// HTTPS. The HTTPS upgrade will always happen after the Throttle's
// WillStartRequest() (which is just a no-op PROCEED), so tracking upgraded
// requests is deferred to WillRedirectRequest() here. Which navigations to
// upgrade is determined by the Interceptor, not the Throttle.
//
// The navigation may get upgraded at various points during redirects:
// 1. The Interceptor serves an artificial redirect to HTTPS if the
// navigation is upgraded. This means the Throttle will see the upgraded
// navigation state for the first time here in WillRedirectRequest().
// 2. HTTPS->HTTP downgrades can occur later in the lifecycle of a
// navigation, and will also result in the Interceptor serving an
// artificial redirect to upgrade the navigation.
//
// HTTPS->HTTP downgrades may result in net::ERR_TOO_MANY_REDIRECTS, but these
// redirect loops should hit the cache and not cost too much. If they go too
// long, the fallback timer will kick in. ERR_TOO_MANY_REDIRECTS should result
// in the request failing and triggering fallback. Alternately, the
// Interceptor could log URLs seen and bail if it encounters a redirect loop,
// but it is simpler to rely on existing handling unless the optimization is
// needed.
auto* tab_helper = HttpsOnlyModeTabHelper::FromWebContents(
navigation_handle()->GetWebContents());
if (tab_helper->is_navigation_upgraded()) {
// Check if the timer is already started, as there may be additional
// redirects on the navigation after the artificial upgrade redirect.
bool timer_started =
navigation_handle()->SetNavigationTimeout(g_fallback_delay);
if (timer_started) {
RecordHttpsFirstModeNavigation(
Event::kUpgradeAttempted,
security_interstitials::https_only_mode::HttpInterstitialState{});
}
}
return content::NavigationThrottle::PROCEED;
}
content::NavigationThrottle::ThrottleCheckResult
HttpsOnlyModeNavigationThrottle::WillProcessResponse() {
// Clear the status for this navigation as it will successfully commit.
auto* tab_helper = HttpsOnlyModeTabHelper::FromWebContents(
navigation_handle()->GetWebContents());
if (tab_helper->is_navigation_upgraded()) {
RecordHttpsFirstModeNavigation(
Event::kUpgradeSucceeded,
security_interstitials::https_only_mode::HttpInterstitialState{});
tab_helper->set_is_navigation_upgraded(false);
}
return content::NavigationThrottle::PROCEED;
}
const char* HttpsOnlyModeNavigationThrottle::GetNameForLogging() {
return "HttpsOnlyModeNavigationThrottle";
}
// static
void HttpsOnlyModeNavigationThrottle::set_timeout_for_testing(
int timeout_in_seconds) {
g_fallback_delay = base::Seconds(timeout_in_seconds);
}