blob: 93193d1cfc281c2c8c1a8d6326c5ad2d8d7b1107 [file]
/*
* Copyright (C) 2025 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#import "HTTPServer.h"
#import "PlatformUtilities.h"
#import "Test.h"
#import "TestCocoa.h"
#import "TestNavigationDelegate.h"
#import "TestUIDelegate.h"
#import "TestWKWebView.h"
#import "WKWebViewConfigurationExtras.h"
#import <WebCore/SQLiteDatabase.h>
#import <WebCore/SQLiteStatement.h>
#import <WebKit/WKFoundation.h>
#import <WebKit/WKFrameInfoPrivate.h>
#import <WebKit/WKHTTPCookieStorePrivate.h>
#import <WebKit/WKHistoryDelegatePrivate.h>
#import <WebKit/WKPreferencesPrivate.h>
#import <WebKit/WKProcessPoolPrivate.h>
#import <WebKit/WKWebViewConfigurationPrivate.h>
#import <WebKit/WKWebViewPrivate.h>
#import <WebKit/WKWebViewPrivateForTesting.h>
#import <WebKit/WKWebpagePreferencesPrivate.h>
#import <WebKit/WKWebsiteDataStorePrivate.h>
#import <WebKit/WebKit.h>
#import <WebKit/_WKFeature.h>
#import <WebKit/_WKFrameTreeNode.h>
#import <WebKit/_WKProcessPoolConfiguration.h>
#import <WebKit/_WKWebsiteDataStoreConfiguration.h>
#import <wtf/Assertions.h>
#import <wtf/Function.h>
#import <wtf/RetainPtr.h>
#import <wtf/text/MakeString.h>
using namespace TestWebKitAPI;
#if !PLATFORM(IOS)
@interface TestUIDelegate (EnhancedSecurityExtras)
- (NSArray *)waitForAlertWithEnhancedSecurity;
@end
@implementation TestUIDelegate (EnhancedSecurityExtras)
- (NSArray *)waitForAlertWithEnhancedSecurity
{
EXPECT_FALSE(self.runJavaScriptAlertPanelWithMessage);
__block bool finished = false;
__block RetainPtr<NSString> alertMessage;
__block RetainPtr<NSString> childFrameVariant;
self.runJavaScriptAlertPanelWithMessage = ^(WKWebView *webView, NSString *message, WKFrameInfo *frameInfo, void (^completionHandler)(void)) {
alertMessage = message;
childFrameVariant = [webView _webContentProcessVariantForFrame:frameInfo._handle];
finished = true;
completionHandler();
};
TestWebKitAPI::Util::run(&finished);
self.runJavaScriptAlertPanelWithMessage = nil;
BOOL enhancedSecurityEnabled = [childFrameVariant isEqualToString:@"security"];
NSArray *result = @[alertMessage.autorelease(), [NSNumber numberWithBool:enhancedSecurityEnabled]];
return result;
}
@end
@interface WKWebView (EnhancedSecurityExtras)
- (NSArray *)_test_waitForAlertWithEnhancedSecurity;
@end
@implementation WKWebView (EnhancedSecurityExtras)
- (NSArray *)_test_waitForAlertWithEnhancedSecurity
{
EXPECT_FALSE(self.UIDelegate);
auto uiDelegate = adoptNS([TestUIDelegate new]);
self.UIDelegate = uiDelegate.get();
NSArray *result = [uiDelegate waitForAlertWithEnhancedSecurity];
self.UIDelegate = nil;
return result;
}
@end
static uint64_t observerCallbacks;
static RetainPtr<WKHTTPCookieStore> globalCookieStore;
@interface EnhancedSecurityCookieObserver : NSObject<WKHTTPCookieStoreObserver>
- (void)cookiesDidChangeInCookieStore:(WKHTTPCookieStore *)cookieStore;
@end
@implementation EnhancedSecurityCookieObserver
- (void)cookiesDidChangeInCookieStore:(WKHTTPCookieStore *)cookieStore
{
ASSERT_EQ(cookieStore, globalCookieStore.get());
++observerCallbacks;
}
@end
@interface EnhancedSecurityHistoryDelegate : NSObject <WKHistoryDelegatePrivate> {
@public
RetainPtr<WKNavigationData> lastNavigationData;
RetainPtr<NSString> lastUpdatedTitle;
RetainPtr<NSURL> lastRedirectSource;
RetainPtr<NSURL> lastRedirectDestination;
}
@end
@implementation EnhancedSecurityHistoryDelegate
- (void)_webView:(WKWebView *)webView didNavigateWithNavigationData:(WKNavigationData *)navigationData
{
lastNavigationData = navigationData;
}
- (void)_webView:(WKWebView *)webView didUpdateHistoryTitle:(NSString *)title forURL:(NSURL *)URL
{
lastUpdatedTitle = title;
}
- (void)_webView:(WKWebView *)webView didPerformServerRedirectFromURL:(NSURL *)sourceURL toURL:(NSURL *)destinationURL
{
lastRedirectSource = sourceURL;
lastRedirectDestination = destinationURL;
}
@end
static void testAlertWithEnhancedSecurity(RetainPtr<TestUIDelegate> uiDelegate, String message, BOOL enhancedSecurityEnabled)
{
NSArray *result = [uiDelegate waitForAlertWithEnhancedSecurity];
EXPECT_WK_STREQ(result[0], message);
EXPECT_EQ([result[1] boolValue], enhancedSecurityEnabled);
}
static RetainPtr<TestWKWebView> enhancedSecurityTestConfiguration(
const TestWebKitAPI::HTTPServer* plaintextServer,
const TestWebKitAPI::HTTPServer* secureServer = nullptr,
bool useSiteIsolation = false,
bool useNonPersistentStore = true)
{
auto configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
[configuration preferences].javaScriptCanOpenWindowsAutomatically = YES;
auto preferences = [configuration preferences];
for (_WKFeature *feature in [WKPreferences _features]) {
if ((useSiteIsolation && [feature.key isEqualToString:@"SiteIsolationEnabled"])
|| [feature.key isEqualToString:@"EnhancedSecurityHeuristicsEnabled"]) {
[preferences _setEnabled:YES forFeature:feature];
}
}
auto storeConfiguration = useNonPersistentStore
? adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration])
: adoptNS([_WKWebsiteDataStoreConfiguration new]);
if (plaintextServer)
[storeConfiguration setHTTPProxy:[NSURL URLWithString:[NSString stringWithFormat:@"http://127.0.0.1:%d/", plaintextServer->port()]]];
if (secureServer)
[storeConfiguration setHTTPSProxy:[NSURL URLWithString:[NSString stringWithFormat:@"https://127.0.0.1:%d/", secureServer->port()]]];
[configuration setWebsiteDataStore:adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]).get()];
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 1, 1) configuration:configuration]);
if (secureServer) {
auto navigationDelegate = [TestNavigationDelegate new];
[navigationDelegate allowAnyTLSCertificate];
[webView setNavigationDelegate: navigationDelegate];
}
return webView;
}
enum class ExpectedEnhancedSecurity : bool { Disabled = false, Enabled = true };
static void runActionAndCheckEnhancedSecurityAlerts(
RetainPtr<TestWKWebView> webView,
Function<void()>&& performAction,
std::initializer_list<std::pair<String, ExpectedEnhancedSecurity>> alerts)
{
RELEASE_ASSERT(!webView.get().UIDelegate);
__block auto uiDelegate = adoptNS([TestUIDelegate new]);
[webView setUIDelegate:uiDelegate.get()];
__block auto navigationDelegate = [webView navigationDelegate];
__block RetainPtr<TestWKWebView> createdWebView;
uiDelegate.get().createWebViewWithConfiguration = ^(WKWebViewConfiguration *configuration, WKNavigationAction *action, WKWindowFeatures *windowFeatures) {
createdWebView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
createdWebView.get().UIDelegate = uiDelegate.get();
createdWebView.get().navigationDelegate = navigationDelegate;
return createdWebView.get();
};
performAction();
for (auto& pair : alerts)
testAlertWithEnhancedSecurity(uiDelegate, pair.first, static_cast<bool>(pair.second));
[webView setUIDelegate:nil];
}
static void loadRequestAndCheckEnhancedSecurityAlerts(
RetainPtr<TestWKWebView> webView,
NSString *url,
std::initializer_list<std::pair<String, ExpectedEnhancedSecurity>> alerts)
{
runActionAndCheckEnhancedSecurityAlerts(webView, [webView, url] {
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]]];
}, alerts);
}
#define TEST_WITHOUT_SITE_ISOLATION(test_name) \
TEST(EnhancedSecurityPolicies, test_name) \
{ \
run##test_name(false); \
} \
#define TEST_WITH_SITE_ISOLATION(test_name) \
TEST(EnhancedSecurityPolicies, test_name##WithSiteIsolation) \
{ \
run##test_name(true); \
}
#define TEST_WITH_AND_WITHOUT_SITE_ISOLATION(test_name) \
TEST_WITHOUT_SITE_ISOLATION(test_name) \
\
TEST_WITH_SITE_ISOLATION(test_name)
// MARK: - Basic HTTP Detection Tests
static void runHttpLoad(bool useSiteIsolation)
{
HTTPServer plaintextServer({
{ "http://insecure.example.internal/"_s, { "<script>alert('insecure-page')</script>"_s } },
});
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, nullptr, useSiteIsolation);
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "insecure-page"_s, ExpectedEnhancedSecurity::Enabled }
});
}
TEST_WITH_AND_WITHOUT_SITE_ISOLATION(HttpLoad)
static void runHttpsLoad(bool useSiteIsolation)
{
HTTPServer secureServer({
{ "/"_s, { "<script>alert('secure-page')</script>"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(nullptr, &secureServer, useSiteIsolation);
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"https://secure.example.internal/", {
{ "secure-page"_s, ExpectedEnhancedSecurity::Disabled }
});
}
TEST_WITH_AND_WITHOUT_SITE_ISOLATION(HttpsLoad)
static void runSameSiteHttpsUpgrade(bool useSiteIsolation)
{
HTTPServer plaintextServer({
{ "http://example.co.uk/"_s, { 302, { { "Location"_s, "https://example.co.uk/"_s } }, emptyString() } }
});
HTTPServer secureServer({
{ "/"_s, { "<script>alert('secure-page')</script>"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, &secureServer, useSiteIsolation);
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://example.co.uk/", {
{ "secure-page"_s, ExpectedEnhancedSecurity::Disabled }
});
}
TEST_WITH_AND_WITHOUT_SITE_ISOLATION(SameSiteHttpsUpgrade)
// MARK: - Frame Tests
static void runHttpEmbeddingHttpIframe(bool useSiteIsolation)
{
using namespace TestWebKitAPI;
HTTPServer plaintextServer({
{ "http://insecure.example.internal/"_s, { "<iframe src='http://insecure.different.internal/'></iframe>"_s } },
{ "http://insecure.different.internal/"_s, { "<script>alert('redirected-page')</script>"_s } }
});
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, nullptr, useSiteIsolation);
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "redirected-page"_s, ExpectedEnhancedSecurity::Enabled }
});
}
TEST_WITH_AND_WITHOUT_SITE_ISOLATION(HttpEmbeddingHttpIframe)
static void runHttpEmbedHttpsIframe(bool useSiteIsolation)
{
HTTPServer plaintextServer({
{ "http://insecure.example.internal/"_s, { "<iframe src='https://secure.example.internal/'></iframe>"_s } },
});
HTTPServer secureServer({
{ "/"_s, { "<script>alert('embed-iframe')</script>"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, &secureServer, useSiteIsolation);
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "embed-iframe"_s, ExpectedEnhancedSecurity::Enabled }
});
}
TEST_WITH_AND_WITHOUT_SITE_ISOLATION(HttpEmbedHttpsIframe)
// MARK: - Redirection Tests
static void runCrossSiteHttpRedirect(bool useSiteIsolation)
{
HTTPServer plaintextServer({
{ "http://first.example.internal/"_s, { 302, { { "Location"_s, "http://second.different.internal/"_s } }, emptyString() } },
{ "http://second.different.internal/"_s, { "<script>alert('redirected-site')</script>"_s } },
});
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, nullptr, useSiteIsolation);
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://first.example.internal/", {
{ "redirected-site"_s, ExpectedEnhancedSecurity::Enabled }
});
}
TEST_WITH_AND_WITHOUT_SITE_ISOLATION(CrossSiteHttpRedirect)
static void runCrossSiteHttpToHttpsRedirect(bool useSiteIsolation)
{
HTTPServer plaintextServer({
{ "http://insecure.example.internal/"_s, { 302, { { "Location"_s, "https://secure.different.internal/"_s } }, emptyString() } },
});
HTTPServer secureServer({
{ "/"_s, { "<script>alert('redirected-site')</script>"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, &secureServer, useSiteIsolation);
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "redirected-site"_s, ExpectedEnhancedSecurity::Enabled }
});
}
TEST_WITH_AND_WITHOUT_SITE_ISOLATION(CrossSiteHttpToHttpsRedirect)
static void runHttpsOnlyExplicitlyBypassedWithHttpRedirect(bool useSiteIsolation)
{
HTTPServer plaintextServer({
{ "http://site.example/secure"_s, { { }, "hi: not secure"_s } },
{ "http://site2.example/secure2"_s, { { }, "hi: not secure"_s } },
{ "http://site2.example/secure3"_s, { { }, "hi: not secure"_s } },
}, HTTPServer::Protocol::Http);
HTTPServer secureServer({
{ "/secure"_s, { 302, {{ "Location"_s, "http://site.example/secure"_s }}, "redirecting..."_s } },
{ "/secure2"_s, { 302, {{ "Location"_s, "http://site2.example/secure3"_s }}, "redirecting..."_s } },
{ "/secure3"_s, { 302, {{ "Location"_s, "http://site2.example/secure2"_s }}, "redirecting..."_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, &secureServer, useSiteIsolation);
__block int errorCode { 0 };
__block bool finishedSuccessfully { false };
__block int loadCount { 0 };
RetainPtr<TestNavigationDelegate> delegate = adoptNS([webView navigationDelegate]);
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
++loadCount;
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
EXPECT_NULL(error);
if (error)
errorCode = error.code;
};
[webView configuration].defaultWebpagePreferences._networkConnectionIntegrityPolicy = _WKWebsiteNetworkConnectionIntegrityPolicyHTTPSOnly | _WKWebsiteNetworkConnectionIntegrityPolicyHTTPSOnlyExplicitlyBypassedForDomain;
errorCode = 0;
finishedSuccessfully = false;
loadCount = 0;
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://site.example/secure"]]];
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_TRUE(finishedSuccessfully);
EXPECT_EQ(loadCount, 2);
EXPECT_WK_STREQ(@"http://site.example/secure", [webView URL].absoluteString);
errorCode = 0;
finishedSuccessfully = false;
loadCount = 0;
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://site.example/secure2"]]];
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_TRUE(finishedSuccessfully);
EXPECT_EQ(loadCount, 3);
EXPECT_WK_STREQ(@"http://site2.example/secure2", [webView URL].absoluteString);
// Now use a different domain to the above to ensure a process swap happens,
// this ensures testing of an edge case in the HTTPS upgrade / same-site HTTP logic.
errorCode = 0;
finishedSuccessfully = false;
loadCount = 0;
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://different.example/secure2"]]];
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_TRUE(finishedSuccessfully);
EXPECT_EQ(loadCount, 3);
EXPECT_WK_STREQ(@"http://site2.example/secure2", [webView URL].absoluteString);
}
TEST_WITHOUT_SITE_ISOLATION(HttpsOnlyExplicitlyBypassedWithHttpRedirect)
// MARK: - Window Tests
static void runHttpOpeningHttpsWindow(bool useSiteIsolation)
{
HTTPServer plaintextServer({
{ "http://insecure.example.internal/"_s, { "<script>window.onload = function() { window.open('https://secure.different.internal/'); }</script>"_s } },
});
HTTPServer secureServer({
{ "/"_s, { "<script>alert('opened-window')</script>"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, &secureServer, useSiteIsolation);
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "opened-window"_s, ExpectedEnhancedSecurity::Enabled }
});
}
TEST_WITH_AND_WITHOUT_SITE_ISOLATION(HttpOpeningHttpsWindow)
static void runHttpOpeningHttpsTargetSelf(bool useSiteIsolation)
{
HTTPServer plaintextServer({
{ "http://insecure.example.internal/"_s, { "<script>window.onload = function() { window.open('https://secure.different.internal/', '_self', 'noopener'); }</script>"_s } }
});
HTTPServer secureServer({
{ "/"_s, { "<script>alert('opened-window')</script>"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, &secureServer, useSiteIsolation);
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "opened-window"_s, ExpectedEnhancedSecurity::Enabled }
});
}
TEST_WITH_AND_WITHOUT_SITE_ISOLATION(HttpOpeningHttpsTargetSelf)
static void runHttpsOpeningHttp(bool useSiteIsolation)
{
HTTPServer plaintextServer({
{ "http://insecure.example.internal/"_s, { "<script>alert('opened-window'); alert(!!window.opener)</script>"_s } }
});
HTTPServer secureServer({
{ "/"_s, { "<script>window.onload = function() { window.open('http://insecure.example.internal/'); }</script>"_s } }
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, &secureServer, useSiteIsolation);
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"https://secure.different.internal/", {
{ "opened-window"_s, ExpectedEnhancedSecurity::Disabled },
{ "true"_s, ExpectedEnhancedSecurity::Disabled }
});
}
TEST_WITH_AND_WITHOUT_SITE_ISOLATION(HttpsOpeningHttp)
static void runOpenerMultipleNavigations(bool useSiteIsolation)
{
HTTPServer plaintextServer({
{ "http://insecure.example.internal/"_s, { "<script>alert('opened-window'); alert(!!window.opener); window.location = 'http://insecure.final.internal/second-page'</script>"_s } },
{ "http://insecure.final.internal/second-page"_s, { "<script>alert('second-page'); alert(!!window.opener);</script>"_s } }
});
HTTPServer secureServer({
{ "/"_s, { "<script>window.onload = function() { window.open('http://insecure.example.internal/'); }</script>"_s } }
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, &secureServer, useSiteIsolation);
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"https://secure.different.internal/", {
{ "opened-window"_s, ExpectedEnhancedSecurity::Disabled },
{ "true"_s, ExpectedEnhancedSecurity::Disabled },
{ "second-page"_s, ExpectedEnhancedSecurity::Disabled },
{ "true"_s, ExpectedEnhancedSecurity::Disabled }
});
}
TEST_WITH_AND_WITHOUT_SITE_ISOLATION(OpenerMultipleNavigations)
static void runOpenerThenSelfNavigation(bool useSiteIsolation)
{
HTTPServer plaintextServer({
{ "http://insecure.example.internal/"_s, { "<script>alert('initial-page'); window.open('https://secure.different.internal/'); window.location = 'https://secure.final.internal/self-navigate';</script>"_s } }
});
HTTPServer secureServer({
{ "/"_s, { "<script>setTimeout(function() { alert('opened-window'); alert(!!window.opener); window.opener.location = 'https://secure.final.internal/navigated'; }, 500);</script>"_s } },
{ "/self-navigate"_s, { "<script>alert('self-new-page');</script>"_s } },
{ "/navigated"_s, { "<script>alert('navigated');</script>"_s } }
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, &secureServer, useSiteIsolation);
// Ensure the navigation to secure.final.internal would be considered valid
// to drop out of Enhanced Security by having a cookie present.
RetainPtr<NSHTTPCookie> sessionCookie = [NSHTTPCookie cookieWithProperties:@{
NSHTTPCookiePath: @"/",
NSHTTPCookieName: @"CookieName",
NSHTTPCookieValue: @"CookieValue",
NSHTTPCookieDomain: @"secure.final.internal",
}];
[[webView configuration].websiteDataStore.httpCookieStore setCookie:sessionCookie.get() completionHandler:^{ }];
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "initial-page"_s, ExpectedEnhancedSecurity::Enabled },
{ "self-new-page"_s, ExpectedEnhancedSecurity::Enabled },
{ "opened-window"_s, ExpectedEnhancedSecurity::Enabled },
{ "true"_s, ExpectedEnhancedSecurity::Enabled },
{ "navigated"_s, ExpectedEnhancedSecurity::Enabled },
});
}
// FIXME: rdar://164474301 (Fix SiteIsolation compatibility with EnhancedSecurity feature)
TEST_WITHOUT_SITE_ISOLATION(OpenerThenSelfNavigation)
static void runHttpOpeningHttpsNoOpener(bool useSiteIsolation)
{
HTTPServer plaintextServer({
{ "http://insecure.example.internal/"_s, { "<script>window.onload = function() { window.open('https://secure.different.internal/', '_blank', 'noopener'); }</script>"_s } }
});
HTTPServer secureServer({
{ "/"_s, { "<script>alert('opened-window')</script>"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, &secureServer, useSiteIsolation);
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "opened-window"_s, ExpectedEnhancedSecurity::Enabled }
});
}
TEST_WITH_AND_WITHOUT_SITE_ISOLATION(HttpOpeningHttpsNoOpener)
static void runHttpLocationRedirectsHttps(bool useSiteIsolation)
{
HTTPServer plaintextServer({
{ "http://insecure.example.internal/"_s, { "<script>window.onload = function() { window.location = 'https://secure.different.internal/'; }</script>"_s } }
});
HTTPServer secureServer({
{ "/"_s, { "<script>alert('location-redirected-site')</script>"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, &secureServer, useSiteIsolation);
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "location-redirected-site"_s, ExpectedEnhancedSecurity::Enabled }
});
}
TEST_WITH_AND_WITHOUT_SITE_ISOLATION(HttpLocationRedirectsHttps)
// MARK: - User / Client Input Tests
static void runHttpThenUserNavigateToHttps(bool useSiteIsolation)
{
HTTPServer plaintextServer({
{ "http://insecure.example.internal/"_s, { "<script>alert('insecure-first-site');</script>"_s } },
});
HTTPServer secureServer({
{ "/"_s, { "<script>alert('secure-second-site');</script>"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, &secureServer, useSiteIsolation);
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "insecure-first-site"_s, ExpectedEnhancedSecurity::Enabled }
});
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"https://secure.example.internal/", {
{ "secure-second-site"_s, ExpectedEnhancedSecurity::Disabled }
});
}
TEST_WITH_AND_WITHOUT_SITE_ISOLATION(HttpThenUserNavigateToHttps)
static void runHttpThenClickLinkToHttps(bool useSiteIsolation)
{
HTTPServer plaintextServer({
{ "http://insecure.example.internal/"_s, { "<script>alert('insecure-first-site');</script><a id='testLink' href='https://secure.different.internal/'>Link</a>"_s } },
});
HTTPServer secureServer({
{ "/"_s, { "<script>alert('secure-second-site')</script>"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, &secureServer, useSiteIsolation);
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "insecure-first-site"_s, ExpectedEnhancedSecurity::Enabled }
});
runActionAndCheckEnhancedSecurityAlerts(webView, [webView]() {
[webView clickOnElementID:@"testLink"];
}, {
{ "secure-second-site"_s, ExpectedEnhancedSecurity::Enabled }
});
}
TEST_WITH_AND_WITHOUT_SITE_ISOLATION(HttpThenClickLinkToHttps)
static void runHttpsToHttpsThenBack(bool useSiteIsolation)
{
HTTPServer secureServer({
{ "/first"_s, { "<script>window.addEventListener('pageshow', function() { alert('first-page'); });</script>"_s } },
{ "/second"_s, { "<script>alert('second-page')</script>"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(nullptr, &secureServer, useSiteIsolation);
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"https://secure.example.internal/first", {
{ "first-page"_s, ExpectedEnhancedSecurity::Disabled }
});
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"https://secure.example.internal/second", {
{ "second-page"_s, ExpectedEnhancedSecurity::Disabled }
});
runActionAndCheckEnhancedSecurityAlerts(webView, [webView]() {
auto* backForwardList = [webView backForwardList];
EXPECT_TRUE(!!backForwardList.backItem);
EXPECT_EQ(1U, backForwardList.backList.count);
[webView goBack];
}, {
{ "first-page"_s, ExpectedEnhancedSecurity::Disabled }
});
}
TEST_WITH_AND_WITHOUT_SITE_ISOLATION(HttpsToHttpsThenBack)
static void runHttpNavigateToHttpsThenBack(bool useSiteIsolation)
{
HTTPServer plaintextServer({
{ "http://insecure.example.internal/"_s, { "<script>window.addEventListener('pageshow', function() { alert('insecure-first-site'); });</script>"_s } },
});
HTTPServer secureServer({
{ "/"_s, { "<script>alert('secure-page');</script>"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, &secureServer, useSiteIsolation);
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "insecure-first-site"_s, ExpectedEnhancedSecurity::Enabled }
});
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"https://secure.example.internal/", {
{ "secure-page"_s, ExpectedEnhancedSecurity::Disabled }
});
runActionAndCheckEnhancedSecurityAlerts(webView, [webView]() {
auto* backForwardList = [webView backForwardList];
EXPECT_TRUE(!!backForwardList.backItem);
EXPECT_EQ(1U, backForwardList.backList.count);
[webView goBack];
}, {
{ "insecure-first-site"_s, ExpectedEnhancedSecurity::Enabled }
});
}
TEST_WITH_AND_WITHOUT_SITE_ISOLATION(HttpNavigateToHttpsThenBack)
static void runMultiHopThenBack(bool useSiteIsolation)
{
HTTPServer plaintextServer({
{ "http://insecure.example.internal/"_s, { "<script>alert('insecure-first-site'); window.location.href = 'https://secure.example.internal/tainted'; </script>"_s } },
});
HTTPServer secureServer({
{ "/tainted"_s, { "<script>window.addEventListener('pageshow', function() { alert('tainted-https-site'); });</script>"_s } },
{ "/secure"_s, { "<script>alert('secure-page');</script>"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, &secureServer, useSiteIsolation);
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "insecure-first-site"_s, ExpectedEnhancedSecurity::Enabled },
{ "tainted-https-site"_s, ExpectedEnhancedSecurity::Enabled }
});
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"https://secure.example.internal/secure", {
{ "secure-page"_s, ExpectedEnhancedSecurity::Disabled }
});
runActionAndCheckEnhancedSecurityAlerts(webView, [webView]() {
auto* backForwardList = [webView backForwardList];
EXPECT_TRUE(!!backForwardList.backItem);
EXPECT_EQ(1U, backForwardList.backList.count);
[webView goBack];
}, {
{ "tainted-https-site"_s, ExpectedEnhancedSecurity::Enabled }
});
}
// FIXME: rdar://164474301 (Fix SiteIsolation compatibility with EnhancedSecurity feature)
TEST_WITHOUT_SITE_ISOLATION(MultiHopThenBack)
static void runMultiHopThenBackJavascript(bool useSiteIsolation)
{
HTTPServer plaintextServer({
{ "http://insecure.example.internal/"_s, { "<script>alert('insecure-first-site'); window.location.href = 'https://secure.example.internal/tainted'; </script>"_s } },
});
HTTPServer secureServer({
{ "/tainted"_s, { "<script>window.addEventListener('pageshow', function() { alert('tainted-https-site'); });</script>"_s } },
{ "/secure"_s, { "<script>alert('secure-page');</script>"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, &secureServer, useSiteIsolation);
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "insecure-first-site"_s, ExpectedEnhancedSecurity::Enabled },
{ "tainted-https-site"_s, ExpectedEnhancedSecurity::Enabled }
});
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"https://secure.example.internal/secure", {
{ "secure-page"_s, ExpectedEnhancedSecurity::Disabled }
});
runActionAndCheckEnhancedSecurityAlerts(webView, [webView]() {
[webView evaluateJavaScript:@"history.back()" completionHandler:nil];
}, {
{ "tainted-https-site"_s, ExpectedEnhancedSecurity::Enabled }
});
}
// FIXME: rdar://164474301 (Fix SiteIsolation compatibility with EnhancedSecurity feature)
TEST_WITHOUT_SITE_ISOLATION(MultiHopThenBackJavascript)
static void runMultiHopThenBackToSecure(bool useSiteIsolation)
{
HTTPServer plaintextServer({
{ "http://insecure.example.internal/"_s, { "<script>alert('insecure-site');</script>"_s } },
});
HTTPServer secureServer({
{ "/"_s, { "<script>window.addEventListener('pageshow', function() { alert('secure-page'); });</script>"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, &secureServer, useSiteIsolation);
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "insecure-site"_s, ExpectedEnhancedSecurity::Enabled }
});
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"https://secure.example.internal/", {
{ "secure-page"_s, ExpectedEnhancedSecurity::Disabled }
});
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "insecure-site"_s, ExpectedEnhancedSecurity::Enabled }
});
runActionAndCheckEnhancedSecurityAlerts(webView, [webView]() {
auto* backForwardList = [webView backForwardList];
EXPECT_TRUE(!!backForwardList.backItem);
EXPECT_EQ(2U, backForwardList.backList.count);
[webView goBack];
}, {
{ "secure-page"_s, ExpectedEnhancedSecurity::Disabled }
});
}
TEST_WITH_AND_WITHOUT_SITE_ISOLATION(MultiHopThenBackToSecure)
static void runMultiHopThenBackToSecureJavascript(bool useSiteIsolation)
{
HTTPServer plaintextServer({
{ "http://insecure.example.internal/"_s, { "<script>alert('insecure-site');</script>"_s } },
});
HTTPServer secureServer({
{ "/"_s, { "<script>window.addEventListener('pageshow', function() { alert('secure-page'); });</script>"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, &secureServer, useSiteIsolation);
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "insecure-site"_s, ExpectedEnhancedSecurity::Enabled }
});
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"https://secure.example.internal/", {
{ "secure-page"_s, ExpectedEnhancedSecurity::Disabled }
});
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "insecure-site"_s, ExpectedEnhancedSecurity::Enabled }
});
runActionAndCheckEnhancedSecurityAlerts(webView, [webView]() {
auto* backForwardList = [webView backForwardList];
EXPECT_TRUE(!!backForwardList.backItem);
EXPECT_EQ(2U, backForwardList.backList.count);
[webView evaluateJavaScript:@"history.back()" completionHandler:nil];
}, {
{ "secure-page"_s, ExpectedEnhancedSecurity::Disabled }
});
}
TEST_WITH_AND_WITHOUT_SITE_ISOLATION(MultiHopThenBackToSecureJavascript)
// MARK: - Cookies / Local Storage / Meaningful Site Checks
static void runHttpThenNavigateToHttpsSiteWithCookies(bool useSiteIsolation)
{
HTTPServer plaintextServer({
{ "http://insecure.example.internal/"_s, { "<script>alert('insecure-site');</script>"_s } },
});
HTTPServer secureServer({
{ "/"_s, { "<script>alert('secure-site-with-cookies');</script>"_s } },
{ "/set-cookie"_s, { "<script>document.cookie = 'CookieName=CookieValue; Path=/'; alert('cookie-set');</script>"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, &secureServer, useSiteIsolation);
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"https://secure.example.internal/set-cookie", {
{ "cookie-set"_s, ExpectedEnhancedSecurity::Disabled }
});
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "insecure-site"_s, ExpectedEnhancedSecurity::Enabled }
});
runActionAndCheckEnhancedSecurityAlerts(webView, [webView]() {
[webView evaluateJavaScript:@"window.location.href = 'https://secure.example.internal/'" completionHandler:nil];
}, {
{ "secure-site-with-cookies"_s, ExpectedEnhancedSecurity::Disabled }
});
}
TEST_WITH_AND_WITHOUT_SITE_ISOLATION(HttpThenNavigateToHttpsSiteWithCookies)
static void runHttpThenNavigateToHttpsSiteWithLocalStorage(bool useSiteIsolation)
{
HTTPServer plaintextServer({
{ "http://insecure.example.internal/"_s, { "<script>alert('insecure-site');</script>"_s } },
});
HTTPServer secureServer({
{ "/"_s, { "<script>alert('secure-site-with-storage');</script>"_s } },
{ "/set-storage"_s, { "<script>localStorage.setItem('KeyName', 'KeyValue'); alert('storage-set');</script>"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, &secureServer, useSiteIsolation);
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"https://secure.example.internal/set-storage", {
{ "storage-set"_s, ExpectedEnhancedSecurity::Disabled }
});
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "insecure-site"_s, ExpectedEnhancedSecurity::Enabled }
});
runActionAndCheckEnhancedSecurityAlerts(webView, [webView]() {
[webView evaluateJavaScript:@"window.location.href = 'https://secure.example.internal/'" completionHandler:nil];
}, {
{ "secure-site-with-storage"_s, ExpectedEnhancedSecurity::Disabled }
});
}
TEST_WITH_AND_WITHOUT_SITE_ISOLATION(HttpThenNavigateToHttpsSiteWithLocalStorage)
static void runHttpThenHttpsThenNavigateToHttpsSiteWithCookies(bool useSiteIsolation)
{
HTTPServer plaintextServer({
{ "http://insecure.example.internal/"_s, { "<script>alert('insecure-site');</script>"_s } },
});
HTTPServer secureServer({
{ "/"_s, { "<script>alert(document.cookie ? 'secure-site-with-cookies' : 'secure-site-without-cookies');</script>"_s } },
{ "/set-cookie"_s, { "<script>document.cookie = 'CookieName=CookieValue; Path=/'; alert('cookie-set');</script>"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, &secureServer, useSiteIsolation);
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"https://secure.different.internal/set-cookie", {
{ "cookie-set"_s, ExpectedEnhancedSecurity::Disabled }
});
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "insecure-site"_s, ExpectedEnhancedSecurity::Enabled }
});
runActionAndCheckEnhancedSecurityAlerts(webView, [webView]() {
[webView evaluateJavaScript:@"window.location.href = 'https://secure.example.internal/'" completionHandler:nil];
}, {
{ "secure-site-without-cookies"_s, ExpectedEnhancedSecurity::Enabled }
});
runActionAndCheckEnhancedSecurityAlerts(webView, [webView]() {
[webView evaluateJavaScript:@"window.location.href = 'https://secure.different.internal/'" completionHandler:nil];
}, {
{ "secure-site-with-cookies"_s, ExpectedEnhancedSecurity::Disabled }
});
}
TEST_WITH_AND_WITHOUT_SITE_ISOLATION(HttpThenHttpsThenNavigateToHttpsSiteWithCookies)
static void runHttpThenNavigateToHttpsSiteWithCookiesThenHttps(bool useSiteIsolation)
{
HTTPServer plaintextServer({
{ "http://insecure.example.internal/"_s, { "<script>alert('insecure-site');</script>"_s } },
});
HTTPServer secureServer({
{ "/"_s, { "<script>alert(document.cookie ? 'secure-site-with-cookies' : 'secure-site-without-cookies');</script>"_s } },
{ "/set-cookie"_s, { "<script>document.cookie = 'CookieName=CookieValue; Path=/'; alert('cookie-set');</script>"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, &secureServer, useSiteIsolation);
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"https://secure.different.internal/set-cookie", {
{ "cookie-set"_s, ExpectedEnhancedSecurity::Disabled }
});
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "insecure-site"_s, ExpectedEnhancedSecurity::Enabled }
});
runActionAndCheckEnhancedSecurityAlerts(webView, [webView]() {
[webView evaluateJavaScript:@"window.location.href = 'https://secure.different.internal/'" completionHandler:nil];
}, {
{ "secure-site-with-cookies"_s, ExpectedEnhancedSecurity::Disabled }
});
runActionAndCheckEnhancedSecurityAlerts(webView, [webView]() {
[webView evaluateJavaScript:@"window.location.href = 'https://secure.example.internal/'" completionHandler:nil];
}, {
{ "secure-site-without-cookies"_s, ExpectedEnhancedSecurity::Enabled }
});
}
TEST_WITH_AND_WITHOUT_SITE_ISOLATION(HttpThenNavigateToHttpsSiteWithCookiesThenHttps)
static void runHttpThenNavigateToHttpsSetCookiesThenNavigateToHttpsAgain(bool useSiteIsolation)
{
HTTPServer plaintextServer({
{ "http://insecure.example.internal/"_s, { "<script>alert('insecure-site');</script>"_s } },
});
HTTPServer secureServer({
{ "/"_s, { "<script>alert(document.cookie ? 'secure-site-with-cookies' : 'secure-site-without-cookies');</script>"_s } },
{ "/set-cookie"_s, { "<script>document.cookie = 'CookieName=CookieValue; Path=/'; alert('cookie-set');</script>"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, &secureServer, useSiteIsolation);
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "insecure-site"_s, ExpectedEnhancedSecurity::Enabled }
});
runActionAndCheckEnhancedSecurityAlerts(webView, [webView]() {
[webView evaluateJavaScript:@"window.location.href = 'https://secure.example.internal/set-cookie'" completionHandler:nil];
}, {
{ "cookie-set"_s, ExpectedEnhancedSecurity::Enabled }
});
runActionAndCheckEnhancedSecurityAlerts(webView, [webView]() {
[webView evaluateJavaScript:@"window.location.href = 'https://secure.example.internal/'" completionHandler:nil];
}, {
{ "secure-site-with-cookies"_s, ExpectedEnhancedSecurity::Enabled }
});
}
TEST_WITH_AND_WITHOUT_SITE_ISOLATION(HttpThenNavigateToHttpsSetCookiesThenNavigateToHttpsAgain)
static void runHttpRedirectsHttpsWithExplicitNavigationToMeaningfulSite(bool useSiteIsolation)
{
HTTPServer plaintextServer({
{ "http://insecure.example.internal/"_s, { "<script>window.onload = function() { window.location = 'https://secure.different.internal/'; }</script>"_s } }
});
HTTPServer secureServer({
{ "/"_s, { "<script>alert('location-redirected-site')</script>"_s } },
{ "/explicit-set-cookies"_s, { "<script>document.cookie = 'CookieName=CookieValue; Path=/'; alert('explicit-navigation-set-cookies')</script>"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, &secureServer, useSiteIsolation);
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "location-redirected-site"_s, ExpectedEnhancedSecurity::Enabled }
});
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"https://secure.different.internal/explicit-set-cookies", {
{ "explicit-navigation-set-cookies"_s, ExpectedEnhancedSecurity::Disabled }
});
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "location-redirected-site"_s, ExpectedEnhancedSecurity::Disabled }
});
}
// FIXME: rdar://164474301 (Fix SiteIsolation compatibility with EnhancedSecurity feature)
TEST_WITHOUT_SITE_ISOLATION(HttpRedirectsHttpsWithExplicitNavigationToMeaningfulSite)
static void runWindowOpenThenNavigateToMeaningfulSite(bool useSiteIsolation)
{
HTTPServer plaintextServer({
{ "http://insecure.example.internal/"_s, { "<script>alert('insecure-site');</script>"_s } },
});
HTTPServer secureServer({
{ "/"_s, { "<script>alert(document.cookie ? 'secure-site-with-cookies' : 'secure-site-without-cookies');</script>"_s } },
{ "/set-cookie"_s, { "<script>document.cookie = 'CookieName=CookieValue; Path=/'; alert('cookie-set');</script>"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, &secureServer, useSiteIsolation);
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "insecure-site"_s, ExpectedEnhancedSecurity::Enabled }
});
runActionAndCheckEnhancedSecurityAlerts(webView, [webView]() {
[webView evaluateJavaScript:@"window.location.href = 'https://secure.example.internal/set-cookie'" completionHandler:nil];
}, {
{ "cookie-set"_s, ExpectedEnhancedSecurity::Enabled }
});
runActionAndCheckEnhancedSecurityAlerts(webView, [webView]() {
[webView evaluateJavaScript:@"window.open('https://secure.example.internal/', '_blank', 'noopener');" completionHandler:nil];
}, {
{ "secure-site-with-cookies"_s, ExpectedEnhancedSecurity::Enabled }
});
}
TEST_WITH_AND_WITHOUT_SITE_ISOLATION(WindowOpenThenNavigateToMeaningfulSite)
static void runReloadEnhancedSecurityRemains(bool useSiteIsolation)
{
HTTPServer plaintextServer({
{ "http://insecure.example.internal/"_s, { "<script>alert('insecure-site');</script>"_s } },
});
HTTPServer secureServer({
{ "/"_s, { "<script>window.onload = function() { alert('secure-site'); }</script>"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, &secureServer, useSiteIsolation);
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "insecure-site"_s, ExpectedEnhancedSecurity::Enabled }
});
runActionAndCheckEnhancedSecurityAlerts(webView, [webView]() {
[webView evaluateJavaScript:@"window.location.href = 'https://secure.example.internal/'" completionHandler:nil];
}, {
{ "secure-site"_s, ExpectedEnhancedSecurity::Enabled }
});
runActionAndCheckEnhancedSecurityAlerts(webView, [webView]() {
[webView reload];
}, {
{ "secure-site"_s, ExpectedEnhancedSecurity::Enabled }
});
}
TEST_WITH_AND_WITHOUT_SITE_ISOLATION(ReloadEnhancedSecurityRemains)
static void runJavascriptRefreshEnhancedSecurityRemains(bool useSiteIsolation)
{
HTTPServer plaintextServer({
{ "http://insecure.example.internal/"_s, { "<script>alert('insecure-site');</script>"_s } },
});
HTTPServer secureServer({
{ "/"_s, { "<script>window.onload = function() { alert('secure-site'); }</script>"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, &secureServer, useSiteIsolation);
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "insecure-site"_s, ExpectedEnhancedSecurity::Enabled }
});
runActionAndCheckEnhancedSecurityAlerts(webView, [webView]() {
[webView evaluateJavaScript:@"window.location.href = 'https://secure.example.internal/'" completionHandler:nil];
}, {
{ "secure-site"_s, ExpectedEnhancedSecurity::Enabled }
});
runActionAndCheckEnhancedSecurityAlerts(webView, [webView]() {
[webView evaluateJavaScript:@"window.location.reload();" completionHandler:nil];
}, {
{ "secure-site"_s, ExpectedEnhancedSecurity::Enabled }
});
}
TEST_WITH_AND_WITHOUT_SITE_ISOLATION(JavascriptRefreshEnhancedSecurityRemains)
static void runHttpThenNavigateToHttpsSiteWithCookiesViaAPI(bool useSiteIsolation)
{
HTTPServer plaintextServer({
{ "http://insecure.example.internal/"_s, { "<script>alert('insecure-site');</script>"_s } },
});
HTTPServer secureServer({
{ "/"_s, { "<script>alert('secure-site-with-cookies');</script>"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, &secureServer, useSiteIsolation);
RetainPtr<NSHTTPCookie> sessionCookie = [NSHTTPCookie cookieWithProperties:@{
NSHTTPCookiePath: @"/",
NSHTTPCookieName: @"CookieName",
NSHTTPCookieValue: @"CookieValue",
NSHTTPCookieDomain: @"different.internal",
}];
[[webView configuration].websiteDataStore.httpCookieStore setCookie:sessionCookie.get() completionHandler:^{ }];
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "insecure-site"_s, ExpectedEnhancedSecurity::Enabled }
});
runActionAndCheckEnhancedSecurityAlerts(webView, [webView]() {
[webView evaluateJavaScript:@"window.location.href = 'https://different.internal/'" completionHandler:nil];
}, {
{ "secure-site-with-cookies"_s, ExpectedEnhancedSecurity::Disabled }
});
}
TEST_WITH_AND_WITHOUT_SITE_ISOLATION(HttpThenNavigateToHttpsSiteWithCookiesViaAPI)
// Intentionally flipped to match EnhancedSecurity::Disabled, EnhancedSecurity::EnabledInsecure
enum class SeenOutsideEnhancedSecurity : bool { Seen, NotSeen };
static NSURL *enhancedSecuritySitesPath()
{
auto dataStoreConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] init]);
NSURL *rootStorage = [dataStoreConfiguration.get().generalStorageDirectory URLByDeletingLastPathComponent];
NSURL *enhancedSecurityDirectory = [rootStorage URLByAppendingPathComponent:@"EnhancedSecurity"];
NSURL *enhancedSecurityFile = [enhancedSecurityDirectory URLByAppendingPathComponent:@"EnhancedSecuritySites.db"];
return enhancedSecurityFile;
}
static RetainPtr<NSString> emptyEnhancedSecuritySitesPath()
{
NSFileManager *defaultFileManager = NSFileManager.defaultManager;
NSURL *enhancedSecurityFile = enhancedSecuritySitesPath();
NSURL *enhancedSecurityDirectory = [enhancedSecurityFile URLByDeletingLastPathComponent];
[defaultFileManager removeItemAtPath:enhancedSecurityDirectory.path error:nil];
EXPECT_FALSE([defaultFileManager fileExistsAtPath:enhancedSecurityFile.path]);
[defaultFileManager createDirectoryAtURL:enhancedSecurityDirectory withIntermediateDirectories:YES attributes:nil error:nil];
return enhancedSecurityFile.path;
}
static void createEnhancedSecuritySitesTable(WebCore::SQLiteDatabase& database)
{
constexpr auto createEnhancedSecuritySitesTable = "CREATE TABLE sites ("
"site TEXT PRIMARY KEY, enhanced_security_state INT NOT NULL)"_s;
EXPECT_TRUE(database.executeCommand(createEnhancedSecuritySitesTable));
}
static void addEnhancedSecuritySite(WebCore::SQLiteDatabase& database, String site, SeenOutsideEnhancedSecurity seenOutsideEnhancedSecurity)
{
constexpr auto query = "INSERT INTO sites (site, enhanced_security_state) VALUES (?, ?)"_s;
auto statement = database.prepareStatement(query);
EXPECT_TRUE(!!statement);
EXPECT_EQ(statement->bindText(1, site), SQLITE_OK);
EXPECT_EQ(statement->bindInt(2, seenOutsideEnhancedSecurity == SeenOutsideEnhancedSecurity::Seen ? 0 : 1), SQLITE_OK);
EXPECT_EQ(statement->step(), SQLITE_DONE);
EXPECT_EQ(statement->reset(), SQLITE_OK);
}
static void setUpEnhancedSecuritySeenValues(std::initializer_list<std::pair<String, SeenOutsideEnhancedSecurity>> priorValues)
{
auto database = makeUniqueRef<WebCore::SQLiteDatabase>();
EXPECT_TRUE(database->open(emptyEnhancedSecuritySitesPath().get()));
createEnhancedSecuritySitesTable(database.get());
RetainPtr<NSMutableDictionary> itemsDictionary = adoptNS([[NSMutableDictionary alloc] initWithCapacity:priorValues.size()]);
for (auto& pair : priorValues)
addEnhancedSecuritySite(database.get(), pair.first, pair.second);
database->close();
}
static void cleanUpEnhancedSecuritySites()
{
NSFileManager *defaultFileManager = NSFileManager.defaultManager;
NSString* enhancedSecurityFile = enhancedSecuritySitesPath().path;
[defaultFileManager removeItemAtPath:enhancedSecurityFile error:nil];
while ([defaultFileManager fileExistsAtPath:enhancedSecurityFile])
usleep(10000);
}
static void runHttpThenNavigateToHttpsSiteWithCookiesViaAndExpectations(bool useSiteIsolation, SeenOutsideEnhancedSecurity seenOutsideEnhancedSecurity, ExpectedEnhancedSecurity finalExpectedEnhancedSecurity)
{
setUpEnhancedSecuritySeenValues({
{ "different.internal"_s, seenOutsideEnhancedSecurity }
});
HTTPServer plaintextServer({
{ "http://insecure.example.internal/"_s, { "<script>alert('insecure-site');</script>"_s } },
});
HTTPServer secureServer({
{ "/"_s, { "<script>alert('secure-site-with-cookies');</script>"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, &secureServer, useSiteIsolation, /* useNonPersistentStore */ false);
RetainPtr<NSHTTPCookie> sessionCookie = [NSHTTPCookie cookieWithProperties:@{
NSHTTPCookiePath: @"/",
NSHTTPCookieName: @"CookieName",
NSHTTPCookieValue: @"CookieValue",
NSHTTPCookieDomain: @"different.internal",
}];
[[webView configuration].websiteDataStore.httpCookieStore setCookie:sessionCookie.get() completionHandler:^{ }];
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "insecure-site"_s, ExpectedEnhancedSecurity::Enabled }
});
runActionAndCheckEnhancedSecurityAlerts(webView, [webView]() {
[webView evaluateJavaScript:@"window.location.href = 'https://different.internal/'" completionHandler:nil];
}, {
{ "secure-site-with-cookies"_s, finalExpectedEnhancedSecurity }
});
cleanUpEnhancedSecuritySites();
}
static void runHttpThenNavigateToHttpsSiteWithCookiesViaAPIAndNotSeenOutsideEnhancedSecurity(bool useSiteIsolation)
{
runHttpThenNavigateToHttpsSiteWithCookiesViaAndExpectations(useSiteIsolation, SeenOutsideEnhancedSecurity::NotSeen, ExpectedEnhancedSecurity::Enabled);
}
TEST_WITH_AND_WITHOUT_SITE_ISOLATION(HttpThenNavigateToHttpsSiteWithCookiesViaAPIAndNotSeenOutsideEnhancedSecurity)
static void runHttpThenNavigateToHttpsSiteWithCookiesViaAPIAndSeenOutsideEnhancedSecurity(bool useSiteIsolation)
{
runHttpThenNavigateToHttpsSiteWithCookiesViaAndExpectations(useSiteIsolation, SeenOutsideEnhancedSecurity::Seen, ExpectedEnhancedSecurity::Disabled);
}
TEST_WITH_AND_WITHOUT_SITE_ISOLATION(HttpThenNavigateToHttpsSiteWithCookiesViaAPIAndSeenOutsideEnhancedSecurity)
TEST(EnhancedSecurityPolicies, NonPersistentDataStoreCookieNotification)
{
HTTPServer plaintextServer({
{ "http://insecure.example.internal/"_s, {{{ "Set-Cookie"_s, "testkey=testvalue"_s }}, "<script>alert('insecure-site')</script>"_s }}
});
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, nullptr, /* useSiteIsolation */ false, /* useNonPersistentStore */ true);
auto observer = adoptNS([EnhancedSecurityCookieObserver new]);
globalCookieStore = webView.get().configuration.websiteDataStore.httpCookieStore;
[globalCookieStore addObserver:observer.get()];
observerCallbacks = 0;
loadRequestAndCheckEnhancedSecurityAlerts(webView, @"http://insecure.example.internal/", {
{ "insecure-site"_s, ExpectedEnhancedSecurity::Enabled }
});
EXPECT_EQ(observerCallbacks, 1u);
}
TEST(EnhancedSecurityPolicies, HistoryEventsUseCorrectOriginalRequest)
{
TestWebKitAPI::HTTPServer plaintextServer({
{ "http://example.internal/"_s, { "<head><title>Page Title</title></head><body><script>alert('first-page')</script></body>"_s } },
{ "http://example.internal/server_redirect_source"_s, { 301, { { "Location"_s, "server_redirect_destination"_s } } } },
{ "http://example.internal/server_redirect_destination"_s, { "<head><title>Target Page</title></head><body><script>alert('target-page')</script></body>"_s } }
});
auto webView = enhancedSecurityTestConfiguration(&plaintextServer, nullptr, /* useSiteIsolation */ false, /* useNonPersistentStore */ false);
RetainPtr historyDelegate = adoptNS([[EnhancedSecurityHistoryDelegate alloc] init]);
[webView _setHistoryDelegate:historyDelegate.get()];
auto mainURL = "http://example.internal/"_s;
RetainPtr url = adoptNS([[NSURL alloc] initWithString:mainURL.createNSString().get()]);
RetainPtr request = adoptNS([[NSURLRequest alloc] initWithURL:url.get()]);
runActionAndCheckEnhancedSecurityAlerts(webView, [webView, request] {
[webView loadRequest:request.get()];
}, { { "first-page"_s, ExpectedEnhancedSecurity::Enabled } });
TestWebKitAPI::Util::waitFor([&] {
return historyDelegate->lastNavigationData && historyDelegate->lastUpdatedTitle;
});
EXPECT_NS_EQUAL([historyDelegate->lastNavigationData originalRequest], request.get());
EXPECT_NS_EQUAL([historyDelegate->lastNavigationData originalRequest].URL, url.get());
EXPECT_NS_EQUAL(historyDelegate->lastUpdatedTitle.get(), @"Page Title");
auto sourceURL = "http://example.internal/server_redirect_source"_s;
auto destinationURL = "http://example.internal/server_redirect_destination"_s;
url = adoptNS([[NSURL alloc] initWithString:sourceURL.createNSString().get()]);
request = adoptNS([[NSURLRequest alloc] initWithURL:url.get()]);
runActionAndCheckEnhancedSecurityAlerts(webView, [webView, request] {
[webView loadRequest:request.get()];
}, { { "target-page"_s, ExpectedEnhancedSecurity::Enabled } });
TestWebKitAPI::Util::spinRunLoop();
TestWebKitAPI::Util::waitFor([&] {
return historyDelegate->lastRedirectSource && historyDelegate->lastRedirectDestination;
});
EXPECT_WK_STREQ([historyDelegate->lastRedirectSource absoluteString], sourceURL);
EXPECT_WK_STREQ([historyDelegate->lastRedirectDestination absoluteString], destinationURL);
}
#endif