Commit 40cbb16d authored by Chris Thompson's avatar Chris Thompson Committed by Commit Bot

Add legacy TLS interstitial on iOS

This adds support for triggering the legacy TLS interstitial on iOS, for
iOS 14 or higher where WebKit exposes the |shouldAllowDeprecatedTLS|
method. This is behind an off-by-default feature flag
|kIOSLegacyTLSInterstitial|. (Without this change, in iOS14 legacy TLS
connections will default to being allowed in WKWebView; with this change
but with the feature disabled will be the same behavior.) This new
interstitial roughly follows the same code structure as the iOS
lookalike interstitial.

The new method |shouldAllowDeprecatedTLS| checks if the domain has been
allowlisted (by the user clicking through the interstitial) and if not
sets an error for the navigation and tells WebKit to cancel. The error
is then handled via the standard error page flow, with a new check for
the net::ERR_SSL_OBSOLETE_VERSION error.

Because CRWWKNavigationHandler can't depend on security_interstitals,
this adds a delegate method to the WebClient interface called
|IsLegacyTLSAllowedForHost|, which in turn looks up the host in the
LegacyTLSTabAllowList from the WebState.

This also includes a new egtest suite for exercising legacy TLS
connections and the interstitial behavior.

This does not include downgrading the security indicator for sites that
use legacy TLS connections (after a user clicks through the
interstitial).

Bug: 1100647
Change-Id: Ia92e28bf52c0808875877322909fabdbaead3fa1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2325071Reviewed-by: default avatarEugene But <eugenebut@chromium.org>
Reviewed-by: default avatarMatt Mueller <mattm@chromium.org>
Reviewed-by: default avatarLivvie Lin <livvielin@chromium.org>
Commit-Queue: Christopher Thompson <cthomp@chromium.org>
Cr-Commit-Position: refs/heads/master@{#794757}
parent bf41f106
...@@ -2837,6 +2837,11 @@ ...@@ -2837,6 +2837,11 @@
"owners": [ "cthomp" ], "owners": [ "cthomp" ],
"expiry_milestone": 87 "expiry_milestone": 87
}, },
{
"name": "legacy-tls-interstitial",
"owners": [ "cthomp" ],
"expiry_milestone": 92
},
{ {
"name": "list-all-display-modes", "name": "list-all-display-modes",
"owners": [ "//ui/display/OWNERS" ], "owners": [ "//ui/display/OWNERS" ],
......
...@@ -67,6 +67,26 @@ void PopulateSSLDebuggingStrings(const net::SSLInfo ssl_info, ...@@ -67,6 +67,26 @@ void PopulateSSLDebuggingStrings(const net::SSLInfo ssl_info,
load_time_data->SetString("pem", base::StrCat(encoded_chain)); load_time_data->SetString("pem", base::StrCat(encoded_chain));
} }
void PopulateLegacyTLSStrings(base::DictionaryValue* load_time_data,
const base::string16& hostname) {
load_time_data->SetString("tabTitle",
l10n_util::GetStringUTF16(IDS_SSL_V2_TITLE));
load_time_data->SetString("heading",
l10n_util::GetStringUTF16(IDS_LEGACY_TLS_HEADING));
load_time_data->SetString(
"primaryButtonText",
l10n_util::GetStringUTF16(IDS_SSL_OVERRIDABLE_SAFETY_BUTTON));
load_time_data->SetString(
"primaryParagraph",
l10n_util::GetStringUTF16(IDS_LEGACY_TLS_PRIMARY_PARAGRAPH));
load_time_data->SetString(
"explanationParagraph",
l10n_util::GetStringUTF16(IDS_LEGACY_TLS_EXPLANATION));
load_time_data->SetString(
"finalParagraph", l10n_util::GetStringFUTF16(
IDS_SSL_OVERRIDABLE_PROCEED_PARAGRAPH, hostname));
}
} // namespace common_string_util } // namespace common_string_util
} // namespace security_interstitials } // namespace security_interstitials
...@@ -32,6 +32,11 @@ void PopulateSSLDebuggingStrings(const net::SSLInfo ssl_info, ...@@ -32,6 +32,11 @@ void PopulateSSLDebuggingStrings(const net::SSLInfo ssl_info,
// For determining whether to use the old or new icon sets. // For determining whether to use the old or new icon sets.
void PopulateNewIconStrings(base::DictionaryValue* load_time_data); void PopulateNewIconStrings(base::DictionaryValue* load_time_data);
// Fills in the details for a legacy TLS error. Abstracts the strings for
// access from ios/.
void PopulateLegacyTLSStrings(base::DictionaryValue* load_time_data,
const base::string16& hostname);
} // common_string_util } // common_string_util
} // namespace security_interstitials } // namespace security_interstitials
......
...@@ -680,6 +680,10 @@ const flags_ui::FeatureEntry kFeatureEntries[] = { ...@@ -680,6 +680,10 @@ const flags_ui::FeatureEntry kFeatureEntries[] = {
{"scroll-to-text-ios", flag_descriptions::kScrollToTextIOSName, {"scroll-to-text-ios", flag_descriptions::kScrollToTextIOSName,
flag_descriptions::kScrollToTextIOSDescription, flags_ui::kOsIos, flag_descriptions::kScrollToTextIOSDescription, flags_ui::kOsIos,
FEATURE_VALUE_TYPE(web::features::kScrollToTextIOS)}, FEATURE_VALUE_TYPE(web::features::kScrollToTextIOS)},
{"legacy-tls-interstitial",
flag_descriptions::kIOSLegacyTLSInterstitialsName,
flag_descriptions::kIOSLegacyTLSInterstitialsDescription, flags_ui::kOsIos,
FEATURE_VALUE_TYPE(web::features::kIOSLegacyTLSInterstitial)},
}; };
bool SkipConditionalFeatureEntry(const flags_ui::FeatureEntry& entry) { bool SkipConditionalFeatureEntry(const flags_ui::FeatureEntry& entry) {
......
...@@ -312,6 +312,12 @@ const char kInProductHelpDemoModeDescription[] = ...@@ -312,6 +312,12 @@ const char kInProductHelpDemoModeDescription[] =
"an individual promotion causes that promotion but no other promotions to " "an individual promotion causes that promotion but no other promotions to "
"occur."; "occur.";
const char kIOSLegacyTLSInterstitialsName[] = "Show legacy TLS interstitials";
const char kIOSLegacyTLSInterstitialsDescription[] =
"When enabled, an interstitial will be shown on main-frame navigations "
"that use legacy TLS connections, and subresources using legacy TLS "
"connections will be blocked.";
const char kIOSLookalikeUrlNavigationSuggestionsUIName[] = const char kIOSLookalikeUrlNavigationSuggestionsUIName[] =
"Lookalike URL Navigation Suggestions UI"; "Lookalike URL Navigation Suggestions UI";
const char kIOSLookalikeUrlNavigationSuggestionsUIDescription[] = const char kIOSLookalikeUrlNavigationSuggestionsUIDescription[] =
......
...@@ -268,6 +268,11 @@ extern const char kInfobarUIRebootOnlyiOS13Description[]; ...@@ -268,6 +268,11 @@ extern const char kInfobarUIRebootOnlyiOS13Description[];
extern const char kInProductHelpDemoModeName[]; extern const char kInProductHelpDemoModeName[];
extern const char kInProductHelpDemoModeDescription[]; extern const char kInProductHelpDemoModeDescription[];
// Title and description for the flag to enable interstitials on legacy TLS
// connections.
extern const char kIOSLegacyTLSInterstitialsName[];
extern const char kIOSLegacyTLSInterstitialsDescription[];
// Title and description for the flag to enable interstitials on lookalike // Title and description for the flag to enable interstitials on lookalike
// URL navigations. // URL navigations.
extern const char kIOSLookalikeUrlNavigationSuggestionsUIName[]; extern const char kIOSLookalikeUrlNavigationSuggestionsUIName[];
......
...@@ -111,6 +111,7 @@ source_set("tabs_internal") { ...@@ -111,6 +111,7 @@ source_set("tabs_internal") {
"//ios/chrome/browser/web_state_list", "//ios/chrome/browser/web_state_list",
"//ios/chrome/browser/web_state_list/web_usage_enabler", "//ios/chrome/browser/web_state_list/web_usage_enabler",
"//ios/components/security_interstitials", "//ios/components/security_interstitials",
"//ios/components/security_interstitials/legacy_tls",
"//ios/components/security_interstitials/lookalikes", "//ios/components/security_interstitials/lookalikes",
"//ios/public/provider/chrome/browser", "//ios/public/provider/chrome/browser",
"//ios/web/common:features", "//ios/web/common:features",
......
...@@ -78,6 +78,7 @@ ...@@ -78,6 +78,7 @@
#import "ios/chrome/browser/web/tab_id_tab_helper.h" #import "ios/chrome/browser/web/tab_id_tab_helper.h"
#import "ios/chrome/browser/web/web_state_delegate_tab_helper.h" #import "ios/chrome/browser/web/web_state_delegate_tab_helper.h"
#import "ios/components/security_interstitials/ios_blocking_page_tab_helper.h" #import "ios/components/security_interstitials/ios_blocking_page_tab_helper.h"
#import "ios/components/security_interstitials/legacy_tls/legacy_tls_tab_allow_list.h"
#import "ios/components/security_interstitials/lookalikes/lookalike_url_container.h" #import "ios/components/security_interstitials/lookalikes/lookalike_url_container.h"
#import "ios/components/security_interstitials/lookalikes/lookalike_url_tab_allow_list.h" #import "ios/components/security_interstitials/lookalikes/lookalike_url_tab_allow_list.h"
#import "ios/components/security_interstitials/lookalikes/lookalike_url_tab_helper.h" #import "ios/components/security_interstitials/lookalikes/lookalike_url_tab_helper.h"
...@@ -197,6 +198,10 @@ void AttachTabHelpers(web::WebState* web_state, bool for_prerender) { ...@@ -197,6 +198,10 @@ void AttachTabHelpers(web::WebState* web_state, bool for_prerender) {
LookalikeUrlContainer::CreateForWebState(web_state); LookalikeUrlContainer::CreateForWebState(web_state);
} }
if (base::FeatureList::IsEnabled(web::features::kIOSLegacyTLSInterstitial)) {
LegacyTLSTabAllowList::CreateForWebState(web_state);
}
// TODO(crbug.com/794115): pre-rendered WebState have lots of unnecessary // TODO(crbug.com/794115): pre-rendered WebState have lots of unnecessary
// tab helpers for historical reasons. For the moment, AttachTabHelpers // tab helpers for historical reasons. For the moment, AttachTabHelpers
// allows to inhibit the creation of some of them. Once PreloadController // allows to inhibit the creation of some of them. Once PreloadController
......
...@@ -262,8 +262,10 @@ source_set("web_internal") { ...@@ -262,8 +262,10 @@ source_set("web_internal") {
"//ios/chrome/browser/ui/util", "//ios/chrome/browser/ui/util",
"//ios/chrome/browser/web:feature_flags", "//ios/chrome/browser/web:feature_flags",
"//ios/components/security_interstitials", "//ios/components/security_interstitials",
"//ios/components/security_interstitials/legacy_tls",
"//ios/components/security_interstitials/lookalikes", "//ios/components/security_interstitials/lookalikes",
"//ios/components/webui:url_constants", "//ios/components/webui:url_constants",
"//ios/net",
"//ios/public/provider/chrome/browser", "//ios/public/provider/chrome/browser",
"//ios/public/provider/chrome/browser/voice", "//ios/public/provider/chrome/browser/voice",
"//ios/web", "//ios/web",
...@@ -335,7 +337,9 @@ source_set("unit_tests_internal") { ...@@ -335,7 +337,9 @@ source_set("unit_tests_internal") {
"//ios/chrome/browser/web", "//ios/chrome/browser/web",
"//ios/chrome/test/fakes", "//ios/chrome/test/fakes",
"//ios/components/security_interstitials", "//ios/components/security_interstitials",
"//ios/components/security_interstitials/legacy_tls",
"//ios/components/security_interstitials/lookalikes", "//ios/components/security_interstitials/lookalikes",
"//ios/net",
"//ios/web/common:features", "//ios/web/common:features",
"//ios/web/common:web_view_creation_util", "//ios/web/common:web_view_creation_util",
"//ios/web/public/test", "//ios/web/public/test",
...@@ -424,6 +428,7 @@ source_set("eg2_tests") { ...@@ -424,6 +428,7 @@ source_set("eg2_tests") {
"forms_egtest.mm", "forms_egtest.mm",
"http_auth_egtest.mm", "http_auth_egtest.mm",
"js_print_egtest.mm", "js_print_egtest.mm",
"legacy_tls_egtest.mm",
"lookalike_url_egtest.mm", "lookalike_url_egtest.mm",
"navigation_egtest.mm", "navigation_egtest.mm",
"progress_indicator_egtest.mm", "progress_indicator_egtest.mm",
......
...@@ -51,6 +51,8 @@ class ChromeWebClient : public web::WebClient { ...@@ -51,6 +51,8 @@ class ChromeWebClient : public web::WebClient {
bool overridable, bool overridable,
int64_t navigation_id, int64_t navigation_id,
const base::Callback<void(bool)>& callback) override; const base::Callback<void(bool)>& callback) override;
bool IsLegacyTLSAllowedForHost(web::WebState* web_state,
const std::string& hostname) override;
void PrepareErrorPage(web::WebState* web_state, void PrepareErrorPage(web::WebState* web_state,
const GURL& url, const GURL& url,
NSError* error, NSError* error,
......
...@@ -33,11 +33,15 @@ ...@@ -33,11 +33,15 @@
#import "ios/chrome/browser/web/error_page_util.h" #import "ios/chrome/browser/web/error_page_util.h"
#include "ios/chrome/browser/web/features.h" #include "ios/chrome/browser/web/features.h"
#import "ios/components/security_interstitials/ios_blocking_page_tab_helper.h" #import "ios/components/security_interstitials/ios_blocking_page_tab_helper.h"
#import "ios/components/security_interstitials/legacy_tls/legacy_tls_blocking_page.h"
#import "ios/components/security_interstitials/legacy_tls/legacy_tls_controller_client.h"
#import "ios/components/security_interstitials/legacy_tls/legacy_tls_tab_allow_list.h"
#import "ios/components/security_interstitials/lookalikes/lookalike_url_blocking_page.h" #import "ios/components/security_interstitials/lookalikes/lookalike_url_blocking_page.h"
#import "ios/components/security_interstitials/lookalikes/lookalike_url_container.h" #import "ios/components/security_interstitials/lookalikes/lookalike_url_container.h"
#import "ios/components/security_interstitials/lookalikes/lookalike_url_controller_client.h" #import "ios/components/security_interstitials/lookalikes/lookalike_url_controller_client.h"
#import "ios/components/security_interstitials/lookalikes/lookalike_url_error.h" #import "ios/components/security_interstitials/lookalikes/lookalike_url_error.h"
#include "ios/components/webui/web_ui_url_constants.h" #include "ios/components/webui/web_ui_url_constants.h"
#import "ios/net/protocol_handler_util.h"
#include "ios/public/provider/chrome/browser/browser_url_rewriter_provider.h" #include "ios/public/provider/chrome/browser/browser_url_rewriter_provider.h"
#import "ios/public/provider/chrome/browser/chrome_browser_provider.h" #import "ios/public/provider/chrome/browser/chrome_browser_provider.h"
#include "ios/public/provider/chrome/browser/chrome_browser_provider.h" #include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
...@@ -47,6 +51,7 @@ ...@@ -47,6 +51,7 @@
#include "ios/web/common/user_agent.h" #include "ios/web/common/user_agent.h"
#include "ios/web/public/navigation/browser_url_rewriter.h" #include "ios/web/public/navigation/browser_url_rewriter.h"
#include "ios/web/public/navigation/navigation_manager.h" #include "ios/web/public/navigation/navigation_manager.h"
#include "net/base/net_errors.h"
#include "net/http/http_util.h" #include "net/http/http_util.h"
#include "services/metrics/public/cpp/ukm_source_id.h" #include "services/metrics/public/cpp/ukm_source_id.h"
#include "ui/base/l10n/l10n_util.h" #include "ui/base/l10n/l10n_util.h"
...@@ -127,6 +132,23 @@ NSString* GetLookalikeUrlErrorPageHtml(web::WebState* web_state, ...@@ -127,6 +132,23 @@ NSString* GetLookalikeUrlErrorPageHtml(web::WebState* web_state,
return base::SysUTF8ToNSString(error_page_content); return base::SysUTF8ToNSString(error_page_content);
} }
// Returns the legacy TLS error page HTML.
NSString* GetLegacyTLSErrorPageHTML(web::WebState* web_state,
int64_t navigation_id) {
// Construct the blocking page and associate it with the WebState.
std::unique_ptr<security_interstitials::IOSSecurityInterstitialPage> page =
std::make_unique<LegacyTLSBlockingPage>(
web_state, web_state->GetVisibleURL() /*request_url*/,
std::make_unique<LegacyTLSControllerClient>(
web_state, web_state->GetVisibleURL(),
GetApplicationContext()->GetApplicationLocale()));
std::string error_page_content = page->GetHtmlContents();
security_interstitials::IOSBlockingPageTabHelper::FromWebState(web_state)
->AssociateBlockingPage(navigation_id, std::move(page));
return base::SysUTF8ToNSString(error_page_content);
}
// Returns a string describing the product name and version, of the // Returns a string describing the product name and version, of the
// form "productname/version". Used as part of the user agent string. // form "productname/version". Used as part of the user agent string.
std::string GetMobileProduct() { std::string GetMobileProduct() {
...@@ -293,6 +315,12 @@ void ChromeWebClient::AllowCertificateError( ...@@ -293,6 +315,12 @@ void ChromeWebClient::AllowCertificateError(
std::move(null_callback)); std::move(null_callback));
} }
bool ChromeWebClient::IsLegacyTLSAllowedForHost(web::WebState* web_state,
const std::string& hostname) {
return LegacyTLSTabAllowList::FromWebState(web_state)->IsDomainAllowed(
hostname);
}
void ChromeWebClient::PrepareErrorPage( void ChromeWebClient::PrepareErrorPage(
web::WebState* web_state, web::WebState* web_state,
const GURL& url, const GURL& url,
...@@ -335,6 +363,10 @@ void ChromeWebClient::PrepareErrorPage( ...@@ -335,6 +363,10 @@ void ChromeWebClient::PrepareErrorPage(
DCHECK_EQ(kLookalikeUrlErrorCode, final_underlying_error.code); DCHECK_EQ(kLookalikeUrlErrorCode, final_underlying_error.code);
std::move(error_html_callback) std::move(error_html_callback)
.Run(GetLookalikeUrlErrorPageHtml(web_state, navigation_id)); .Run(GetLookalikeUrlErrorPageHtml(web_state, navigation_id));
} else if ([final_underlying_error.domain isEqual:net::kNSErrorDomain] &&
final_underlying_error.code == net::ERR_SSL_OBSOLETE_VERSION) {
std::move(error_html_callback)
.Run(GetLegacyTLSErrorPageHTML(web_state, navigation_id));
} else if (info.has_value()) { } else if (info.has_value()) {
base::OnceCallback<void(bool)> proceed_callback; base::OnceCallback<void(bool)> proceed_callback;
base::OnceCallback<void(NSString*)> blocking_page_callback = base::OnceCallback<void(NSString*)> blocking_page_callback =
......
...@@ -33,6 +33,7 @@ ...@@ -33,6 +33,7 @@
#import "ios/components/security_interstitials/ios_blocking_page_tab_helper.h" #import "ios/components/security_interstitials/ios_blocking_page_tab_helper.h"
#import "ios/components/security_interstitials/lookalikes/lookalike_url_container.h" #import "ios/components/security_interstitials/lookalikes/lookalike_url_container.h"
#import "ios/components/security_interstitials/lookalikes/lookalike_url_error.h" #import "ios/components/security_interstitials/lookalikes/lookalike_url_error.h"
#import "ios/net/protocol_handler_util.h"
#include "ios/web/common/features.h" #include "ios/web/common/features.h"
#import "ios/web/common/web_view_creation_util.h" #import "ios/web/common/web_view_creation_util.h"
#import "ios/web/public/test/error_test_util.h" #import "ios/web/public/test/error_test_util.h"
...@@ -40,6 +41,7 @@ ...@@ -40,6 +41,7 @@
#import "ios/web/public/test/fakes/test_web_state.h" #import "ios/web/public/test/fakes/test_web_state.h"
#import "ios/web/public/test/js_test_util.h" #import "ios/web/public/test/js_test_util.h"
#include "ios/web/public/test/scoped_testing_web_client.h" #include "ios/web/public/test/scoped_testing_web_client.h"
#include "net/base/net_errors.h"
#include "net/http/http_status_code.h" #include "net/http/http_status_code.h"
#include "net/ssl/ssl_info.h" #include "net/ssl/ssl_info.h"
#include "net/test/cert_test_util.h" #include "net/test/cert_test_util.h"
...@@ -483,6 +485,41 @@ TEST_F(ChromeWebClientTest, PrepareErrorPageForLookalikeUrlErrorNoSuggestion) { ...@@ -483,6 +485,41 @@ TEST_F(ChromeWebClientTest, PrepareErrorPageForLookalikeUrlErrorNoSuggestion) {
<< base::SysNSStringToUTF8(page); << base::SysNSStringToUTF8(page);
} }
// Tests PrepareErrorPage for a legacy TLS error, which results in a
// committed legacy TLS interstitial.
TEST_F(ChromeWebClientTest, PrepareErrorPageForLegacyTLSError) {
web::TestWebState web_state;
web_state.SetBrowserState(browser_state());
security_interstitials::IOSBlockingPageTabHelper::CreateForWebState(
&web_state);
auto navigation_manager = std::make_unique<web::TestNavigationManager>();
web_state.SetNavigationManager(std::move(navigation_manager));
NSError* error = [NSError errorWithDomain:net::kNSErrorDomain
code:net::ERR_SSL_OBSOLETE_VERSION
userInfo:nil];
__block bool callback_called = false;
__block NSString* page = nil;
base::OnceCallback<void(NSString*)> callback =
base::BindOnce(^(NSString* error_html) {
callback_called = true;
page = error_html;
});
ChromeWebClient web_client;
web_client.PrepareErrorPage(&web_state, GURL(kTestUrl), error,
/*is_post=*/false,
/*is_off_the_record=*/false,
/*info=*/base::Optional<net::SSLInfo>(),
/*navigation_id=*/0, std::move(callback));
EXPECT_TRUE(callback_called);
NSString* error_string =
l10n_util::GetNSString(IDS_LEGACY_TLS_PRIMARY_PARAGRAPH);
EXPECT_TRUE([page containsString:error_string])
<< base::SysNSStringToUTF8(page);
}
// Tests the default user agent for different views. // Tests the default user agent for different views.
TEST_F(ChromeWebClientTest, DefaultUserAgent) { TEST_F(ChromeWebClientTest, DefaultUserAgent) {
if (@available(iOS 13, *)) { if (@available(iOS 13, *)) {
......
// Copyright 2020 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 "base/ios/ios_util.h"
#include "components/strings/grit/components_strings.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
#import "ios/chrome/test/earl_grey/chrome_test_case.h"
#import "ios/testing/earl_grey/earl_grey_test.h"
#include "ios/testing/embedded_test_server_handlers.h"
#include "ios/web/common/features.h"
#include "net/ssl/ssl_config.h"
#include "net/ssl/ssl_server_config.h"
#include "net/test/embedded_test_server/default_handlers.h"
#include "ui/base/l10n/l10n_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface LegacyTLSTestCase : ChromeTestCase {
// A test server that connects via TLS 1.0.
std::unique_ptr<net::test_server::EmbeddedTestServer> _legacyTLSServer;
// A URL for the legacy TLS test server, which should trigger legacy TLS
// interstitials.
GURL _legacyTLSURL;
// A URL for the normal test server.
GURL _safeURL;
// Text that is found on the legacy TLS interstitial.
std::string _interstitialContent;
// Text that is found on |_legacyTLSURL| if the user clicks through.
std::string _unsafeContent;
// Text that is found on |_safeURL|.
std::string _safeContent;
}
@end
@implementation LegacyTLSTestCase
- (AppLaunchConfiguration)appConfigurationForTestCase {
AppLaunchConfiguration config;
config.features_enabled.push_back(web::features::kSSLCommittedInterstitials);
config.features_enabled.push_back(web::features::kIOSLegacyTLSInterstitial);
config.relaunch_policy = NoForceRelaunchAndResetState;
return config;
}
- (void)setUp {
[super setUp];
// Setup server that will negotiate TLS 1.0.
_legacyTLSServer = std::make_unique<net::test_server::EmbeddedTestServer>(
net::test_server::EmbeddedTestServer::TYPE_HTTPS);
net::SSLServerConfig ssl_config;
ssl_config.version_min = net::SSL_PROTOCOL_VERSION_TLS1;
ssl_config.version_max = net::SSL_PROTOCOL_VERSION_TLS1;
_legacyTLSServer->SetSSLConfig(net::EmbeddedTestServer::CERT_OK, ssl_config);
RegisterDefaultHandlers(_legacyTLSServer.get());
GREYAssertTrue(_legacyTLSServer->Start(),
@"Legacy TLS test server failed to start.");
_legacyTLSURL = _legacyTLSServer->GetURL("/");
_interstitialContent =
l10n_util::GetStringUTF8(IDS_LEGACY_TLS_PRIMARY_PARAGRAPH);
// The legacy TLS server will cause self-signed cert warnings after we click
// through the legacy TLS interstitial, so pull the heading string for that
// to match on.
_unsafeContent = l10n_util::GetStringUTF8(IDS_SSL_V2_HEADING);
RegisterDefaultHandlers(self.testServer);
GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");
_safeURL = self.testServer->GetURL("/defaultresponse");
_safeContent = "Default response";
}
// On iOS < 14, the legacy TLS interstitial should not be shown.
- (void)testLegacyTLSInterstitialNotShownOnOldVersions {
if (base::ios::IsRunningOnIOS14OrLater()) {
return;
}
[ChromeEarlGrey loadURL:_legacyTLSURL];
[ChromeEarlGrey waitForWebStateContainingText:_unsafeContent];
}
// The remaining tests in this file are only relevant for iOS 14 or later.
// Test that loading a page from a server over TLS 1.0 causes the legacy TLS
// interstitial to show.
- (void)testLegacyTLSShowsInterstitial {
if (!base::ios::IsRunningOnIOS14OrLater()) {
return;
}
[ChromeEarlGrey loadURL:_legacyTLSURL];
[ChromeEarlGrey waitForWebStateContainingText:_interstitialContent];
}
// Test that going back to safety returns the user to the previous page.
- (void)testLegacyTLSInterstitialBackToSafety {
if (!base::ios::IsRunningOnIOS14OrLater()) {
return;
}
// Load a benign page first.
[ChromeEarlGrey loadURL:_safeURL];
[ChromeEarlGrey waitForWebStateContainingText:_safeContent];
// Trigger the legacy TLS interstitial.
[ChromeEarlGrey loadURL:_legacyTLSURL];
[ChromeEarlGrey waitForWebStateContainingText:_interstitialContent];
// Tap on the "Back to safety" button and verify that we navigate back to the
// previous page.
[ChromeEarlGrey tapWebStateElementWithID:@"primary-button"];
[ChromeEarlGrey waitForWebStateContainingText:_safeContent];
}
// Test that clicking through the interstitial works.
- (void)testLegacyTLSInterstitialProceed {
if (!base::ios::IsRunningOnIOS14OrLater()) {
return;
}
// Load a benign page first.
[ChromeEarlGrey loadURL:_safeURL];
[ChromeEarlGrey waitForWebStateContainingText:_safeContent];
// Trigger the legacy TLS interstitial.
[ChromeEarlGrey loadURL:_legacyTLSURL];
[ChromeEarlGrey waitForWebStateContainingText:_interstitialContent];
// Tap on the "Proceed" link and verify that we go to the unsafe page (in this
// case, a cert error interstitial for the test server's self-signed cert).
[ChromeEarlGrey tapWebStateElementWithID:@"details-button"];
[ChromeEarlGrey tapWebStateElementWithID:@"proceed-link"];
[ChromeEarlGrey waitForWebStateContainingText:_unsafeContent];
}
// Test that clicking through the interstitial is remembered on a reload, and
// we don't show the interstitial again.
- (void)testLegacyTLSInterstitialAllowlist {
if (!base::ios::IsRunningOnIOS14OrLater()) {
return;
}
// Trigger the legacy TLS interstitial.
[ChromeEarlGrey loadURL:_legacyTLSURL];
[ChromeEarlGrey waitForWebStateContainingText:_interstitialContent];
// Tap on the "Proceed" link and verify that we go to the unsafe page.
[ChromeEarlGrey tapWebStateElementWithID:@"details-button"];
[ChromeEarlGrey tapWebStateElementWithID:@"proceed-link"];
[ChromeEarlGrey waitForWebStateContainingText:_unsafeContent];
// Navigate away to another page.
[ChromeEarlGrey loadURL:_safeURL];
[ChromeEarlGrey waitForWebStateContainingText:_safeContent];
// Navigate to the legacy TLS page again. Legacy TLS interstitial should not
// be shown.
[ChromeEarlGrey loadURL:_legacyTLSURL];
[ChromeEarlGrey waitForWebStateContainingText:_unsafeContent];
}
// Test that the allowlist is cleared after session restart and that we show the
// legacy TLS interstitial again.
- (void)testLegacyTLSInterstitialAllowlistClearedOnRestart {
if (!base::ios::IsRunningOnIOS14OrLater()) {
return;
}
// Trigger the legacy TLS interstitial.
[ChromeEarlGrey loadURL:_legacyTLSURL];
[ChromeEarlGrey waitForWebStateContainingText:_interstitialContent];
// Tap on the "Proceed" link and verify that we go to the unsafe page.
[ChromeEarlGrey tapWebStateElementWithID:@"details-button"];
[ChromeEarlGrey tapWebStateElementWithID:@"proceed-link"];
[ChromeEarlGrey waitForWebStateContainingText:_unsafeContent];
// Navigate away to another page.
[ChromeEarlGrey loadURL:_safeURL];
[ChromeEarlGrey waitForWebStateContainingText:_safeContent];
// Do a session restoration.
[ChromeEarlGrey triggerRestoreViaTabGridRemoveAllUndo];
[ChromeEarlGrey waitForWebStateContainingText:_safeContent];
// Navigate to the legacy TLS page again. The interstitial should trigger.
[ChromeEarlGrey loadURL:_legacyTLSURL];
[ChromeEarlGrey waitForWebStateContainingText:_interstitialContent];
}
@end
# Copyright 2020 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.
source_set("legacy_tls") {
configs += [ "//build/config/compiler:enable_arc" ]
sources = [
"legacy_tls_blocking_page.h",
"legacy_tls_blocking_page.mm",
"legacy_tls_controller_client.h",
"legacy_tls_controller_client.mm",
"legacy_tls_tab_allow_list.h",
"legacy_tls_tab_allow_list.mm",
]
deps = [
"//base",
"//components/security_interstitials/core",
"//components/strings:components_strings_grit",
"//ios/components/security_interstitials",
"//ios/web/public",
"//net",
"//ui/base",
]
}
include_rules = [
"+ios/net",
"+net/base",
]
// Copyright 2020 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.
#ifndef IOS_COMPONENTS_SECURITY_INTERSTITIALS_LEGACY_TLS_LEGACY_TLS_BLOCKING_PAGE_H_
#define IOS_COMPONENTS_SECURITY_INTERSTITIALS_LEGACY_TLS_LEGACY_TLS_BLOCKING_PAGE_H_
#include "ios/components/security_interstitials/ios_security_interstitial_page.h"
#include "ios/components/security_interstitials/legacy_tls/legacy_tls_controller_client.h"
class GURL;
// This class is responsible for showing/hiding the interstitial page that is
// shown for legacy TLS connections.
class LegacyTLSBlockingPage
: public security_interstitials::IOSSecurityInterstitialPage {
public:
~LegacyTLSBlockingPage() override;
// Creates a legacy TLS blocking page.
LegacyTLSBlockingPage(web::WebState* web_state,
const GURL& request_url,
std::unique_ptr<LegacyTLSControllerClient> client);
protected:
// SecurityInterstitialPage implementation:
bool ShouldCreateNewNavigation() const override;
void PopulateInterstitialStrings(
base::DictionaryValue* load_time_data) const override;
private:
void HandleScriptCommand(const base::DictionaryValue& message,
const GURL& origin_url,
bool user_is_interacting,
web::WebFrame* sender_frame) override;
void AfterShow() override;
web::WebState* web_state_ = nullptr;
const GURL request_url_;
std::unique_ptr<LegacyTLSControllerClient> controller_;
DISALLOW_COPY_AND_ASSIGN(LegacyTLSBlockingPage);
};
#endif // IOS_COMPONENTS_SECURITY_INTERSTITIALS_LEGACY_TLS_LEGACY_TLS_BLOCKING_PAGE_H_
// Copyright 2020 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.
#import "ios/components/security_interstitials/legacy_tls/legacy_tls_blocking_page.h"
#include <utility>
#include "base/strings/string_number_conversions.h"
#include "base/values.h"
#include "components/security_interstitials/core/common_string_util.h"
#include "components/security_interstitials/core/metrics_helper.h"
#include "ios/components/security_interstitials/ios_blocking_page_controller_client.h"
#include "ios/components/security_interstitials/ios_blocking_page_metrics_helper.h"
#include "net/base/net_errors.h"
#include "ui/base/l10n/l10n_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
LegacyTLSBlockingPage::LegacyTLSBlockingPage(
web::WebState* web_state,
const GURL& request_url,
std::unique_ptr<LegacyTLSControllerClient> client)
: security_interstitials::IOSSecurityInterstitialPage(web_state,
request_url,
client.get()),
web_state_(web_state),
request_url_(request_url),
controller_(std::move(client)) {
DCHECK(web_state_);
// Creating an interstitial without showing it (e.g. from
// chrome://interstitials) leaks memory, so don't create it here.
}
LegacyTLSBlockingPage::~LegacyTLSBlockingPage() = default;
bool LegacyTLSBlockingPage::ShouldCreateNewNavigation() const {
return true;
}
void LegacyTLSBlockingPage::PopulateInterstitialStrings(
base::DictionaryValue* load_time_data) const {
CHECK(load_time_data);
// Shared with SSL errors.
security_interstitials::common_string_util::PopulateSSLLayoutStrings(
net::ERR_SSL_OBSOLETE_VERSION, load_time_data);
load_time_data->SetBoolean("overridable", true);
load_time_data->SetBoolean("hide_primary_button", false);
load_time_data->SetBoolean("bad_clock", false);
load_time_data->SetString("type", "LEGACY_TLS");
const base::string16 hostname(
security_interstitials::common_string_util::GetFormattedHostName(
request_url_));
security_interstitials::common_string_util::PopulateLegacyTLSStrings(
load_time_data, hostname);
}
void LegacyTLSBlockingPage::HandleScriptCommand(
const base::DictionaryValue& message,
const GURL& origin_url,
bool user_is_interacting,
web::WebFrame* sender_frame) {
std::string command_string;
if (!message.GetString("command", &command_string)) {
LOG(ERROR) << "JS message parameter not found: command";
return;
}
// Remove the command prefix so that the string value can be converted to a
// SecurityInterstitialCommand enum value.
std::size_t delimiter = command_string.find(".");
if (delimiter == std::string::npos) {
return;
}
// Parse the command int value from the text after the delimiter.
int command = 0;
if (!base::StringToInt(command_string.substr(delimiter + 1), &command)) {
NOTREACHED() << "Command cannot be parsed to an int : " << command_string;
return;
}
if (command == security_interstitials::CMD_DONT_PROCEED) {
controller_->metrics_helper()->RecordUserDecision(
security_interstitials::MetricsHelper::DONT_PROCEED);
controller_->GoBack();
} else if (command == security_interstitials::CMD_PROCEED) {
controller_->metrics_helper()->RecordUserDecision(
security_interstitials::MetricsHelper::PROCEED);
controller_->Proceed();
}
}
void LegacyTLSBlockingPage::AfterShow() {}
// Copyright 2020 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.
#ifndef IOS_COMPONENTS_SECURITY_INTERSTITIALS_LEGACY_TLS_LEGACY_TLS_CONTROLLER_CLIENT_H_
#define IOS_COMPONENTS_SECURITY_INTERSTITIALS_LEGACY_TLS_LEGACY_TLS_CONTROLLER_CLIENT_H_
#include "ios/components/security_interstitials/ios_blocking_page_controller_client.h"
#include "url/gurl.h"
class GURL;
namespace web {
class WebState;
} // namespace web
// Controller client used for legacy TLS blocking pages.
class LegacyTLSControllerClient
: public security_interstitials::IOSBlockingPageControllerClient {
public:
LegacyTLSControllerClient(web::WebState* web_state,
const GURL& request_url,
const std::string& app_locale);
~LegacyTLSControllerClient() override;
// security_interstitials::ControllerClient:
void Proceed() override;
private:
// The URL of the page causing the insterstitial.
const GURL request_url_;
};
#endif // IOS_COMPONENTS_SECURITY_INTERSTITIALS_LEGACY_TLS_LEGACY_TLS_CONTROLLER_CLIENT_H_
// Copyright 2020 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.
#import "ios/components/security_interstitials/legacy_tls/legacy_tls_controller_client.h"
#include "components/security_interstitials/core/metrics_helper.h"
#include "ios/components/security_interstitials/ios_blocking_page_metrics_helper.h"
#include "ios/components/security_interstitials/legacy_tls/legacy_tls_tab_allow_list.h"
#import "ios/web/public/web_state.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
// Creates a metrics helper for |url|.
std::unique_ptr<security_interstitials::IOSBlockingPageMetricsHelper>
CreateMetricsHelper(web::WebState* web_state, const GURL& url) {
security_interstitials::MetricsHelper::ReportDetails reporting_info;
reporting_info.metric_prefix = "legacy_tls";
return std::make_unique<security_interstitials::IOSBlockingPageMetricsHelper>(
web_state, url, reporting_info);
}
} // namespace
LegacyTLSControllerClient::LegacyTLSControllerClient(
web::WebState* web_state,
const GURL& request_url,
const std::string& app_locale)
: IOSBlockingPageControllerClient(
web_state,
CreateMetricsHelper(web_state, request_url),
app_locale),
request_url_(request_url) {}
LegacyTLSControllerClient::~LegacyTLSControllerClient() {}
void LegacyTLSControllerClient::Proceed() {
LegacyTLSTabAllowList::FromWebState(web_state())
->AllowDomain(request_url_.host());
Reload();
}
// Copyright 2020 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.
#ifndef IOS_COMPONENTS_SECURITY_INTERSTITIALS_LEGACY_TLS_LEGACY_TLS_TAB_ALLOW_LIST_H_
#define IOS_COMPONENTS_SECURITY_INTERSTITIALS_LEGACY_TLS_LEGACY_TLS_TAB_ALLOW_LIST_H_
#include <set>
#include <string>
#import "ios/web/public/web_state_user_data.h"
// LegacyTLSTabAllowList tracks the allowlist decisions for legacy TLS hosts.
// Decisions are scoped to the domain.
class LegacyTLSTabAllowList
: public web::WebStateUserData<LegacyTLSTabAllowList> {
public:
// LegacyTLSTabAllowList is move-only.
LegacyTLSTabAllowList(LegacyTLSTabAllowList&& other);
LegacyTLSTabAllowList& operator=(LegacyTLSTabAllowList&& other);
~LegacyTLSTabAllowList() override;
// Returns whether |domain| has been allowlisted.
bool IsDomainAllowed(const std::string& domain) const;
// Allows future navigations to |domain|.
void AllowDomain(const std::string& domain);
private:
explicit LegacyTLSTabAllowList(web::WebState* web_state);
friend class web::WebStateUserData<LegacyTLSTabAllowList>;
WEB_STATE_USER_DATA_KEY_DECL();
// Set of allowlisted domains.
std::set<std::string> allowed_domains_;
};
#endif // IOS_COMPONENTS_SECURITY_INTERSTITIALS_LEGACY_TLS_LEGACY_TLS_TAB_ALLOW_LIST_H_
// Copyright 2020 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.
#import "ios/components/security_interstitials/legacy_tls/legacy_tls_tab_allow_list.h"
#import "ios/web/public/web_state.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
WEB_STATE_USER_DATA_KEY_IMPL(LegacyTLSTabAllowList)
LegacyTLSTabAllowList::LegacyTLSTabAllowList(web::WebState* web_state) {}
LegacyTLSTabAllowList::LegacyTLSTabAllowList(LegacyTLSTabAllowList&& other) =
default;
LegacyTLSTabAllowList& LegacyTLSTabAllowList::operator=(
LegacyTLSTabAllowList&& other) = default;
LegacyTLSTabAllowList::~LegacyTLSTabAllowList() = default;
bool LegacyTLSTabAllowList::IsDomainAllowed(const std::string& domain) const {
return allowed_domains_.find(domain) != allowed_domains_.end();
}
void LegacyTLSTabAllowList::AllowDomain(const std::string& domain) {
allowed_domains_.insert(domain);
}
...@@ -63,6 +63,9 @@ extern const base::Feature kAddWebContentDropInteraction; ...@@ -63,6 +63,9 @@ extern const base::Feature kAddWebContentDropInteraction;
// See also: https://wicg.github.io/scroll-to-text-fragment/ // See also: https://wicg.github.io/scroll-to-text-fragment/
extern const base::Feature kScrollToTextIOS; extern const base::Feature kScrollToTextIOS;
// When enabled, display an interstitial on legacy TLS connections.
extern const base::Feature kIOSLegacyTLSInterstitial;
// When true, for each navigation, the default user agent is chosen by the // When true, for each navigation, the default user agent is chosen by the
// WebClient GetDefaultUserAgent() method. If it is false, the mobile version // WebClient GetDefaultUserAgent() method. If it is false, the mobile version
// is requested by default. // is requested by default.
......
...@@ -50,6 +50,8 @@ const base::Feature kAddWebContentDropInteraction{ ...@@ -50,6 +50,8 @@ const base::Feature kAddWebContentDropInteraction{
const base::Feature kScrollToTextIOS{"ScrollToTextIOS", const base::Feature kScrollToTextIOS{"ScrollToTextIOS",
base::FEATURE_DISABLED_BY_DEFAULT}; base::FEATURE_DISABLED_BY_DEFAULT};
const base::Feature kIOSLegacyTLSInterstitial{
"IOSLegacyTLSInterstitial", base::FEATURE_DISABLED_BY_DEFAULT};
bool UseWebClientDefaultUserAgent() { bool UseWebClientDefaultUserAgent() {
if (@available(iOS 13, *)) { if (@available(iOS 13, *)) {
......
...@@ -35,6 +35,7 @@ source_set("navigation") { ...@@ -35,6 +35,7 @@ source_set("navigation") {
"//ios/web/web_state/ui:crw_web_view_navigation_proxy", "//ios/web/web_state/ui:crw_web_view_navigation_proxy",
"//ios/web/web_state/ui:web_view_handler", "//ios/web/web_state/ui:web_view_handler",
"//ios/web/web_view:util", "//ios/web/web_view:util",
"//net",
"//ui/base", "//ui/base",
"//url", "//url",
] ]
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "base/strings/sys_string_conversions.h" #include "base/strings/sys_string_conversions.h"
#include "base/timer/timer.h" #include "base/timer/timer.h"
#import "ios/net/http_response_headers_util.h" #import "ios/net/http_response_headers_util.h"
#import "ios/net/protocol_handler_util.h"
#include "ios/web/common/features.h" #include "ios/web/common/features.h"
#import "ios/web/common/url_scheme_util.h" #import "ios/web/common/url_scheme_util.h"
#import "ios/web/js_messaging/crw_js_injector.h" #import "ios/web/js_messaging/crw_js_injector.h"
...@@ -39,6 +40,7 @@ ...@@ -39,6 +40,7 @@
#import "ios/web/web_view/error_translation_util.h" #import "ios/web/web_view/error_translation_util.h"
#import "ios/web/web_view/wk_web_view_util.h" #import "ios/web/web_view/wk_web_view_util.h"
#import "net/base/mac/url_conversions.h" #import "net/base/mac/url_conversions.h"
#include "net/base/net_errors.h"
#include "net/cert/x509_util_ios.h" #include "net/cert/x509_util_ios.h"
#include "url/gurl.h" #include "url/gurl.h"
...@@ -1214,6 +1216,37 @@ void ReportOutOfSyncURLInDidStartProvisionalNavigation( ...@@ -1214,6 +1216,37 @@ void ReportOutOfSyncURLInDidStartProvisionalNavigation(
}]; }];
} }
- (void)webView:(WKWebView*)webView
authenticationChallenge:(NSURLAuthenticationChallenge*)challenge
shouldAllowDeprecatedTLS:(void (^)(BOOL))decisionHandler
API_AVAILABLE(ios(14)) {
[self didReceiveWKNavigationDelegateCallback];
DCHECK(challenge);
DCHECK(decisionHandler);
// If the legacy TLS interstitial is not enabled, don't cause errors.
if (!base::FeatureList::IsEnabled(web::features::kIOSLegacyTLSInterstitial)) {
decisionHandler(YES);
return;
}
if (web::GetWebClient()->IsLegacyTLSAllowedForHost(
self.webStateImpl,
base::SysNSStringToUTF8(challenge.protectionSpace.host))) {
decisionHandler(YES);
return;
}
if (self.pendingNavigationInfo) {
self.pendingNavigationInfo.cancelled = YES;
self.pendingNavigationInfo.cancellationError =
[NSError errorWithDomain:net::kNSErrorDomain
code:net::ERR_SSL_OBSOLETE_VERSION
userInfo:nil];
}
decisionHandler(NO);
}
- (void)webViewWebContentProcessDidTerminate:(WKWebView*)webView { - (void)webViewWebContentProcessDidTerminate:(WKWebView*)webView {
[self didReceiveWKNavigationDelegateCallback]; [self didReceiveWKNavigationDelegateCallback];
......
...@@ -163,6 +163,12 @@ class WebClient { ...@@ -163,6 +163,12 @@ class WebClient {
int64_t navigation_id, int64_t navigation_id,
const base::Callback<void(bool)>& callback); const base::Callback<void(bool)>& callback);
// Allows the embedder to specify legacy TLS enforcement on a per-host basis,
// for example to allow users to bypass interstitial warnings on affected
// hosts.
virtual bool IsLegacyTLSAllowedForHost(WebState* web_state,
const std::string& hostname);
// Calls the given |callback| with the contents of an error page to display // Calls the given |callback| with the contents of an error page to display
// when a navigation error occurs. |error| is always a valid pointer. The // when a navigation error occurs. |error| is always a valid pointer. The
// string passed to |callback| will be nil if no error page should be // string passed to |callback| will be nil if no error page should be
......
...@@ -96,6 +96,11 @@ void WebClient::AllowCertificateError( ...@@ -96,6 +96,11 @@ void WebClient::AllowCertificateError(
callback.Run(false); callback.Run(false);
} }
bool WebClient::IsLegacyTLSAllowedForHost(WebState* web_state,
const std::string& hostname) {
return false;
}
void WebClient::PrepareErrorPage(WebState* web_state, void WebClient::PrepareErrorPage(WebState* web_state,
const GURL& url, const GURL& url,
NSError* error, NSError* error,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment