From 9de3e158637088ed6d8d067c48a9fd86d7d8a371 Mon Sep 17 00:00:00 2001 From: Aleksey Khoroshilov Date: Wed, 13 Apr 2022 17:28:05 +0700 Subject: [PATCH 1/5] Add partitioned HSTS storage support. --- .../hsts_partitioning_browsertest.cc | 411 ++++++++++++++++++ chromium_src/net/base/features.cc | 4 + chromium_src/net/base/features.h | 1 + .../net/http/transport_security_state.cc | 64 +++ .../net/http/transport_security_state.h | 31 ++ .../quic/crypto/proof_verifier_chromium.cc | 13 + .../net/socket/ssl_client_socket_impl.cc | 13 + .../net/url_request/url_request_http_job.cc | 15 + net/http/partitioned_host_state_map.cc | 49 +++ net/http/partitioned_host_state_map.h | 132 ++++++ net/sources.gni | 2 + ...net-http-transport_security_state.cc.patch | 10 +- test/BUILD.gn | 1 + 13 files changed, 745 insertions(+), 1 deletion(-) create mode 100644 browser/ephemeral_storage/hsts_partitioning_browsertest.cc create mode 100644 chromium_src/net/http/transport_security_state.h create mode 100644 chromium_src/net/quic/crypto/proof_verifier_chromium.cc create mode 100644 chromium_src/net/socket/ssl_client_socket_impl.cc create mode 100644 net/http/partitioned_host_state_map.cc create mode 100644 net/http/partitioned_host_state_map.h diff --git a/browser/ephemeral_storage/hsts_partitioning_browsertest.cc b/browser/ephemeral_storage/hsts_partitioning_browsertest.cc new file mode 100644 index 000000000000..f8ffebd14af3 --- /dev/null +++ b/browser/ephemeral_storage/hsts_partitioning_browsertest.cc @@ -0,0 +1,411 @@ +/* Copyright (c) 2022 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "base/path_service.h" +#include "base/strings/pattern.h" +#include "base/strings/strcat.h" +#include "base/strings/string_util.h" +#include "brave/components/brave_shields/browser/brave_shields_util.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "chrome/test/base/ui_test_utils.h" +#include "content/public/test/browser_test.h" +#include "content/public/test/browser_test_utils.h" +#include "content/public/test/content_mock_cert_verifier.h" +#include "content/public/test/test_utils.h" +#include "net/base/features.h" +#include "net/dns/mock_host_resolver.h" +#include "net/test/embedded_test_server/embedded_test_server.h" +#include "services/network/public/cpp/network_switches.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "url/gurl.h" +#include "url/origin.h" + +namespace { + +constexpr char kFetchScript[] = R"( +(async () => { + const response = await fetch($1, {cache: 'no-store'}); + return await response.text(); +})())"; + +std::unique_ptr RespondWithServerType( + const net::test_server::HttpRequest& request) { + GURL url = request.GetURL(); + if (url.path_piece() != "/server_type") { + return nullptr; + } + + auto http_response = std::make_unique(); + http_response->set_code(net::HTTP_OK); + http_response->set_content_type("text/plain"); + http_response->set_content(url.scheme()); + http_response->AddCustomHeader("Access-Control-Allow-Origin", "*"); + return http_response; +} + +} // namespace + +class HSTSPartitioningBrowserTestBase : public InProcessBrowserTest { + public: + HSTSPartitioningBrowserTestBase() + : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) { + base::FilePath path; + base::PathService::Get(chrome::DIR_TEST_DATA, &path); + https_server_.ServeFilesFromDirectory(path); + content::SetupCrossSiteRedirector(&https_server_); + https_server_.RegisterRequestHandler( + base::BindRepeating(&RespondWithServerType)); + https_server_.AddDefaultHandlers(); + embedded_test_server()->RegisterRequestHandler( + base::BindRepeating(&RespondWithServerType)); + EXPECT_TRUE(https_server_.Start()); + EXPECT_TRUE(embedded_test_server()->Start()); + } + + void SetUpCommandLine(base::CommandLine* command_line) override { + InProcessBrowserTest::SetUpCommandLine(command_line); + mock_cert_verifier_.SetUpCommandLine(command_line); + command_line->AppendSwitchASCII( + network::switches::kHostResolverRules, + base::StringPrintf("MAP *:80 127.0.0.1:%d," + "MAP *:443 127.0.0.1:%d", + embedded_test_server()->port(), + https_server_.port())); + } + + void SetUpOnMainThread() override { + InProcessBrowserTest::SetUpOnMainThread(); + host_resolver()->AddRule("*", "127.0.0.1"); + mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK); + } + + void SetUpInProcessBrowserTestFixture() override { + InProcessBrowserTest::SetUpInProcessBrowserTestFixture(); + mock_cert_verifier_.SetUpInProcessBrowserTestFixture(); + } + + void TearDownInProcessBrowserTestFixture() override { + mock_cert_verifier_.TearDownInProcessBrowserTestFixture(); + InProcessBrowserTest::TearDownInProcessBrowserTestFixture(); + } + + void ExpectHSTSState(content::RenderFrameHost* rfh, + const std::string& host, + bool is_hsts) { + auto scheme_fetch_result = content::EvalJs( + rfh, content::JsReplace(kFetchScript, base::StrCat({"http://", host, + "/server_type"}))); + EXPECT_EQ(is_hsts ? "https" : "http", scheme_fetch_result); + } + + void SetHSTS(content::RenderFrameHost* rfh, const std::string& host) { + EXPECT_TRUE(content::ExecJs( + rfh, content::JsReplace( + kFetchScript, + base::StrCat( + {"https://", host, + "/set-header?Strict-Transport-Security: " + "max-age%3D600000&Access-Control-Allow-Origin: %2A"})))); + } + + void ClearHSTS(content::RenderFrameHost* rfh, const std::string& host) { + EXPECT_TRUE(content::ExecJs( + rfh, + content::JsReplace( + kFetchScript, + base::StrCat({"https://", host, + "/set-header?Strict-Transport-Security: " + "max-age%3D0&Access-Control-Allow-Origin: %2A"})))); + } + + protected: + content::ContentMockCertVerifier mock_cert_verifier_; + net::test_server::EmbeddedTestServer https_server_; +}; + +class HSTSPartitioningEnabledBrowserTest + : public HSTSPartitioningBrowserTestBase { + public: + HSTSPartitioningEnabledBrowserTest() { + scoped_feature_list_.InitAndEnableFeature( + net::features::kBravePartitionHSTS); + } + + private: + base::test::ScopedFeatureList scoped_feature_list_; +}; + +IN_PROC_BROWSER_TEST_F(HSTSPartitioningEnabledBrowserTest, HSTSIsPartitioned) { + // Load a.com and set b.com, c.bom HSTS inside a.com. + GURL a_com_url("http://a.com/iframe.html"); + auto* a_com_rfh = ui_test_utils::NavigateToURL(browser(), a_com_url); + ASSERT_TRUE(a_com_rfh); + EXPECT_EQ(a_com_rfh->GetLastCommittedURL(), a_com_url); + + ExpectHSTSState(a_com_rfh, "b.com", false); + SetHSTS(a_com_rfh, "b.com"); + ExpectHSTSState(a_com_rfh, "b.com", true); + + ExpectHSTSState(a_com_rfh, "c.com", false); + SetHSTS(a_com_rfh, "c.com"); + ExpectHSTSState(a_com_rfh, "c.com", true); + + // b.com iframe should be loaded via HTTPS. + { + ASSERT_TRUE(NavigateIframeToURL( + content::WebContents::FromRenderFrameHost(a_com_rfh), "test", + GURL("http://b.com/simple.html"))); + auto* b_com_inside_a_com_rfh = ChildFrameAt(a_com_rfh, 0); + ASSERT_TRUE(b_com_inside_a_com_rfh); + EXPECT_TRUE( + b_com_inside_a_com_rfh->GetLastCommittedURL().SchemeIsCryptographic()); + } + + // c.com iframe should be loaded via HTTPS. + { + ASSERT_TRUE(NavigateIframeToURL( + content::WebContents::FromRenderFrameHost(a_com_rfh), "test", + GURL("http://c.com/simple.html"))); + auto* c_com_inside_a_com_rfh = ChildFrameAt(a_com_rfh, 0); + ASSERT_TRUE(c_com_inside_a_com_rfh); + EXPECT_TRUE( + c_com_inside_a_com_rfh->GetLastCommittedURL().SchemeIsCryptographic()); + } + + // d.com iframe should be loaded via HTTP. + { + ASSERT_TRUE(NavigateIframeToURL( + content::WebContents::FromRenderFrameHost(a_com_rfh), "test", + GURL("http://d.com/simple.html"))); + auto* d_com_inside_a_com_rfh = ChildFrameAt(a_com_rfh, 0); + ASSERT_TRUE(d_com_inside_a_com_rfh); + EXPECT_FALSE( + d_com_inside_a_com_rfh->GetLastCommittedURL().SchemeIsCryptographic()); + + ExpectHSTSState(d_com_inside_a_com_rfh, "b.com", true); + ExpectHSTSState(d_com_inside_a_com_rfh, "c.com", true); + } + + // Load b.com in another tab and expect HSTS is not applied. + GURL b_com_url("http://b.com/iframe.html"); + auto* b_com_rfh = ui_test_utils::NavigateToURLWithDisposition( + browser(), b_com_url, WindowOpenDisposition::NEW_FOREGROUND_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); + ASSERT_TRUE(b_com_rfh); + EXPECT_EQ(b_com_rfh->GetLastCommittedURL(), b_com_url); + + ExpectHSTSState(b_com_rfh, "b.com", false); + ExpectHSTSState(b_com_rfh, "c.com", false); + + // c.com iframe inside b.com should be loaded via HTTP. + { + ASSERT_TRUE(NavigateIframeToURL( + content::WebContents::FromRenderFrameHost(b_com_rfh), "test", + GURL("http://c.com/simple.html"))); + auto* c_com_inside_b_com_rfh = ChildFrameAt(b_com_rfh, 0); + ASSERT_TRUE(c_com_inside_b_com_rfh); + EXPECT_FALSE( + c_com_inside_b_com_rfh->GetLastCommittedURL().SchemeIsCryptographic()); + + ExpectHSTSState(c_com_inside_b_com_rfh, "b.com", false); + ExpectHSTSState(c_com_inside_b_com_rfh, "c.com", false); + } +} + +IN_PROC_BROWSER_TEST_F(HSTSPartitioningEnabledBrowserTest, + HSTSIsPartitionedUsingRegistrableDomain) { + // Load a.com and set b.com HSTS inside a.com. + GURL a_com_url("http://a.com/simple.html"); + auto* a_com_rfh = ui_test_utils::NavigateToURL(browser(), a_com_url); + ASSERT_TRUE(a_com_rfh); + EXPECT_EQ(a_com_rfh->GetLastCommittedURL(), a_com_url); + + SetHSTS(a_com_rfh, "b.com"); + ExpectHSTSState(a_com_rfh, "b.com", true); + + // Load sub.a.com, expect b.com HSTS state is applied. + GURL sub_a_com_url("http://sub.a.com/simple.html"); + auto* sub_a_com_rfh = ui_test_utils::NavigateToURLWithDisposition( + browser(), sub_a_com_url, WindowOpenDisposition::NEW_FOREGROUND_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); + ASSERT_TRUE(sub_a_com_rfh); + + ExpectHSTSState(sub_a_com_rfh, "b.com", true); +} + +IN_PROC_BROWSER_TEST_F(HSTSPartitioningEnabledBrowserTest, + HSTSIsUsedOnMainFrameLoad) { + // Load a.com and set a.com HSTS. + GURL a_com_url("http://a.com/simple.html"); + auto* a_com_rfh = ui_test_utils::NavigateToURL(browser(), a_com_url); + ASSERT_TRUE(a_com_rfh); + EXPECT_EQ(a_com_rfh->GetLastCommittedURL(), a_com_url); + + SetHSTS(a_com_rfh, "a.com"); + ExpectHSTSState(a_com_rfh, "a.com", true); + + // Load a.com in another tab, expect HSTS is applied. + auto* a_com_rfh2 = ui_test_utils::NavigateToURLWithDisposition( + browser(), a_com_url, WindowOpenDisposition::NEW_FOREGROUND_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); + ASSERT_TRUE(a_com_rfh2); + EXPECT_TRUE(a_com_rfh2->GetLastCommittedURL().SchemeIsCryptographic()); +} + +IN_PROC_BROWSER_TEST_F(HSTSPartitioningEnabledBrowserTest, HSTSIsCleared) { + // Load a.com and set c.com HSTS inside a.com. + GURL a_com_url("http://a.com/simple.html"); + auto* a_com_rfh = ui_test_utils::NavigateToURL(browser(), a_com_url); + ASSERT_TRUE(a_com_rfh); + EXPECT_EQ(a_com_rfh->GetLastCommittedURL(), a_com_url); + + ExpectHSTSState(a_com_rfh, "c.com", false); + SetHSTS(a_com_rfh, "c.com"); + ExpectHSTSState(a_com_rfh, "c.com", true); + + // Load b.com in another tab and expect c.com HSTS is not applied. + GURL b_com_url("http://b.com/simple.html"); + auto* b_com_rfh = ui_test_utils::NavigateToURLWithDisposition( + browser(), b_com_url, WindowOpenDisposition::NEW_FOREGROUND_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); + ASSERT_TRUE(b_com_rfh); + EXPECT_EQ(b_com_rfh->GetLastCommittedURL(), b_com_url); + + // Set c.com HSTS inside b.com. + ExpectHSTSState(b_com_rfh, "c.com", false); + SetHSTS(b_com_rfh, "c.com"); + ExpectHSTSState(b_com_rfh, "c.com", true); + + // Clear c.com HSTS inside a.com. + ClearHSTS(a_com_rfh, "c.com"); + ExpectHSTSState(a_com_rfh, "c.com", false); + + // Expect c.com HSTS is still active inside b.com. + ExpectHSTSState(b_com_rfh, "c.com", true); +} + +IN_PROC_BROWSER_TEST_F(HSTSPartitioningEnabledBrowserTest, + PRE_HSTSIsPersisted) { + GURL a_com_url("http://a.com/simple.html"); + auto* a_com_rfh = ui_test_utils::NavigateToURL(browser(), a_com_url); + ASSERT_TRUE(a_com_rfh); + EXPECT_EQ(a_com_rfh->GetLastCommittedURL(), a_com_url); + + ExpectHSTSState(a_com_rfh, "b.com", false); + SetHSTS(a_com_rfh, "b.com"); + ExpectHSTSState(a_com_rfh, "b.com", true); +} + +IN_PROC_BROWSER_TEST_F(HSTSPartitioningEnabledBrowserTest, HSTSIsPersisted) { + GURL a_com_url("http://a.com/simple.html"); + auto* a_com_rfh = ui_test_utils::NavigateToURL(browser(), a_com_url); + ASSERT_TRUE(a_com_rfh); + EXPECT_EQ(a_com_rfh->GetLastCommittedURL(), a_com_url); + + ExpectHSTSState(a_com_rfh, "b.com", true); +} + +IN_PROC_BROWSER_TEST_F(HSTSPartitioningEnabledBrowserTest, + HSTSIsIgnoredOnIpAddress) { + GURL ip_url = embedded_test_server()->GetURL( + "/set-header?Strict-Transport-Security: max-age%3D600000"); + auto* ip_rfh = ui_test_utils::NavigateToURL(browser(), ip_url); + ASSERT_TRUE(ip_rfh); +} + +IN_PROC_BROWSER_TEST_F(HSTSPartitioningEnabledBrowserTest, + HSTSIsIgnoredInSandbox) { + GURL a_com_url( + "http://a.com/set-header?" + "Content-Security-Policy: sandbox allow-scripts"); + auto* a_com_rfh = ui_test_utils::NavigateToURL(browser(), a_com_url); + ASSERT_TRUE(a_com_rfh); + + ExpectHSTSState(a_com_rfh, "a.com", false); + SetHSTS(a_com_rfh, "a.com"); + ExpectHSTSState(a_com_rfh, "a.com", false); + + ExpectHSTSState(a_com_rfh, "b.com", false); + SetHSTS(a_com_rfh, "b.com"); + ExpectHSTSState(a_com_rfh, "b.com", false); +} + +IN_PROC_BROWSER_TEST_F(HSTSPartitioningEnabledBrowserTest, + HSTSIsSetIn3pIframe) { + // Load a.com and set c.com HSTS inside b.com iframe. + GURL a_com_url("http://a.com/iframe.html"); + auto* a_com_rfh = ui_test_utils::NavigateToURL(browser(), a_com_url); + ASSERT_TRUE(a_com_rfh); + EXPECT_EQ(a_com_rfh->GetLastCommittedURL(), a_com_url); + + ASSERT_TRUE( + NavigateIframeToURL(content::WebContents::FromRenderFrameHost(a_com_rfh), + "test", GURL("http://b.com/simple.html"))); + auto* b_com_inside_a_com_rfh = ChildFrameAt(a_com_rfh, 0); + ASSERT_TRUE(b_com_inside_a_com_rfh); + + ExpectHSTSState(b_com_inside_a_com_rfh, "c.com", false); + SetHSTS(b_com_inside_a_com_rfh, "c.com"); + ExpectHSTSState(b_com_inside_a_com_rfh, "c.com", true); + + // Expect c.com HSTS state is also available in the main frame. + ExpectHSTSState(a_com_rfh, "c.com", true); +} + +class HSTSPartitioningDisabledBrowserTest + : public HSTSPartitioningBrowserTestBase { + public: + HSTSPartitioningDisabledBrowserTest() { + scoped_feature_list_.InitAndDisableFeature( + net::features::kBravePartitionHSTS); + } + + private: + base::test::ScopedFeatureList scoped_feature_list_; +}; + +IN_PROC_BROWSER_TEST_F(HSTSPartitioningDisabledBrowserTest, + HSTSIsNotPartitioned) { + // Load a.com and set b.com HSTS inside a.com. + GURL a_com_url("http://a.com/simple.html"); + auto* a_com_rfh = ui_test_utils::NavigateToURL(browser(), a_com_url); + ASSERT_TRUE(a_com_rfh); + EXPECT_EQ(a_com_rfh->GetLastCommittedURL(), a_com_url); + + ExpectHSTSState(a_com_rfh, "a.com", false); + + ExpectHSTSState(a_com_rfh, "b.com", false); + SetHSTS(a_com_rfh, "b.com"); + ExpectHSTSState(a_com_rfh, "b.com", true); + + ExpectHSTSState(a_com_rfh, "c.com", false); + SetHSTS(a_com_rfh, "c.com"); + ExpectHSTSState(a_com_rfh, "c.com", true); + + // Load b.com in another tab and expect HSTS is applied. + GURL b_com_url("http://b.com/simple.html"); + auto* b_com_rfh = ui_test_utils::NavigateToURLWithDisposition( + browser(), b_com_url, WindowOpenDisposition::NEW_FOREGROUND_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); + ASSERT_TRUE(b_com_rfh); + EXPECT_TRUE(b_com_rfh->GetLastCommittedURL().SchemeIsCryptographic()); + + // Load b.com in another tab and expect HSTS is applied. + GURL c_com_url("http://c.com/simple.html"); + auto* c_com_rfh = ui_test_utils::NavigateToURLWithDisposition( + browser(), c_com_url, WindowOpenDisposition::NEW_FOREGROUND_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); + ASSERT_TRUE(c_com_rfh); + EXPECT_TRUE(c_com_rfh->GetLastCommittedURL().SchemeIsCryptographic()); + + // Load a.com and expect HSTS is not applied. + auto* a_com_rfh2 = ui_test_utils::NavigateToURL(browser(), a_com_url); + ASSERT_TRUE(a_com_rfh2); + EXPECT_EQ(a_com_rfh2->GetLastCommittedURL(), a_com_url); +} diff --git a/chromium_src/net/base/features.cc b/chromium_src/net/base/features.cc index e2c4891f64ea..e90b3bd03826 100644 --- a/chromium_src/net/base/features.cc +++ b/chromium_src/net/base/features.cc @@ -41,5 +41,9 @@ const base::Feature kBraveFirstPartyEphemeralStorage{ const base::Feature kBravePartitionBlobStorage{ "BravePartitionBlobStorage", base::FEATURE_ENABLED_BY_DEFAULT}; +// Partition HSTS state storage by top frame site. +const base::Feature kBravePartitionHSTS{"BravePartitionHSTS", + base::FEATURE_ENABLED_BY_DEFAULT}; + } // namespace features } // namespace net diff --git a/chromium_src/net/base/features.h b/chromium_src/net/base/features.h index cb3fb1226d3f..35d469d5ec49 100644 --- a/chromium_src/net/base/features.h +++ b/chromium_src/net/base/features.h @@ -19,6 +19,7 @@ NET_EXPORT extern const base::FeatureParam kBraveEphemeralStorageKeepAliveTimeInSeconds; NET_EXPORT extern const base::Feature kBraveFirstPartyEphemeralStorage; NET_EXPORT extern const base::Feature kBravePartitionBlobStorage; +NET_EXPORT extern const base::Feature kBravePartitionHSTS; } // namespace features } // namespace net diff --git a/chromium_src/net/http/transport_security_state.cc b/chromium_src/net/http/transport_security_state.cc index 2af3794867e3..a19868a05621 100644 --- a/chromium_src/net/http/transport_security_state.cc +++ b/chromium_src/net/http/transport_security_state.cc @@ -21,4 +21,68 @@ #endif +#define BRAVE_TRANSPORT_SECURITY_STATE_DELETE_DYNAMIC_DATA_FOR_HOST \ + if (enabled_sts_hosts_.DeleteDataInAllPartitions(hashed_host)) { \ + deleted = true; \ + } + #include "src/net/http/transport_security_state.cc" + +#undef BRAVE_TRANSPORT_SECURITY_STATE_DELETE_DYNAMIC_DATA_FOR_HOST +#undef BRAVE_ENABLE_STATIC_PINS + +namespace net { + +namespace { + +// Use only top frame site as a key for HSTS partitioning to not over-populate +// HSTS state storage. +absl::optional GetTopFrameSiteHash( + const NetworkIsolationKey& network_isolation_key) { + if (!base::FeatureList::IsEnabled(features::kBravePartitionHSTS)) { + return absl::nullopt; + } + + // An empty or opaque top frame site cannot be used as a partition key, return + // an empty string which will be treated as a non-persistable partition. + if (network_isolation_key.IsTransient() || + !network_isolation_key.GetTopFrameSite().has_value() || + network_isolation_key.GetTopFrameSite()->opaque()) { + return std::string(); + } + + return HashHost(network_isolation_key.GetTopFrameSite()->Serialize()); +} + +} // namespace + +bool TransportSecurityState::ShouldSSLErrorsBeFatal( + const NetworkIsolationKey& network_isolation_key, + const std::string& host) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + auto auto_reset_partition_hash = enabled_sts_hosts_.SetPartitionHash( + GetTopFrameSiteHash(network_isolation_key)); + return ShouldSSLErrorsBeFatal(host); +} + +bool TransportSecurityState::ShouldUpgradeToSSL( + const NetworkIsolationKey& network_isolation_key, + const std::string& host, + const NetLogWithSource& net_log) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + auto auto_reset_partition_hash = enabled_sts_hosts_.SetPartitionHash( + GetTopFrameSiteHash(network_isolation_key)); + return ShouldUpgradeToSSL(host, net_log); +} + +bool TransportSecurityState::AddHSTSHeader( + const NetworkIsolationKey& network_isolation_key, + const std::string& host, + const std::string& value) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + auto auto_reset_partition_hash = enabled_sts_hosts_.SetPartitionHash( + GetTopFrameSiteHash(network_isolation_key)); + return AddHSTSHeader(host, value); +} + +} // namespace net diff --git a/chromium_src/net/http/transport_security_state.h b/chromium_src/net/http/transport_security_state.h new file mode 100644 index 000000000000..67d06afd93ee --- /dev/null +++ b/chromium_src/net/http/transport_security_state.h @@ -0,0 +1,31 @@ +/* Copyright 2022 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_CHROMIUM_SRC_NET_HTTP_TRANSPORT_SECURITY_STATE_H_ +#define BRAVE_CHROMIUM_SRC_NET_HTTP_TRANSPORT_SECURITY_STATE_H_ + +#include "brave/net/http/partitioned_host_state_map.h" + +#define enabled_sts_hosts_ \ + enabled_sts_hosts_unused_; \ + \ + public: \ + bool ShouldSSLErrorsBeFatal( \ + const NetworkIsolationKey& network_isolation_key, \ + const std::string& host); \ + bool ShouldUpgradeToSSL(const NetworkIsolationKey& network_isolation_key, \ + const std::string& host, \ + const NetLogWithSource& net_log); \ + bool AddHSTSHeader(const NetworkIsolationKey& network_isolation_key, \ + const std::string& host, const std::string& value); \ + \ + private: \ + PartitionedHostStateMap enabled_sts_hosts_ + +#include "src/net/http/transport_security_state.h" + +#undef enabled_sts_hosts_ + +#endif // BRAVE_CHROMIUM_SRC_NET_HTTP_TRANSPORT_SECURITY_STATE_H_ diff --git a/chromium_src/net/quic/crypto/proof_verifier_chromium.cc b/chromium_src/net/quic/crypto/proof_verifier_chromium.cc new file mode 100644 index 000000000000..4333b60e4aec --- /dev/null +++ b/chromium_src/net/quic/crypto/proof_verifier_chromium.cc @@ -0,0 +1,13 @@ +/* Copyright 2022 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "net/http/transport_security_state.h" + +#define ShouldSSLErrorsBeFatal(host) \ + ShouldSSLErrorsBeFatal(proof_verifier_->network_isolation_key_, host) + +#include "src/net/quic/crypto/proof_verifier_chromium.cc" + +#undef ShouldSSLErrorsBeFatal diff --git a/chromium_src/net/socket/ssl_client_socket_impl.cc b/chromium_src/net/socket/ssl_client_socket_impl.cc new file mode 100644 index 000000000000..fb0afb4a20d5 --- /dev/null +++ b/chromium_src/net/socket/ssl_client_socket_impl.cc @@ -0,0 +1,13 @@ +/* Copyright 2022 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "net/http/transport_security_state.h" + +#define ShouldSSLErrorsBeFatal(host) \ + ShouldSSLErrorsBeFatal(ssl_config_.network_isolation_key, host) + +#include "src/net/socket/ssl_client_socket_impl.cc" + +#undef ShouldSSLErrorsBeFatal diff --git a/chromium_src/net/url_request/url_request_http_job.cc b/chromium_src/net/url_request/url_request_http_job.cc index fdc9ba4bc69a..7a43624a5354 100644 --- a/chromium_src/net/url_request/url_request_http_job.cc +++ b/chromium_src/net/url_request/url_request_http_job.cc @@ -5,8 +5,23 @@ #include "net/url_request/url_request_http_job.h" +#include "net/http/transport_security_state.h" + +#define ShouldSSLErrorsBeFatal(host) \ + ShouldSSLErrorsBeFatal(request_->isolation_info().network_isolation_key(), \ + host) +#define ShouldUpgradeToSSL(host, net_log) \ + ShouldUpgradeToSSL(request->isolation_info().network_isolation_key(), host, \ + net_log) +#define AddHSTSHeader(host, value) \ + AddHSTSHeader(request_->isolation_info().network_isolation_key(), host, value) + #include "src/net/url_request/url_request_http_job.cc" +#undef AddHSTSHeader +#undef ShouldUpgradeToSSL +#undef ShouldSSLErrorsBeFatal + namespace net { CookieOptions URLRequestHttpJob::CreateCookieOptions( diff --git a/net/http/partitioned_host_state_map.cc b/net/http/partitioned_host_state_map.cc new file mode 100644 index 000000000000..6befcac8eefc --- /dev/null +++ b/net/http/partitioned_host_state_map.cc @@ -0,0 +1,49 @@ +/* Copyright 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/net/http/partitioned_host_state_map.h" + +#include + +#include "base/strings/strcat.h" +#include "crypto/sha2.h" +#include "net/base/network_isolation_key.h" + +namespace net { + +PartitionedHostStateMapBase::PartitionedHostStateMapBase() = default; +PartitionedHostStateMapBase::~PartitionedHostStateMapBase() = default; + +base::AutoReset> +PartitionedHostStateMapBase::SetPartitionHash( + absl::optional partition_hash) { + CHECK(!partition_hash || partition_hash->empty() || + partition_hash->size() == crypto::kSHA256Length); + return base::AutoReset>( + &partition_hash_, std::move(partition_hash)); +} + +bool PartitionedHostStateMapBase::HasPartitionHash() const { + return partition_hash_.has_value(); +} + +bool PartitionedHostStateMapBase::IsPartitionHashValid() const { + return partition_hash_ && !partition_hash_->empty(); +} + +std::string PartitionedHostStateMapBase::GetKeyWithPartitionHash( + const std::string& k) const { + CHECK(IsPartitionHashValid()); + return base::StrCat({GetHalfKey(k), GetHalfKey(*partition_hash_)}); +} + +// static +base::StringPiece PartitionedHostStateMapBase::GetHalfKey(base::StringPiece k) { + CHECK_EQ(k.size(), crypto::kSHA256Length); + const size_t kHalfSHA256HashLength = crypto::kSHA256Length / 2; + return k.substr(0, kHalfSHA256HashLength); +} + +} // namespace net diff --git a/net/http/partitioned_host_state_map.h b/net/http/partitioned_host_state_map.h new file mode 100644 index 000000000000..e5f8142db8ab --- /dev/null +++ b/net/http/partitioned_host_state_map.h @@ -0,0 +1,132 @@ +/* Copyright 2022 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_NET_HTTP_PARTITIONED_HOST_STATE_MAP_H_ +#define BRAVE_NET_HTTP_PARTITIONED_HOST_STATE_MAP_H_ + +#include + +#include "base/auto_reset.h" +#include "base/ranges/algorithm.h" +#include "base/strings/string_piece.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace net { + +// Implements partitioning support for structures in TransportSecurityState. +class PartitionedHostStateMapBase { + public: + PartitionedHostStateMapBase(); + ~PartitionedHostStateMapBase(); + + PartitionedHostStateMapBase(const PartitionedHostStateMapBase&) = delete; + PartitionedHostStateMapBase& operator=(const PartitionedHostStateMapBase&) = + delete; + + // Stores scoped partition hash for use in subsequent calls. + base::AutoReset> SetPartitionHash( + absl::optional partition_hash); + // Returns true if |partition_hash_| is set. The value may be empty. + bool HasPartitionHash() const; + // Returns true if |partition_hash_| contains a non empty valid hash. + bool IsPartitionHashValid() const; + // Creates a host hash by concatenating first 16 bytes (half of SHA256) from + // |k| and first 16 bytes from |partition_hash_|. + // CHECKs if |partition_hash_| is not valid. + std::string GetKeyWithPartitionHash(const std::string& k) const; + + // Returns first 16 bytes from |k|. + static base::StringPiece GetHalfKey(base::StringPiece k); + + private: + // Partition hash can be of these values: + // nullopt - unpartitioned; + // empty string - invalid/opaque partition, i.e. shouldn't be stored; + // non empty string - valid partition. + absl::optional partition_hash_; +}; + +// Allows data partitioning using half key from PartitionHash. The class mimics +// std::map interface just enough to replace unpartitioned maps in +// TransportSecurityState. +template +class PartitionedHostStateMap : public PartitionedHostStateMapBase { + public: + using iterator = typename T::iterator; + using const_iterator = typename T::const_iterator; + using key_type = typename T::key_type; + using mapped_type = typename T::mapped_type; + using value_type = typename T::value_type; + using size_type = typename T::size_type; + + const_iterator begin() const noexcept { return map_.begin(); } + const_iterator end() const noexcept { return map_.end(); } + + size_type size() const noexcept { return map_.size(); } + iterator erase(const_iterator position) { return map_.erase(position); } + void clear() noexcept { map_.clear(); } + + size_type erase(const key_type& k) { + if (!HasPartitionHash()) { + return map_.erase(k); + } + + if (!IsPartitionHashValid()) { + return size_type(); + } + + return map_.erase(GetKeyWithPartitionHash(k)); + } + + mapped_type& operator[](const key_type& k) { + if (!HasPartitionHash()) { + return map_[k]; + } + + if (!IsPartitionHashValid()) { + // Return a temporary item reference when the partition is invalid. + // The item in this case shouldn't be used or persisted. + temporary_item_ = mapped_type(); + return temporary_item_; + } + + return map_[GetKeyWithPartitionHash(k)]; + } + + const_iterator find(const key_type& k) const { + if (!HasPartitionHash()) { + return map_.find(k); + } + + if (!IsPartitionHashValid()) { + return map_.end(); + } + + return map_.find(GetKeyWithPartitionHash(k)); + } + + // Removes all items with similar first 16 bytes of |k|, effectively ignoring + // partition hash part. + bool DeleteDataInAllPartitions(const key_type& k) { + auto equal_range_pair = base::ranges::equal_range( + map_, GetHalfKey(k), {}, + [](const value_type& v) { return GetHalfKey(v.first); }); + + if (equal_range_pair.first == equal_range_pair.second) { + return false; + } + + map_.erase(equal_range_pair.first, equal_range_pair.second); + return true; + } + + private: + T map_; + mapped_type temporary_item_; +}; + +} // namespace net + +#endif // BRAVE_NET_HTTP_PARTITIONED_HOST_STATE_MAP_H_ diff --git a/net/sources.gni b/net/sources.gni index c2e763c96889..883a330090c8 100644 --- a/net/sources.gni +++ b/net/sources.gni @@ -5,6 +5,8 @@ brave_net_sources = [ "//brave/net/decentralized_dns/constants.h", + "//brave/net/http/partitioned_host_state_map.cc", + "//brave/net/http/partitioned_host_state_map.h", "//brave/net/proxy_resolution/proxy_config_service_tor.cc", "//brave/net/proxy_resolution/proxy_config_service_tor.h", ] diff --git a/patches/net-http-transport_security_state.cc.patch b/patches/net-http-transport_security_state.cc.patch index 9d565bdfae4e..226f68f88f94 100644 --- a/patches/net-http-transport_security_state.cc.patch +++ b/patches/net-http-transport_security_state.cc.patch @@ -1,5 +1,5 @@ diff --git a/net/http/transport_security_state.cc b/net/http/transport_security_state.cc -index 340b2e49df29ac57b2f827836bdcd3a4c63da279..6e014a9e54fdfcaef7ce32f0f03f2420ed1dbf94 100644 +index 340b2e49df29ac57b2f827836bdcd3a4c63da279..f329508de8e0407126d616bf7c547dedd84a17ad 100644 --- a/net/http/transport_security_state.cc +++ b/net/http/transport_security_state.cc @@ -417,6 +417,7 @@ TransportSecurityState::TransportSecurityState( @@ -10,3 +10,11 @@ index 340b2e49df29ac57b2f827836bdcd3a4c63da279..6e014a9e54fdfcaef7ce32f0f03f2420 #endif // Check that there no invalid entries in the static HSTS bypass list. for (auto& host : hsts_host_bypass_list) { +@@ -881,6 +882,7 @@ bool TransportSecurityState::DeleteDynamicDataForHost(const std::string& host) { + enabled_sts_hosts_.erase(sts_interator); + deleted = true; + } ++ BRAVE_TRANSPORT_SECURITY_STATE_DELETE_DYNAMIC_DATA_FOR_HOST + + auto pkp_iterator = enabled_pkp_hosts_.find(hashed_host); + if (pkp_iterator != enabled_pkp_hosts_.end()) { diff --git a/test/BUILD.gn b/test/BUILD.gn index ce6017fec76d..8c5bea6304e8 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -663,6 +663,7 @@ if (!is_android) { "//brave/browser/ephemeral_storage/ephemeral_storage_browsertest.cc", "//brave/browser/ephemeral_storage/ephemeral_storage_browsertest.h", "//brave/browser/ephemeral_storage/ephemeral_storage_qa_browsertest.cc", + "//brave/browser/ephemeral_storage/hsts_partitioning_browsertest.cc", "//brave/browser/extensions/api/brave_shields_api_browsertest.cc", "//brave/browser/extensions/api/brave_theme_api_browsertest.cc", "//brave/browser/extensions/brave_base_local_data_files_browsertest.cc", From 9490e8ecdcb0a83cf01a55a15e895a59d8dee705 Mon Sep 17 00:00:00 2001 From: Aleksey Khoroshilov Date: Tue, 26 Apr 2022 15:57:54 +0700 Subject: [PATCH 2/5] Add preload HSTS state test to ensure it's working as intended. --- .../hsts_partitioning_browsertest.cc | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/browser/ephemeral_storage/hsts_partitioning_browsertest.cc b/browser/ephemeral_storage/hsts_partitioning_browsertest.cc index f8ffebd14af3..30aad3a98a98 100644 --- a/browser/ephemeral_storage/hsts_partitioning_browsertest.cc +++ b/browser/ephemeral_storage/hsts_partitioning_browsertest.cc @@ -124,6 +124,15 @@ class HSTSPartitioningBrowserTestBase : public InProcessBrowserTest { "max-age%3D0&Access-Control-Allow-Origin: %2A"})))); } + void ExpectPreloadWorks() { + for (const auto* preloaded_host : {"brave.com", "accounts.google.com"}) { + GURL url(base::StrCat({"http://", preloaded_host, "/simple.html"})); + auto* rfh = ui_test_utils::NavigateToURL(browser(), url); + ASSERT_TRUE(rfh); + EXPECT_TRUE(rfh->GetLastCommittedURL().SchemeIsCryptographic()); + } + } + protected: content::ContentMockCertVerifier mock_cert_verifier_; net::test_server::EmbeddedTestServer https_server_; @@ -358,6 +367,10 @@ IN_PROC_BROWSER_TEST_F(HSTSPartitioningEnabledBrowserTest, ExpectHSTSState(a_com_rfh, "c.com", true); } +IN_PROC_BROWSER_TEST_F(HSTSPartitioningEnabledBrowserTest, HSTSPreloadWorks) { + ExpectPreloadWorks(); +} + class HSTSPartitioningDisabledBrowserTest : public HSTSPartitioningBrowserTestBase { public: @@ -409,3 +422,7 @@ IN_PROC_BROWSER_TEST_F(HSTSPartitioningDisabledBrowserTest, ASSERT_TRUE(a_com_rfh2); EXPECT_EQ(a_com_rfh2->GetLastCommittedURL(), a_com_url); } + +IN_PROC_BROWSER_TEST_F(HSTSPartitioningDisabledBrowserTest, HSTSPreloadWorks) { + ExpectPreloadWorks(); +} From 03b46d4595ca9ee7903259e307b000e7c9b934d1 Mon Sep 17 00:00:00 2001 From: Aleksey Khoroshilov Date: Thu, 28 Apr 2022 00:24:49 +0700 Subject: [PATCH 3/5] Use schemeless key to partition HSTS data. Don't store HSTS if site_for_cookies doesn't match top_frame_site. --- .../hsts_partitioning_browsertest.cc | 17 ++++ chromium_src/net/base/schemeful_site.h | 17 ++++ .../net/http/transport_security_state.cc | 83 +++++++++++++++---- .../net/http/transport_security_state.h | 3 +- .../net/url_request/url_request_http_job.cc | 2 +- 5 files changed, 104 insertions(+), 18 deletions(-) create mode 100644 chromium_src/net/base/schemeful_site.h diff --git a/browser/ephemeral_storage/hsts_partitioning_browsertest.cc b/browser/ephemeral_storage/hsts_partitioning_browsertest.cc index 30aad3a98a98..bf6ba9e66f41 100644 --- a/browser/ephemeral_storage/hsts_partitioning_browsertest.cc +++ b/browser/ephemeral_storage/hsts_partitioning_browsertest.cc @@ -367,6 +367,23 @@ IN_PROC_BROWSER_TEST_F(HSTSPartitioningEnabledBrowserTest, ExpectHSTSState(a_com_rfh, "c.com", true); } +IN_PROC_BROWSER_TEST_F(HSTSPartitioningEnabledBrowserTest, + HSTSIsSetInMainFrameOnNavigation) { + GURL a_com_set_hsts_url( + "https://a.com/set-header?Strict-Transport-Security: max-age%3D600000"); + auto* a_com_set_hsts_rfh = + ui_test_utils::NavigateToURL(browser(), a_com_set_hsts_url); + ASSERT_TRUE(a_com_set_hsts_rfh); + EXPECT_EQ(a_com_set_hsts_rfh->GetLastCommittedURL(), a_com_set_hsts_url); + + GURL a_com_url("http://a.com/simple.html"); + auto* a_com_rfh = ui_test_utils::NavigateToURLWithDisposition( + browser(), a_com_url, WindowOpenDisposition::NEW_FOREGROUND_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); + ASSERT_TRUE(a_com_rfh); + EXPECT_TRUE(a_com_rfh->GetLastCommittedURL().SchemeIsCryptographic()); +} + IN_PROC_BROWSER_TEST_F(HSTSPartitioningEnabledBrowserTest, HSTSPreloadWorks) { ExpectPreloadWorks(); } diff --git a/chromium_src/net/base/schemeful_site.h b/chromium_src/net/base/schemeful_site.h new file mode 100644 index 000000000000..9c65cf833931 --- /dev/null +++ b/chromium_src/net/base/schemeful_site.h @@ -0,0 +1,17 @@ +/* Copyright (c) 2022 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_CHROMIUM_SRC_NET_BASE_SCHEMEFUL_SITE_H_ +#define BRAVE_CHROMIUM_SRC_NET_BASE_SCHEMEFUL_SITE_H_ + +#define NetworkIsolationKey \ + NetworkIsolationKey; \ + friend class HSTSPartitionHashHelper + +#include "src/net/base/schemeful_site.h" + +#undef NetworkIsolationKey + +#endif // BRAVE_CHROMIUM_SRC_NET_BASE_SCHEMEFUL_SITE_H_ diff --git a/chromium_src/net/http/transport_security_state.cc b/chromium_src/net/http/transport_security_state.cc index a19868a05621..d9914ab241ca 100644 --- a/chromium_src/net/http/transport_security_state.cc +++ b/chromium_src/net/http/transport_security_state.cc @@ -6,6 +6,7 @@ #include "net/http/transport_security_state.h" #include "build/build_config.h" +#include "url/gurl.h" #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) @@ -33,25 +34,76 @@ namespace net { +// Non-anonymous helper to friend with SchemefulSite. +class HSTSPartitionHashHelper { + public: + static absl::optional GetSchemefulSiteRegistrableDomainOrHost( + const SchemefulSite& schemeful_site) { + if (!schemeful_site.has_registrable_domain_or_host()) { + return absl::nullopt; + } + return schemeful_site.registrable_domain_or_host(); + } +}; + namespace { -// Use only top frame site as a key for HSTS partitioning to not over-populate -// HSTS state storage. -absl::optional GetTopFrameSiteHash( - const NetworkIsolationKey& network_isolation_key) { - if (!base::FeatureList::IsEnabled(features::kBravePartitionHSTS)) { - return absl::nullopt; - } +bool IsTopFrameOriginCryptographic(const IsolationInfo& isolation_info) { + return isolation_info.top_frame_origin() && + GURL::SchemeIsCryptographic( + isolation_info.top_frame_origin()->scheme()); +} +absl::optional GetHSTSPartitionHash( + const NetworkIsolationKey& network_isolation_key) { + DCHECK(base::FeatureList::IsEnabled(features::kBravePartitionHSTS)); // An empty or opaque top frame site cannot be used as a partition key, return - // an empty string which will be treated as a non-persistable partition. + // a hash which will be treated as a non-persistable partition. if (network_isolation_key.IsTransient() || !network_isolation_key.GetTopFrameSite().has_value() || network_isolation_key.GetTopFrameSite()->opaque()) { return std::string(); } - return HashHost(network_isolation_key.GetTopFrameSite()->Serialize()); + absl::optional top_frame_registrable_domain_or_host = + HSTSPartitionHashHelper::GetSchemefulSiteRegistrableDomainOrHost( + *network_isolation_key.GetTopFrameSite()); + if (!top_frame_registrable_domain_or_host) { + return std::string(); + } + + return HashHost(*top_frame_registrable_domain_or_host); +} + +// Use only top frame site as a key for HSTS partitioning to not over-populate +// HSTS state storage. Check top frame site for equality with site for cookies, +// don't store HSTS if it differs. IsolationInfo is not available everywhere, +// that's why we're using it only when parsing new HSTS state. +absl::optional GetPartitionHashForAddingHSTS( + const IsolationInfo& isolation_info) { + if (!base::FeatureList::IsEnabled(features::kBravePartitionHSTS)) { + return absl::nullopt; + } + + // If the top frame scheme is secure and SiteForCookies doesn't match + // TopFrameSite, then we don't want to store this HSTS state at all. Return an + // empty hash in this case, which will be treated as a non-persistable + // partition. + if (IsTopFrameOriginCryptographic(isolation_info) && + isolation_info.site_for_cookies().site() != + *isolation_info.network_isolation_key().GetTopFrameSite()) { + return std::string(); + } + + return GetHSTSPartitionHash(isolation_info.network_isolation_key()); +} + +absl::optional GetPartitionHashForReadingHSTS( + const NetworkIsolationKey& network_isolation_key) { + if (!base::FeatureList::IsEnabled(features::kBravePartitionHSTS)) { + return absl::nullopt; + } + return GetHSTSPartitionHash(network_isolation_key); } } // namespace @@ -61,7 +113,7 @@ bool TransportSecurityState::ShouldSSLErrorsBeFatal( const std::string& host) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); auto auto_reset_partition_hash = enabled_sts_hosts_.SetPartitionHash( - GetTopFrameSiteHash(network_isolation_key)); + GetPartitionHashForReadingHSTS(network_isolation_key)); return ShouldSSLErrorsBeFatal(host); } @@ -71,17 +123,16 @@ bool TransportSecurityState::ShouldUpgradeToSSL( const NetLogWithSource& net_log) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); auto auto_reset_partition_hash = enabled_sts_hosts_.SetPartitionHash( - GetTopFrameSiteHash(network_isolation_key)); + GetPartitionHashForReadingHSTS(network_isolation_key)); return ShouldUpgradeToSSL(host, net_log); } -bool TransportSecurityState::AddHSTSHeader( - const NetworkIsolationKey& network_isolation_key, - const std::string& host, - const std::string& value) { +bool TransportSecurityState::AddHSTSHeader(const IsolationInfo& isolation_info, + const std::string& host, + const std::string& value) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); auto auto_reset_partition_hash = enabled_sts_hosts_.SetPartitionHash( - GetTopFrameSiteHash(network_isolation_key)); + GetPartitionHashForAddingHSTS(isolation_info)); return AddHSTSHeader(host, value); } diff --git a/chromium_src/net/http/transport_security_state.h b/chromium_src/net/http/transport_security_state.h index 67d06afd93ee..7f407547b64c 100644 --- a/chromium_src/net/http/transport_security_state.h +++ b/chromium_src/net/http/transport_security_state.h @@ -7,6 +7,7 @@ #define BRAVE_CHROMIUM_SRC_NET_HTTP_TRANSPORT_SECURITY_STATE_H_ #include "brave/net/http/partitioned_host_state_map.h" +#include "net/base/isolation_info.h" #define enabled_sts_hosts_ \ enabled_sts_hosts_unused_; \ @@ -18,7 +19,7 @@ bool ShouldUpgradeToSSL(const NetworkIsolationKey& network_isolation_key, \ const std::string& host, \ const NetLogWithSource& net_log); \ - bool AddHSTSHeader(const NetworkIsolationKey& network_isolation_key, \ + bool AddHSTSHeader(const IsolationInfo& isolation_info, \ const std::string& host, const std::string& value); \ \ private: \ diff --git a/chromium_src/net/url_request/url_request_http_job.cc b/chromium_src/net/url_request/url_request_http_job.cc index 7a43624a5354..5e4b12db8c70 100644 --- a/chromium_src/net/url_request/url_request_http_job.cc +++ b/chromium_src/net/url_request/url_request_http_job.cc @@ -14,7 +14,7 @@ ShouldUpgradeToSSL(request->isolation_info().network_isolation_key(), host, \ net_log) #define AddHSTSHeader(host, value) \ - AddHSTSHeader(request_->isolation_info().network_isolation_key(), host, value) + AddHSTSHeader(request_->isolation_info(), host, value) #include "src/net/url_request/url_request_http_job.cc" From cc0d9a81a714deaea51c7fe4f8183cf4b8b6b01f Mon Sep 17 00:00:00 2001 From: Aleksey Khoroshilov Date: Mon, 16 May 2022 23:24:12 +0100 Subject: [PATCH 4/5] Add support for net-internals and old format reuse, add more tests. --- .../hsts_partitioning_browsertest.cc | 220 ++++++++++++- .../net/http/transport_security_persister.cc | 12 + .../net/http/transport_security_persister.h | 17 + .../net/http/transport_security_state.cc | 158 +++++++-- .../net/http/transport_security_state.h | 59 +++- net/BUILD.gn | 28 ++ net/http/partitioned_host_state_map.cc | 5 +- net/http/partitioned_host_state_map.h | 7 +- .../partitioned_host_state_map_unittest.cc | 142 +++++++++ net/http/transport_security_state_unittest.cc | 301 ++++++++++++++++++ net/proxy_resolution/BUILD.gn | 22 -- ...net-http-transport_security_state.cc.patch | 10 +- test/BUILD.gn | 2 +- 13 files changed, 901 insertions(+), 82 deletions(-) create mode 100644 chromium_src/net/http/transport_security_persister.cc create mode 100644 chromium_src/net/http/transport_security_persister.h create mode 100644 net/BUILD.gn create mode 100644 net/http/partitioned_host_state_map_unittest.cc create mode 100644 net/http/transport_security_state_unittest.cc delete mode 100644 net/proxy_resolution/BUILD.gn diff --git a/browser/ephemeral_storage/hsts_partitioning_browsertest.cc b/browser/ephemeral_storage/hsts_partitioning_browsertest.cc index bf6ba9e66f41..902fb66b2694 100644 --- a/browser/ephemeral_storage/hsts_partitioning_browsertest.cc +++ b/browser/ephemeral_storage/hsts_partitioning_browsertest.cc @@ -7,6 +7,7 @@ #include "base/strings/pattern.h" #include "base/strings/strcat.h" #include "base/strings/string_util.h" +#include "base/test/bind.h" #include "brave/components/brave_shields/browser/brave_shields_util.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" @@ -14,6 +15,7 @@ #include "chrome/common/chrome_paths.h" #include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/ui_test_utils.h" +#include "content/public/browser/storage_partition.h" #include "content/public/test/browser_test.h" #include "content/public/test/browser_test_utils.h" #include "content/public/test/content_mock_cert_verifier.h" @@ -22,6 +24,7 @@ #include "net/dns/mock_host_resolver.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "services/network/public/cpp/network_switches.h" +#include "services/network/public/mojom/network_context.mojom.h" #include "testing/gmock/include/gmock/gmock.h" #include "url/gurl.h" #include "url/origin.h" @@ -133,6 +136,60 @@ class HSTSPartitioningBrowserTestBase : public InProcessBrowserTest { } } + void NetworkContextAddHSTS(const std::string& host) { + content::StoragePartition* partition = + browser()->profile()->GetDefaultStoragePartition(); + base::RunLoop run_loop; + partition->GetNetworkContext()->AddHSTS( + host, base::Time::Now() + base::Days(1), false, run_loop.QuitClosure()); + run_loop.Run(); + } + + bool NetworkContextIsHSTSActiveForHost(const std::string& host) { + content::StoragePartition* partition = + browser()->profile()->GetDefaultStoragePartition(); + base::RunLoop run_loop; + bool result = false; + partition->GetNetworkContext()->IsHSTSActiveForHost( + host, + base::BindLambdaForTesting([&run_loop, &result](bool is_hsts_active) { + result = is_hsts_active; + run_loop.Quit(); + })); + run_loop.Run(); + return result; + } + + base::Value NetworkContextGetHSTSState(const std::string& host) { + content::StoragePartition* partition = + browser()->profile()->GetDefaultStoragePartition(); + base::RunLoop run_loop; + base::Value result; + partition->GetNetworkContext()->GetHSTSState( + host, + base::BindLambdaForTesting([&run_loop, &result](base::Value sts_state) { + result = std::move(sts_state); + run_loop.Quit(); + })); + run_loop.Run(); + return result; + } + + bool NetworkContextDeleteDynamicDataForHost(const std::string& host) { + content::StoragePartition* partition = + browser()->profile()->GetDefaultStoragePartition(); + base::RunLoop run_loop; + bool result = false; + partition->GetNetworkContext()->DeleteDynamicDataForHost( + host, + base::BindLambdaForTesting([&run_loop, &result](bool is_hsts_active) { + result = is_hsts_active; + run_loop.Quit(); + })); + run_loop.Run(); + return result; + } + protected: content::ContentMockCertVerifier mock_cert_verifier_; net::test_server::EmbeddedTestServer https_server_; @@ -225,6 +282,11 @@ IN_PROC_BROWSER_TEST_F(HSTSPartitioningEnabledBrowserTest, HSTSIsPartitioned) { ExpectHSTSState(c_com_inside_b_com_rfh, "b.com", false); ExpectHSTSState(c_com_inside_b_com_rfh, "c.com", false); } + + // No data should be available via unpartitioned API. + EXPECT_FALSE(NetworkContextIsHSTSActiveForHost("a.com")); + EXPECT_FALSE(NetworkContextIsHSTSActiveForHost("b.com")); + EXPECT_FALSE(NetworkContextIsHSTSActiveForHost("c.com")); } IN_PROC_BROWSER_TEST_F(HSTSPartitioningEnabledBrowserTest, @@ -246,6 +308,9 @@ IN_PROC_BROWSER_TEST_F(HSTSPartitioningEnabledBrowserTest, ASSERT_TRUE(sub_a_com_rfh); ExpectHSTSState(sub_a_com_rfh, "b.com", true); + + // No data should be available via unpartitioned API. + EXPECT_FALSE(NetworkContextIsHSTSActiveForHost("b.com")); } IN_PROC_BROWSER_TEST_F(HSTSPartitioningEnabledBrowserTest, @@ -265,6 +330,9 @@ IN_PROC_BROWSER_TEST_F(HSTSPartitioningEnabledBrowserTest, ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); ASSERT_TRUE(a_com_rfh2); EXPECT_TRUE(a_com_rfh2->GetLastCommittedURL().SchemeIsCryptographic()); + + // Unpartitioned API should see a.com HSTS state. + EXPECT_TRUE(NetworkContextIsHSTSActiveForHost("a.com")); } IN_PROC_BROWSER_TEST_F(HSTSPartitioningEnabledBrowserTest, HSTSIsCleared) { @@ -329,7 +397,7 @@ IN_PROC_BROWSER_TEST_F(HSTSPartitioningEnabledBrowserTest, } IN_PROC_BROWSER_TEST_F(HSTSPartitioningEnabledBrowserTest, - HSTSIsIgnoredInSandbox) { + HSTSIsStoredInSandbox) { GURL a_com_url( "http://a.com/set-header?" "Content-Security-Policy: sandbox allow-scripts"); @@ -338,11 +406,18 @@ IN_PROC_BROWSER_TEST_F(HSTSPartitioningEnabledBrowserTest, ExpectHSTSState(a_com_rfh, "a.com", false); SetHSTS(a_com_rfh, "a.com"); - ExpectHSTSState(a_com_rfh, "a.com", false); + ExpectHSTSState(a_com_rfh, "a.com", true); ExpectHSTSState(a_com_rfh, "b.com", false); SetHSTS(a_com_rfh, "b.com"); - ExpectHSTSState(a_com_rfh, "b.com", false); + ExpectHSTSState(a_com_rfh, "b.com", true); + + // Load a.com in another tab, expect HSTS is applied. + auto* a_com_rfh2 = ui_test_utils::NavigateToURLWithDisposition( + browser(), a_com_url, WindowOpenDisposition::NEW_FOREGROUND_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); + ASSERT_TRUE(a_com_rfh2); + EXPECT_TRUE(a_com_rfh2->GetLastCommittedURL().SchemeIsCryptographic()); } IN_PROC_BROWSER_TEST_F(HSTSPartitioningEnabledBrowserTest, @@ -365,6 +440,11 @@ IN_PROC_BROWSER_TEST_F(HSTSPartitioningEnabledBrowserTest, // Expect c.com HSTS state is also available in the main frame. ExpectHSTSState(a_com_rfh, "c.com", true); + + // No data should be available via unpartitioned API. + EXPECT_FALSE(NetworkContextIsHSTSActiveForHost("a.com")); + EXPECT_FALSE(NetworkContextIsHSTSActiveForHost("b.com")); + EXPECT_FALSE(NetworkContextIsHSTSActiveForHost("c.com")); } IN_PROC_BROWSER_TEST_F(HSTSPartitioningEnabledBrowserTest, @@ -382,12 +462,143 @@ IN_PROC_BROWSER_TEST_F(HSTSPartitioningEnabledBrowserTest, ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); ASSERT_TRUE(a_com_rfh); EXPECT_TRUE(a_com_rfh->GetLastCommittedURL().SchemeIsCryptographic()); + + EXPECT_TRUE(NetworkContextIsHSTSActiveForHost("a.com")); } IN_PROC_BROWSER_TEST_F(HSTSPartitioningEnabledBrowserTest, HSTSPreloadWorks) { ExpectPreloadWorks(); } +IN_PROC_BROWSER_TEST_F(HSTSPartitioningEnabledBrowserTest, NetworkContextAPI) { + // Load a.com and set b.com, c.bom HSTS inside a.com. + GURL a_com_url("http://a.com/iframe.html"); + auto* a_com_rfh = ui_test_utils::NavigateToURL(browser(), a_com_url); + ASSERT_TRUE(a_com_rfh); + EXPECT_EQ(a_com_rfh->GetLastCommittedURL(), a_com_url); + + SetHSTS(a_com_rfh, "a.com"); + ExpectHSTSState(a_com_rfh, "a.com", true); + EXPECT_TRUE(NetworkContextIsHSTSActiveForHost("a.com")); + EXPECT_TRUE( + NetworkContextGetHSTSState("a.com").FindIntKey("dynamic_upgrade_mode")); + + SetHSTS(a_com_rfh, "b.com"); + ExpectHSTSState(a_com_rfh, "b.com", true); + EXPECT_FALSE(NetworkContextIsHSTSActiveForHost("b.com")); + EXPECT_FALSE( + NetworkContextGetHSTSState("b.com").FindIntKey("dynamic_upgrade_mode")); + + GURL b_com_url("http://b.com/iframe.html"); + auto* b_com_rfh = ui_test_utils::NavigateToURLWithDisposition( + browser(), b_com_url, WindowOpenDisposition::NEW_FOREGROUND_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); + ASSERT_TRUE(b_com_rfh); + EXPECT_EQ(b_com_rfh->GetLastCommittedURL(), b_com_url); + + SetHSTS(b_com_rfh, "a.com"); + ExpectHSTSState(b_com_rfh, "a.com", true); + SetHSTS(b_com_rfh, "b.com"); + ExpectHSTSState(b_com_rfh, "b.com", true); + + EXPECT_TRUE(NetworkContextDeleteDynamicDataForHost("a.com")); + ExpectHSTSState(a_com_rfh, "a.com", false); + ExpectHSTSState(b_com_rfh, "a.com", false); + ExpectHSTSState(b_com_rfh, "b.com", true); +} + +IN_PROC_BROWSER_TEST_F(HSTSPartitioningEnabledBrowserTest, + NetworkContextAddHSTS) { + NetworkContextAddHSTS("sub.a.com"); + EXPECT_TRUE(NetworkContextIsHSTSActiveForHost("sub.a.com")); + EXPECT_TRUE(NetworkContextGetHSTSState("sub.a.com") + .FindIntKey("dynamic_upgrade_mode")); + + GURL a_com_url("http://sub.a.com/iframe.html"); + auto* a_com_rfh = ui_test_utils::NavigateToURL(browser(), a_com_url); + ASSERT_TRUE(a_com_rfh); + EXPECT_TRUE(a_com_rfh->GetLastCommittedURL().SchemeIsCryptographic()); +} + +// Test fixture to ensure a.com domain partitioned inside a.com will reuse old +// format HSTS data. +class HSTSSameDomainPartitionUsesOldFormatBrowserTest + : public HSTSPartitioningBrowserTestBase { + public: + HSTSSameDomainPartitionUsesOldFormatBrowserTest() { + if (IsPreTest()) { + scoped_feature_list_.InitAndDisableFeature( + net::features::kBravePartitionHSTS); + } else { + scoped_feature_list_.InitAndEnableFeature( + net::features::kBravePartitionHSTS); + } + } + + static bool IsPreTest() { + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + return base::StartsWith(test_info->name(), "PRE_"); + } + + private: + base::test::ScopedFeatureList scoped_feature_list_; +}; + +IN_PROC_BROWSER_TEST_F(HSTSSameDomainPartitionUsesOldFormatBrowserTest, + PRE_UnpartitionedHSTSIsUsed) { + GURL a_com_url("http://a.com/simple.html"); + auto* a_com_rfh = ui_test_utils::NavigateToURL(browser(), a_com_url); + ASSERT_TRUE(a_com_rfh); + EXPECT_EQ(a_com_rfh->GetLastCommittedURL(), a_com_url); + + ExpectHSTSState(a_com_rfh, "a.com", false); + // This will be stored without partitioning, becase the feature is disabled. + // key = hash(a.com). + SetHSTS(a_com_rfh, "a.com"); + ExpectHSTSState(a_com_rfh, "a.com", true); + EXPECT_TRUE(NetworkContextIsHSTSActiveForHost("a.com")); + + ExpectHSTSState(a_com_rfh, "sub.b.com", false); + // This will be stored without partitioning, becase the feature is disabled. + // key = hash(sub.b.com). + SetHSTS(a_com_rfh, "sub.b.com"); + ExpectHSTSState(a_com_rfh, "sub.b.com", true); + EXPECT_TRUE(NetworkContextIsHSTSActiveForHost("sub.b.com")); +} + +IN_PROC_BROWSER_TEST_F(HSTSSameDomainPartitionUsesOldFormatBrowserTest, + UnpartitionedHSTSIsUsed) { + // a.com should be HSTS-enabled, because it was stored in the old format for + // a.com host: key = hash(a.com). When partitioning is enabled and + // hashes for the domain and for the partition are same, we use domain hash + // directly without appending partition hash. + // This will try to look for key = hash(a.com). + GURL a_com_url("http://a.com/simple.html"); + auto* a_com_rfh = ui_test_utils::NavigateToURL(browser(), a_com_url); + ASSERT_TRUE(a_com_rfh); + EXPECT_TRUE(a_com_rfh->GetLastCommittedURL().SchemeIsCryptographic()); + EXPECT_TRUE(NetworkContextIsHSTSActiveForHost("a.com")); + + // This will try to look for key = hash(b.com). + // It should not have HSTS enabled, because no data was stored for the key. + GURL b_com_url("http://b.com/simple.html"); + auto* b_com_rfh = ui_test_utils::NavigateToURL(browser(), b_com_url); + ASSERT_TRUE(b_com_rfh); + EXPECT_FALSE(b_com_rfh->GetLastCommittedURL().SchemeIsCryptographic()); + EXPECT_FALSE(NetworkContextIsHSTSActiveForHost("b.com")); + + // This will try to look for key = hash(sub.b.com)/2 + hash(b.com)/2. + // It should not have HSTS enabled, because no data was stored for the key. + GURL sub_b_com_url("http://sub.b.com/simple.html"); + auto* sub_b_com_rfh = ui_test_utils::NavigateToURL(browser(), sub_b_com_url); + ASSERT_TRUE(sub_b_com_rfh); + EXPECT_FALSE(sub_b_com_rfh->GetLastCommittedURL().SchemeIsCryptographic()); + // This should return false, because new format looks into etldp1("sub.b.com") + // partition, which is equal to "b.com", but no such data was stored. + EXPECT_FALSE(NetworkContextIsHSTSActiveForHost("sub.b.com")); +} + class HSTSPartitioningDisabledBrowserTest : public HSTSPartitioningBrowserTestBase { public: @@ -409,14 +620,17 @@ IN_PROC_BROWSER_TEST_F(HSTSPartitioningDisabledBrowserTest, EXPECT_EQ(a_com_rfh->GetLastCommittedURL(), a_com_url); ExpectHSTSState(a_com_rfh, "a.com", false); + EXPECT_FALSE(NetworkContextIsHSTSActiveForHost("a.com")); ExpectHSTSState(a_com_rfh, "b.com", false); SetHSTS(a_com_rfh, "b.com"); ExpectHSTSState(a_com_rfh, "b.com", true); + EXPECT_TRUE(NetworkContextIsHSTSActiveForHost("b.com")); ExpectHSTSState(a_com_rfh, "c.com", false); SetHSTS(a_com_rfh, "c.com"); ExpectHSTSState(a_com_rfh, "c.com", true); + EXPECT_TRUE(NetworkContextIsHSTSActiveForHost("c.com")); // Load b.com in another tab and expect HSTS is applied. GURL b_com_url("http://b.com/simple.html"); diff --git a/chromium_src/net/http/transport_security_persister.cc b/chromium_src/net/http/transport_security_persister.cc new file mode 100644 index 000000000000..fd984fd2e37d --- /dev/null +++ b/chromium_src/net/http/transport_security_persister.cc @@ -0,0 +1,12 @@ +/* Copyright 2022 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "net/http/transport_security_persister.h" + +// Use upstream version of TransportSerurityState to reference +// TransportSecurityState::Delegate without build issues. +#define TransportSecurityState TransportSecurityState_ChromiumImpl +#include "src/net/http/transport_security_persister.cc" +#undef TransportSecurityState diff --git a/chromium_src/net/http/transport_security_persister.h b/chromium_src/net/http/transport_security_persister.h new file mode 100644 index 000000000000..da7b4905620b --- /dev/null +++ b/chromium_src/net/http/transport_security_persister.h @@ -0,0 +1,17 @@ +/* Copyright 2022 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_CHROMIUM_SRC_NET_HTTP_TRANSPORT_SECURITY_PERSISTER_H_ +#define BRAVE_CHROMIUM_SRC_NET_HTTP_TRANSPORT_SECURITY_PERSISTER_H_ + +#include "net/http/transport_security_state.h" + +// Use upstream version of TransportSerurityState to reference +// TransportSecurityState::Delegate without build issues. +#define TransportSecurityState TransportSecurityState_ChromiumImpl +#include "src/net/http/transport_security_persister.h" +#undef TransportSecurityState + +#endif // BRAVE_CHROMIUM_SRC_NET_HTTP_TRANSPORT_SECURITY_PERSISTER_H_ diff --git a/chromium_src/net/http/transport_security_state.cc b/chromium_src/net/http/transport_security_state.cc index d9914ab241ca..b39e0cb9339a 100644 --- a/chromium_src/net/http/transport_security_state.cc +++ b/chromium_src/net/http/transport_security_state.cc @@ -6,7 +6,10 @@ #include "net/http/transport_security_state.h" #include "build/build_config.h" +#include "net/base/network_isolation_key.h" +#include "net/base/registry_controlled_domains/registry_controlled_domain.h" #include "url/gurl.h" +#include "url/url_util.h" #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) @@ -22,27 +25,37 @@ #endif -#define BRAVE_TRANSPORT_SECURITY_STATE_DELETE_DYNAMIC_DATA_FOR_HOST \ - if (enabled_sts_hosts_.DeleteDataInAllPartitions(hashed_host)) { \ - deleted = true; \ - } +#define TransportSecurityState TransportSecurityState_ChromiumImpl #include "src/net/http/transport_security_state.cc" -#undef BRAVE_TRANSPORT_SECURITY_STATE_DELETE_DYNAMIC_DATA_FOR_HOST #undef BRAVE_ENABLE_STATIC_PINS +#undef TransportSecurityState + namespace net { // Non-anonymous helper to friend with SchemefulSite. class HSTSPartitionHashHelper { public: - static absl::optional GetSchemefulSiteRegistrableDomainOrHost( - const SchemefulSite& schemeful_site) { - if (!schemeful_site.has_registrable_domain_or_host()) { - return absl::nullopt; + static std::string GetPartitionDomain(const SchemefulSite& schemeful_site) { + DCHECK(base::FeatureList::IsEnabled(features::kBravePartitionHSTS)); + if (schemeful_site.has_registrable_domain_or_host()) { + return schemeful_site.registrable_domain_or_host(); + } + + if (schemeful_site.site_as_origin_.opaque()) { + std::string precursor_etld1_host = + registry_controlled_domains::GetDomainAndRegistry( + schemeful_site.site_as_origin_.GetTupleOrPrecursorTupleIfOpaque() + .host(), + registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); + if (!precursor_etld1_host.empty()) { + return precursor_etld1_host; + } } - return schemeful_site.registrable_domain_or_host(); + + return std::string(); } }; @@ -54,25 +67,29 @@ bool IsTopFrameOriginCryptographic(const IsolationInfo& isolation_info) { isolation_info.top_frame_origin()->scheme()); } -absl::optional GetHSTSPartitionHash( +std::string GetHSTSPartitionHash( const NetworkIsolationKey& network_isolation_key) { DCHECK(base::FeatureList::IsEnabled(features::kBravePartitionHSTS)); - // An empty or opaque top frame site cannot be used as a partition key, return - // a hash which will be treated as a non-persistable partition. - if (network_isolation_key.IsTransient() || - !network_isolation_key.GetTopFrameSite().has_value() || - network_isolation_key.GetTopFrameSite()->opaque()) { + // An empty top frame site cannot be used as a partition key, return an empty + // hash which will be treated as a non-persistable partition. + if (!network_isolation_key.GetTopFrameSite().has_value()) { return std::string(); } - absl::optional top_frame_registrable_domain_or_host = - HSTSPartitionHashHelper::GetSchemefulSiteRegistrableDomainOrHost( + const std::string partition_domain = + HSTSPartitionHashHelper::GetPartitionDomain( *network_isolation_key.GetTopFrameSite()); - if (!top_frame_registrable_domain_or_host) { + if (partition_domain.empty()) { return std::string(); } - return HashHost(*top_frame_registrable_domain_or_host); + const std::string canonicalized_partition_domain = + CanonicalizeHost(partition_domain); + if (canonicalized_partition_domain.empty()) { + return std::string(); + } + + return HashHost(canonicalized_partition_domain); } // Use only top frame site as a key for HSTS partitioning to not over-populate @@ -98,7 +115,8 @@ absl::optional GetPartitionHashForAddingHSTS( return GetHSTSPartitionHash(isolation_info.network_isolation_key()); } -absl::optional GetPartitionHashForReadingHSTS( +// Use NetworkIsolationKey to create PartitionHash for accessing/storing data. +absl::optional GetPartitionHashForHSTS( const NetworkIsolationKey& network_isolation_key) { if (!base::FeatureList::IsEnabled(features::kBravePartitionHSTS)) { return absl::nullopt; @@ -106,15 +124,28 @@ absl::optional GetPartitionHashForReadingHSTS( return GetHSTSPartitionHash(network_isolation_key); } +// Use host-bound NetworkIsolationKey in cases when no NetworkIsolationKey is +// available. Such cases may include net-internals page, PasswordManager. +// All network::NetworkContext HSTS-related public methods will use this. +absl::optional GetHostBoundPartitionHashForHSTS( + const std::string& host) { + if (!base::FeatureList::IsEnabled(features::kBravePartitionHSTS)) { + return absl::nullopt; + } + SchemefulSite schemeful_site(url::Origin::Create(GURL("https://" + host))); + NetworkIsolationKey network_isolation_key(schemeful_site, schemeful_site); + return GetHSTSPartitionHash(network_isolation_key); +} + } // namespace bool TransportSecurityState::ShouldSSLErrorsBeFatal( const NetworkIsolationKey& network_isolation_key, const std::string& host) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); - auto auto_reset_partition_hash = enabled_sts_hosts_.SetPartitionHash( - GetPartitionHashForReadingHSTS(network_isolation_key)); - return ShouldSSLErrorsBeFatal(host); + auto auto_reset_partition_hash = enabled_sts_hosts_.SetScopedPartitionHash( + GetPartitionHashForHSTS(network_isolation_key)); + return TransportSecurityState_ChromiumImpl::ShouldSSLErrorsBeFatal(host); } bool TransportSecurityState::ShouldUpgradeToSSL( @@ -122,18 +153,87 @@ bool TransportSecurityState::ShouldUpgradeToSSL( const std::string& host, const NetLogWithSource& net_log) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); - auto auto_reset_partition_hash = enabled_sts_hosts_.SetPartitionHash( - GetPartitionHashForReadingHSTS(network_isolation_key)); - return ShouldUpgradeToSSL(host, net_log); + auto auto_reset_partition_hash = enabled_sts_hosts_.SetScopedPartitionHash( + GetPartitionHashForHSTS(network_isolation_key)); + return TransportSecurityState_ChromiumImpl::ShouldUpgradeToSSL(host, net_log); } bool TransportSecurityState::AddHSTSHeader(const IsolationInfo& isolation_info, const std::string& host, const std::string& value) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); - auto auto_reset_partition_hash = enabled_sts_hosts_.SetPartitionHash( + auto auto_reset_partition_hash = enabled_sts_hosts_.SetScopedPartitionHash( GetPartitionHashForAddingHSTS(isolation_info)); - return AddHSTSHeader(host, value); + if (enabled_sts_hosts_.HasPartitionHash() && + !enabled_sts_hosts_.IsPartitionHashValid()) { + return false; + } + return TransportSecurityState_ChromiumImpl::AddHSTSHeader(host, value); +} + +bool TransportSecurityState::GetDynamicSTSState( + const NetworkIsolationKey& network_isolation_key, + const std::string& host, + STSState* result) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + auto auto_reset_partition_hash = enabled_sts_hosts_.SetScopedPartitionHash( + GetPartitionHashForHSTS(network_isolation_key)); + return TransportSecurityState_ChromiumImpl::GetDynamicSTSState(host, result); +} + +void TransportSecurityState::AddHSTS(const std::string& host, + const base::Time& expiry, + bool include_subdomains) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + auto auto_reset_partition_hash = enabled_sts_hosts_.SetScopedPartitionHash( + GetHostBoundPartitionHashForHSTS(host)); + TransportSecurityState_ChromiumImpl::AddHSTS(host, expiry, + include_subdomains); +} + +bool TransportSecurityState::ShouldSSLErrorsBeFatal(const std::string& host) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + auto auto_reset_partition_hash = enabled_sts_hosts_.SetScopedPartitionHash( + GetHostBoundPartitionHashForHSTS(host)); + return TransportSecurityState_ChromiumImpl::ShouldSSLErrorsBeFatal(host); +} + +bool TransportSecurityState::ShouldUpgradeToSSL( + const std::string& host, + const NetLogWithSource& net_log) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + auto auto_reset_partition_hash = enabled_sts_hosts_.SetScopedPartitionHash( + GetHostBoundPartitionHashForHSTS(host)); + return TransportSecurityState_ChromiumImpl::ShouldUpgradeToSSL(host, net_log); +} + +bool TransportSecurityState::GetDynamicSTSState(const std::string& host, + STSState* result) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + auto auto_reset_partition_hash = enabled_sts_hosts_.SetScopedPartitionHash( + GetHostBoundPartitionHashForHSTS(host)); + return TransportSecurityState_ChromiumImpl::GetDynamicSTSState(host, result); +} + +bool TransportSecurityState::DeleteDynamicDataForHost(const std::string& host) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + const bool chromium_deleted = + TransportSecurityState_ChromiumImpl::DeleteDynamicDataForHost(host); + + bool brave_deleted = false; + if (base::FeatureList::IsEnabled(features::kBravePartitionHSTS)) { + const std::string canonicalized_host = CanonicalizeHost(host); + if (!canonicalized_host.empty()) { + if (enabled_sts_hosts_.DeleteDataInAllPartitions( + HashHost(canonicalized_host))) { + brave_deleted = true; + } + } + } + + if (brave_deleted) + DirtyNotify(); + return chromium_deleted || brave_deleted; } } // namespace net diff --git a/chromium_src/net/http/transport_security_state.h b/chromium_src/net/http/transport_security_state.h index 7f407547b64c..5366531638de 100644 --- a/chromium_src/net/http/transport_security_state.h +++ b/chromium_src/net/http/transport_security_state.h @@ -9,24 +9,55 @@ #include "brave/net/http/partitioned_host_state_map.h" #include "net/base/isolation_info.h" -#define enabled_sts_hosts_ \ - enabled_sts_hosts_unused_; \ - \ - public: \ - bool ShouldSSLErrorsBeFatal( \ - const NetworkIsolationKey& network_isolation_key, \ - const std::string& host); \ - bool ShouldUpgradeToSSL(const NetworkIsolationKey& network_isolation_key, \ - const std::string& host, \ - const NetLogWithSource& net_log); \ - bool AddHSTSHeader(const IsolationInfo& isolation_info, \ - const std::string& host, const std::string& value); \ - \ - private: \ +namespace net { +class TransportSecurityState; +using TransportSecurityState_BraveImpl = TransportSecurityState; +} // namespace net + +#define TransportSecurityState TransportSecurityState_ChromiumImpl + +#define enabled_sts_hosts_ \ + enabled_sts_hosts_unused_; \ + friend TransportSecurityState_BraveImpl; \ PartitionedHostStateMap enabled_sts_hosts_ #include "src/net/http/transport_security_state.h" #undef enabled_sts_hosts_ +#undef TransportSecurityState + +namespace net { + +class NET_EXPORT TransportSecurityState + : public TransportSecurityState_ChromiumImpl { + public: + using TransportSecurityState_ChromiumImpl:: + TransportSecurityState_ChromiumImpl; + + bool ShouldSSLErrorsBeFatal(const NetworkIsolationKey& network_isolation_key, + const std::string& host); + bool ShouldUpgradeToSSL(const NetworkIsolationKey& network_isolation_key, + const std::string& host, + const NetLogWithSource& net_log = NetLogWithSource()); + bool AddHSTSHeader(const IsolationInfo& isolation_info, + const std::string& host, + const std::string& value); + bool GetDynamicSTSState(const NetworkIsolationKey& network_isolation_key, + const std::string& host, + STSState* result); + + // This is used only for manual addiing via net-internals page. + void AddHSTS(const std::string& host, + const base::Time& expiry, + bool include_subdomains); + // These are used in some places where no NIK is available. + bool ShouldSSLErrorsBeFatal(const std::string& host); + bool ShouldUpgradeToSSL(const std::string& host, + const NetLogWithSource& net_log = NetLogWithSource()); + bool GetDynamicSTSState(const std::string& host, STSState* result); + bool DeleteDynamicDataForHost(const std::string& host); +}; + +} // namespace net #endif // BRAVE_CHROMIUM_SRC_NET_HTTP_TRANSPORT_SECURITY_STATE_H_ diff --git a/net/BUILD.gn b/net/BUILD.gn new file mode 100644 index 000000000000..cd864fce937f --- /dev/null +++ b/net/BUILD.gn @@ -0,0 +1,28 @@ +import("//brave/components/tor/buildflags/buildflags.gni") + +source_set("unit_tests") { + testonly = true + sources = [ + "http/partitioned_host_state_map_unittest.cc", + "http/transport_security_state_unittest.cc", + ] + + if (enable_tor) { + sources += [ + "proxy_resolution/configured_proxy_resolution_service_unittest.cc", + "proxy_resolution/proxy_config_service_tor_unittest.cc", + ] + } + + deps = [ + "//base", + "//base/test:test_support", + "//net", + "//net:test_support", + "//net/http:transport_security_state_unittest_data", + "//net/http:transport_security_state_unittest_data_default", + "//net/tools/huffman_trie:huffman_trie_generator_sources", + "//testing/gtest", + "//url", + ] +} diff --git a/net/http/partitioned_host_state_map.cc b/net/http/partitioned_host_state_map.cc index 6befcac8eefc..2fac4a219a42 100644 --- a/net/http/partitioned_host_state_map.cc +++ b/net/http/partitioned_host_state_map.cc @@ -17,7 +17,7 @@ PartitionedHostStateMapBase::PartitionedHostStateMapBase() = default; PartitionedHostStateMapBase::~PartitionedHostStateMapBase() = default; base::AutoReset> -PartitionedHostStateMapBase::SetPartitionHash( +PartitionedHostStateMapBase::SetScopedPartitionHash( absl::optional partition_hash) { CHECK(!partition_hash || partition_hash->empty() || partition_hash->size() == crypto::kSHA256Length); @@ -36,6 +36,9 @@ bool PartitionedHostStateMapBase::IsPartitionHashValid() const { std::string PartitionedHostStateMapBase::GetKeyWithPartitionHash( const std::string& k) const { CHECK(IsPartitionHashValid()); + if (k == *partition_hash_) { + return k; + } return base::StrCat({GetHalfKey(k), GetHalfKey(*partition_hash_)}); } diff --git a/net/http/partitioned_host_state_map.h b/net/http/partitioned_host_state_map.h index e5f8142db8ab..32bf12f76108 100644 --- a/net/http/partitioned_host_state_map.h +++ b/net/http/partitioned_host_state_map.h @@ -11,12 +11,13 @@ #include "base/auto_reset.h" #include "base/ranges/algorithm.h" #include "base/strings/string_piece.h" +#include "net/base/net_export.h" #include "third_party/abseil-cpp/absl/types/optional.h" namespace net { // Implements partitioning support for structures in TransportSecurityState. -class PartitionedHostStateMapBase { +class NET_EXPORT PartitionedHostStateMapBase { public: PartitionedHostStateMapBase(); ~PartitionedHostStateMapBase(); @@ -26,7 +27,7 @@ class PartitionedHostStateMapBase { delete; // Stores scoped partition hash for use in subsequent calls. - base::AutoReset> SetPartitionHash( + base::AutoReset> SetScopedPartitionHash( absl::optional partition_hash); // Returns true if |partition_hash_| is set. The value may be empty. bool HasPartitionHash() const; @@ -52,7 +53,7 @@ class PartitionedHostStateMapBase { // std::map interface just enough to replace unpartitioned maps in // TransportSecurityState. template -class PartitionedHostStateMap : public PartitionedHostStateMapBase { +class NET_EXPORT PartitionedHostStateMap : public PartitionedHostStateMapBase { public: using iterator = typename T::iterator; using const_iterator = typename T::const_iterator; diff --git a/net/http/partitioned_host_state_map_unittest.cc b/net/http/partitioned_host_state_map_unittest.cc new file mode 100644 index 000000000000..d9c60d18aaf7 --- /dev/null +++ b/net/http/partitioned_host_state_map_unittest.cc @@ -0,0 +1,142 @@ +/* Copyright 2022 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/net/http/partitioned_host_state_map.h" + +#include +#include + +#include "crypto/sha2.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +using PartitionedMap = + PartitionedHostStateMap>; + +std::string HashHost(base::StringPiece canonicalized_host) { + char hashed[crypto::kSHA256Length]; + crypto::SHA256HashString(canonicalized_host, hashed, sizeof(hashed)); + return std::string(hashed, sizeof(hashed)); +} + +} // namespace + +TEST(PartitionedHostStateMapTest, WithoutPartitionHash) { + PartitionedMap map; + EXPECT_FALSE(map.HasPartitionHash()); + EXPECT_FALSE(map.IsPartitionHashValid()); + + map[HashHost("key1")] = "1"; + map[HashHost("key2")] = "2"; + EXPECT_EQ(map.size(), 2u); + + EXPECT_EQ(map.find(HashHost("key1")), map.begin()); + EXPECT_EQ(map.find(HashHost("key1"))->second, "1"); + EXPECT_EQ(map.find(HashHost("key2"))->second, "2"); + EXPECT_EQ(map.find(HashHost("key3")), map.end()); + + map.erase(HashHost("key1")); + EXPECT_EQ(map.size(), 1u); + map.erase(HashHost("key2")); + EXPECT_EQ(map.size(), 0u); + + EXPECT_FALSE(map.DeleteDataInAllPartitions(HashHost("key1"))); +} + +TEST(PartitionedHostStateMapTest, InvalidPartitionHash) { + PartitionedMap map; + // Empty string is an invalid partition. It means it should not be persisted. + auto auto_reset_partition_hash = map.SetScopedPartitionHash(""); + EXPECT_TRUE(map.HasPartitionHash()); + EXPECT_FALSE(map.IsPartitionHashValid()); + + // Nothing should be persisted when an invalid hash is set. + map[HashHost("key1")] = "1"; + map[HashHost("key2")] = "2"; + EXPECT_EQ(map.size(), 0u); + + EXPECT_EQ(map.find(HashHost("key1")), map.end()); + EXPECT_EQ(map.find(HashHost("key2")), map.end()); + EXPECT_EQ(map.find(HashHost("key3")), map.end()); + + map.erase(HashHost("key1")); + EXPECT_EQ(map.size(), 0u); + map.erase(HashHost("key2")); + EXPECT_EQ(map.size(), 0u); + + EXPECT_FALSE(map.DeleteDataInAllPartitions(HashHost("key1"))); +} + +TEST(PartitionedHostStateMapTest, ValidPartitionHash) { + PartitionedMap map; + auto auto_reset_partition_hash = + map.SetScopedPartitionHash(HashHost("partition1")); + EXPECT_TRUE(map.HasPartitionHash()); + EXPECT_TRUE(map.IsPartitionHashValid()); + + map[HashHost("key1")] = "11"; + map[HashHost("key2")] = "12"; + EXPECT_EQ(map.size(), 2u); + + EXPECT_EQ(map.find(HashHost("key1"))->second, "11"); + EXPECT_EQ(map.find(HashHost("key2"))->second, "12"); + EXPECT_EQ(map.find(HashHost("key3")), map.end()); + + map.erase(HashHost("key1")); + EXPECT_EQ(map.size(), 1u); + map.erase(HashHost("key2")); + EXPECT_EQ(map.size(), 0u); + + EXPECT_FALSE(map.DeleteDataInAllPartitions(HashHost("key1"))); +} + +TEST(PartitionedHostStateMapTest, MultiplePartitions) { + PartitionedMap map; + auto auto_reset_partition_hash = + map.SetScopedPartitionHash(HashHost("partition1")); + + map[HashHost("key1")] = "11"; + map[HashHost("key2")] = "12"; + EXPECT_EQ(map.size(), 2u); + EXPECT_EQ(map.find(HashHost("key1"))->second, "11"); + EXPECT_EQ(map.find(HashHost("key2"))->second, "12"); + EXPECT_EQ(map.find(HashHost("key3")), map.end()); + + auto_reset_partition_hash = + map.SetScopedPartitionHash(HashHost("partition2")); + map[HashHost("key1")] = "21"; + map[HashHost("key2")] = "22"; + EXPECT_EQ(map.size(), 4u); + + EXPECT_EQ(map.find(HashHost("key1"))->second, "21"); + EXPECT_EQ(map.find(HashHost("key2"))->second, "22"); + EXPECT_EQ(map.find(HashHost("key3")), map.end()); + + EXPECT_EQ(map.erase(HashHost("key2")), 1u); + EXPECT_EQ(map.size(), 3u); + + auto_reset_partition_hash = + map.SetScopedPartitionHash(HashHost("partition3")); + EXPECT_EQ(map.find(HashHost("key1")), map.end()); + EXPECT_EQ(map.find(HashHost("key3")), map.end()); + EXPECT_EQ(map.find(HashHost("key2")), map.end()); + + EXPECT_EQ(map.erase(HashHost("key2")), 0u); + EXPECT_EQ(map.size(), 3u); + + // Should delete key1 in partition1 and partition2. + EXPECT_TRUE(map.DeleteDataInAllPartitions(HashHost("key1"))); + EXPECT_EQ(map.size(), 1u); + + auto_reset_partition_hash = + map.SetScopedPartitionHash(HashHost("partition1")); + EXPECT_EQ(map.find(HashHost("key1")), map.end()); + EXPECT_EQ(map.find(HashHost("key2"))->second, "12"); +} + +} // namespace net diff --git a/net/http/transport_security_state_unittest.cc b/net/http/transport_security_state_unittest.cc new file mode 100644 index 000000000000..45e25db2c918 --- /dev/null +++ b/net/http/transport_security_state_unittest.cc @@ -0,0 +1,301 @@ +/* Copyright 2022 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "net/http/transport_security_state.h" + +#include "base/test/scoped_feature_list.h" +#include "net/base/features.h" +#include "net/base/isolation_info.h" +#include "net/base/network_isolation_key.h" +#include "net/base/schemeful_site.h" +#include "net/test/test_with_task_environment.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/origin.h" + +namespace net { + +namespace { + +const char kHSTSHeaderValue[] = "max-age=600000"; + +} // namespace + +class TransportSecurityStateTestBase : public ::testing::Test, + public WithTaskEnvironment { + public: + TransportSecurityStateTestBase() + : WithTaskEnvironment( + base::test::TaskEnvironment::TimeSource::MOCK_TIME) { + // Need mocked out time for pruning tests. Don't start with a + // time of 0, as code doesn't generally expect it. + FastForwardBy(base::Days(1)); + } + + static IsolationInfo CreateIsolationInfo( + const url::Origin& top_frame_origin, + const SiteForCookies& site_for_cookies) { + return IsolationInfo::Create(IsolationInfo::RequestType::kMainFrame, + top_frame_origin, top_frame_origin, + site_for_cookies); + } + + static NetworkIsolationKey CreateNetworkIsolationKey( + const url::Origin& top_frame_origin) { + SchemefulSite schemeful_site(top_frame_origin); + return NetworkIsolationKey(schemeful_site, schemeful_site); + } + + void ExpectNoHSTS(TransportSecurityState* state, + const NetworkIsolationKey& network_isolation_key, + const std::string& host) { + SCOPED_TRACE(testing::Message() + << network_isolation_key.ToDebugString() << " host: " << host); + EXPECT_FALSE(state->ShouldUpgradeToSSL(host)); + EXPECT_FALSE(state->ShouldUpgradeToSSL(network_isolation_key, host)); + EXPECT_FALSE(state->ShouldSSLErrorsBeFatal(host)); + EXPECT_FALSE(state->ShouldSSLErrorsBeFatal(network_isolation_key, host)); + TransportSecurityState::STSState dynamic_sts_state; + EXPECT_FALSE(state->GetDynamicSTSState(host, &dynamic_sts_state)); + EXPECT_FALSE(state->GetDynamicSTSState(network_isolation_key, host, + &dynamic_sts_state)); + } + + void ExpectHasHSTS(TransportSecurityState* state, + const NetworkIsolationKey& network_isolation_key, + const std::string& host) { + SCOPED_TRACE(testing::Message() + << network_isolation_key.ToDebugString() << " host: " << host); + EXPECT_TRUE(state->ShouldUpgradeToSSL(host)); + EXPECT_TRUE(state->ShouldUpgradeToSSL(network_isolation_key, host)); + EXPECT_TRUE(state->ShouldSSLErrorsBeFatal(host)); + EXPECT_TRUE(state->ShouldSSLErrorsBeFatal(network_isolation_key, host)); + { + TransportSecurityState::STSState dynamic_sts_state; + EXPECT_TRUE(state->GetDynamicSTSState(host, &dynamic_sts_state)); + EXPECT_EQ(dynamic_sts_state.upgrade_mode, + TransportSecurityState::STSState::MODE_FORCE_HTTPS); + } + { + TransportSecurityState::STSState dynamic_sts_state; + EXPECT_TRUE(state->GetDynamicSTSState(network_isolation_key, host, + &dynamic_sts_state)); + EXPECT_EQ(dynamic_sts_state.upgrade_mode, + TransportSecurityState::STSState::MODE_FORCE_HTTPS); + } + } + + void ExpectHasHSTSOnlyWithNIK( + TransportSecurityState* state, + const NetworkIsolationKey& network_isolation_key, + const std::string& host) { + SCOPED_TRACE(testing::Message() + << network_isolation_key.ToDebugString() << " host: " << host); + EXPECT_FALSE(state->ShouldUpgradeToSSL(host)); + EXPECT_TRUE(state->ShouldUpgradeToSSL(network_isolation_key, host)); + EXPECT_FALSE(state->ShouldSSLErrorsBeFatal(host)); + EXPECT_TRUE(state->ShouldSSLErrorsBeFatal(network_isolation_key, host)); + { + TransportSecurityState::STSState dynamic_sts_state; + EXPECT_FALSE(state->GetDynamicSTSState(host, &dynamic_sts_state)); + } + { + TransportSecurityState::STSState dynamic_sts_state; + EXPECT_TRUE(state->GetDynamicSTSState(network_isolation_key, host, + &dynamic_sts_state)); + EXPECT_EQ(dynamic_sts_state.upgrade_mode, + TransportSecurityState::STSState::MODE_FORCE_HTTPS); + } + } +}; + +class TransportSecurityState_DisableHSTSPartitionTest + : public TransportSecurityStateTestBase { + public: + TransportSecurityState_DisableHSTSPartitionTest() { + scoped_feature_list_.InitAndDisableFeature(features::kBravePartitionHSTS); + } + + protected: + base::test::ScopedFeatureList scoped_feature_list_; +}; + +TEST_F(TransportSecurityState_DisableHSTSPartitionTest, + UnpartitionedAddHSTSHeader) { + TransportSecurityState state; + + auto a_com_origin = url::Origin::Create(GURL("https://a.com")); + + ExpectNoHSTS(&state, NetworkIsolationKey(), "a.com"); + ExpectNoHSTS(&state, NetworkIsolationKey(), "b.com"); + + // Simulate valid IsolationInfo (it shouldn't be used anyways). + EXPECT_TRUE(state.AddHSTSHeader( + CreateIsolationInfo(a_com_origin, + SiteForCookies::FromOrigin(a_com_origin)), + "a.com", kHSTSHeaderValue)); + // Simulate invalid IsolationInfo (it shouldn't be used anyways). + EXPECT_TRUE(state.AddHSTSHeader(IsolationInfo(), "b.com", kHSTSHeaderValue)); + + ExpectHasHSTS(&state, NetworkIsolationKey(), "a.com"); + ExpectHasHSTS(&state, NetworkIsolationKey(), "b.com"); +} + +TEST_F(TransportSecurityState_DisableHSTSPartitionTest, + UnpartitionedDeleteDynamicDataForHost) { + TransportSecurityState state; + + EXPECT_TRUE(state.AddHSTSHeader(IsolationInfo(), "a.com", kHSTSHeaderValue)); + EXPECT_TRUE(state.AddHSTSHeader(IsolationInfo(), "b.com", kHSTSHeaderValue)); + ExpectHasHSTS(&state, NetworkIsolationKey(), "a.com"); + ExpectHasHSTS(&state, NetworkIsolationKey(), "b.com"); + + EXPECT_TRUE(state.DeleteDynamicDataForHost("a.com")); + ExpectNoHSTS(&state, NetworkIsolationKey(), "a.com"); + ExpectHasHSTS(&state, NetworkIsolationKey(), "b.com"); + + // Second time shouldn't delete anything. + EXPECT_FALSE(state.DeleteDynamicDataForHost("a.com")); + ExpectHasHSTS(&state, NetworkIsolationKey(), "b.com"); +} + +class TransportSecurityState_EnableHSTSPartitionTest + : public TransportSecurityStateTestBase { + public: + TransportSecurityState_EnableHSTSPartitionTest() { + scoped_feature_list_.InitAndEnableFeature(features::kBravePartitionHSTS); + } + + protected: + base::test::ScopedFeatureList scoped_feature_list_; +}; + +TEST_F(TransportSecurityState_EnableHSTSPartitionTest, + PartitionedAddHSTSHeader) { + base::test::ScopedFeatureList scoped_feature_list( + features::kBravePartitionHSTS); + TransportSecurityState state; + + auto a_com_origin = url::Origin::Create(GURL("https://a.com")); + auto b_com_origin = url::Origin::Create(GURL("https://b.com")); + + ExpectNoHSTS(&state, NetworkIsolationKey(), "a.com"); + ExpectNoHSTS(&state, NetworkIsolationKey(), "bbb.com"); + + // Add a.com record on a.com frame. + EXPECT_TRUE(state.AddHSTSHeader( + CreateIsolationInfo(a_com_origin, + SiteForCookies::FromOrigin(a_com_origin)), + "a.com", kHSTSHeaderValue)); + // Try to add b.com on invalid partition. + EXPECT_FALSE(state.AddHSTSHeader(IsolationInfo(), "b.com", kHSTSHeaderValue)); + // Add a.com on b.com frame. + EXPECT_TRUE(state.AddHSTSHeader( + CreateIsolationInfo(b_com_origin, + SiteForCookies::FromOrigin(b_com_origin)), + "a.com", kHSTSHeaderValue)); + // Add bbb.com on b.com frame. + EXPECT_TRUE(state.AddHSTSHeader( + CreateIsolationInfo(b_com_origin, + SiteForCookies::FromOrigin(b_com_origin)), + "bbb.com", kHSTSHeaderValue)); + + ExpectHasHSTS(&state, CreateNetworkIsolationKey(a_com_origin), "a.com"); + ExpectNoHSTS(&state, NetworkIsolationKey(), "b.com"); + ExpectNoHSTS(&state, CreateNetworkIsolationKey(b_com_origin), "b.com"); + // Partitioned values should be available on b.com frame. + ExpectHasHSTS(&state, CreateNetworkIsolationKey(b_com_origin), "a.com"); + ExpectHasHSTSOnlyWithNIK(&state, CreateNetworkIsolationKey(b_com_origin), + "bbb.com"); +} + +TEST_F(TransportSecurityState_EnableHSTSPartitionTest, + PartitionedSaveAllHSTSOnHTTP) { + base::test::ScopedFeatureList scoped_feature_list( + features::kBravePartitionHSTS); + TransportSecurityState state; + + auto a_com_origin = url::Origin::Create(GURL("http://a.com")); + + EXPECT_TRUE(state.AddHSTSHeader( + CreateIsolationInfo(a_com_origin, + SiteForCookies::FromOrigin(a_com_origin)), + "a.com", kHSTSHeaderValue)); + EXPECT_TRUE( + state.AddHSTSHeader(CreateIsolationInfo(a_com_origin, SiteForCookies()), + "b.com", kHSTSHeaderValue)); + + ExpectHasHSTS(&state, CreateNetworkIsolationKey(a_com_origin), "a.com"); + ExpectHasHSTSOnlyWithNIK(&state, CreateNetworkIsolationKey(a_com_origin), + "b.com"); +} + +TEST_F(TransportSecurityState_EnableHSTSPartitionTest, + PartitionedSaveHSTSForOnlyMatchedSameSiteForCookiesOnHTTPS) { + base::test::ScopedFeatureList scoped_feature_list( + features::kBravePartitionHSTS); + TransportSecurityState state; + + auto a_com_origin = url::Origin::Create(GURL("https://a.com")); + + EXPECT_TRUE(state.AddHSTSHeader( + CreateIsolationInfo(a_com_origin, + SiteForCookies::FromOrigin(a_com_origin)), + "a.com", kHSTSHeaderValue)); + EXPECT_FALSE( + state.AddHSTSHeader(CreateIsolationInfo(a_com_origin, SiteForCookies()), + "b.com", kHSTSHeaderValue)); + + ExpectHasHSTS(&state, CreateNetworkIsolationKey(a_com_origin), "a.com"); + ExpectNoHSTS(&state, CreateNetworkIsolationKey(a_com_origin), "b.com"); +} + +TEST_F(TransportSecurityState_EnableHSTSPartitionTest, + PartitionedDeleteDynamicDataForHost) { + TransportSecurityState state; + + auto a_com_origin = url::Origin::Create(GURL("https://a.com")); + auto b_com_origin = url::Origin::Create(GURL("https://b.com")); + auto c_com_origin = url::Origin::Create(GURL("https://c.com")); + + EXPECT_TRUE(state.AddHSTSHeader( + CreateIsolationInfo(b_com_origin, + SiteForCookies::FromOrigin(b_com_origin)), + "a.com", kHSTSHeaderValue)); + EXPECT_TRUE(state.AddHSTSHeader( + CreateIsolationInfo(c_com_origin, + SiteForCookies::FromOrigin(c_com_origin)), + "a.com", kHSTSHeaderValue)); + ExpectNoHSTS(&state, NetworkIsolationKey(), "a.com"); + ExpectNoHSTS(&state, CreateNetworkIsolationKey(a_com_origin), "a.com"); + + ExpectHasHSTSOnlyWithNIK(&state, CreateNetworkIsolationKey(b_com_origin), + "a.com"); + ExpectHasHSTSOnlyWithNIK(&state, CreateNetworkIsolationKey(c_com_origin), + "a.com"); + + EXPECT_TRUE(state.AddHSTSHeader( + CreateIsolationInfo(a_com_origin, + SiteForCookies::FromOrigin(a_com_origin)), + "a.com", kHSTSHeaderValue)); + ExpectHasHSTS(&state, CreateNetworkIsolationKey(a_com_origin), "a.com"); + + EXPECT_TRUE(state.AddHSTSHeader( + CreateIsolationInfo(b_com_origin, + SiteForCookies::FromOrigin(b_com_origin)), + "b.com", kHSTSHeaderValue)); + ExpectHasHSTS(&state, CreateNetworkIsolationKey(b_com_origin), "b.com"); + + EXPECT_TRUE(state.DeleteDynamicDataForHost("a.com")); + ExpectNoHSTS(&state, CreateNetworkIsolationKey(a_com_origin), "a.com"); + ExpectNoHSTS(&state, CreateNetworkIsolationKey(b_com_origin), "a.com"); + ExpectNoHSTS(&state, CreateNetworkIsolationKey(c_com_origin), "a.com"); + ExpectHasHSTS(&state, CreateNetworkIsolationKey(b_com_origin), "b.com"); + + // Second time shouldn't delete anything. + EXPECT_FALSE(state.DeleteDynamicDataForHost("a.com")); + ExpectHasHSTS(&state, CreateNetworkIsolationKey(b_com_origin), "b.com"); +} + +} // namespace net diff --git a/net/proxy_resolution/BUILD.gn b/net/proxy_resolution/BUILD.gn deleted file mode 100644 index 5b6a2fba1983..000000000000 --- a/net/proxy_resolution/BUILD.gn +++ /dev/null @@ -1,22 +0,0 @@ -import("//brave/components/tor/buildflags/buildflags.gni") - -source_set("unit_tests") { - testonly = true - if (enable_tor) { - sources = [ - "configured_proxy_resolution_service_unittest.cc", - "proxy_config_service_tor_unittest.cc", - ] - - deps = [ - "//base", - "//base/test:test_support", - "//content/public/browser", - "//content/test:test_support", - "//net", - "//net:test_support", - "//testing/gtest", - "//url", - ] - } -} diff --git a/patches/net-http-transport_security_state.cc.patch b/patches/net-http-transport_security_state.cc.patch index 226f68f88f94..9d565bdfae4e 100644 --- a/patches/net-http-transport_security_state.cc.patch +++ b/patches/net-http-transport_security_state.cc.patch @@ -1,5 +1,5 @@ diff --git a/net/http/transport_security_state.cc b/net/http/transport_security_state.cc -index 340b2e49df29ac57b2f827836bdcd3a4c63da279..f329508de8e0407126d616bf7c547dedd84a17ad 100644 +index 340b2e49df29ac57b2f827836bdcd3a4c63da279..6e014a9e54fdfcaef7ce32f0f03f2420ed1dbf94 100644 --- a/net/http/transport_security_state.cc +++ b/net/http/transport_security_state.cc @@ -417,6 +417,7 @@ TransportSecurityState::TransportSecurityState( @@ -10,11 +10,3 @@ index 340b2e49df29ac57b2f827836bdcd3a4c63da279..f329508de8e0407126d616bf7c547ded #endif // Check that there no invalid entries in the static HSTS bypass list. for (auto& host : hsts_host_bypass_list) { -@@ -881,6 +882,7 @@ bool TransportSecurityState::DeleteDynamicDataForHost(const std::string& host) { - enabled_sts_hosts_.erase(sts_interator); - deleted = true; - } -+ BRAVE_TRANSPORT_SECURITY_STATE_DELETE_DYNAMIC_DATA_FOR_HOST - - auto pkp_iterator = enabled_pkp_hosts_.find(hashed_host); - if (pkp_iterator != enabled_pkp_hosts_.end()) { diff --git a/test/BUILD.gn b/test/BUILD.gn index 8c5bea6304e8..7375ba2b77b1 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -244,7 +244,7 @@ test("brave_unit_tests") { "//brave/components/weekly_storage", "//brave/extensions:common", "//brave/mojo/brave_ast_patcher:unit_tests", - "//brave/net/proxy_resolution:unit_tests", + "//brave/net:unit_tests", "//brave/third_party/blink/renderer:renderer", "//brave/vendor/bat-native-ledger/test:bat_native_ledger_tests", "//brave/vendor/brave_base", From 2d6ed5bbe911abe214cdef4b1d4c2d5454ca0c33 Mon Sep 17 00:00:00 2001 From: Aleksey Khoroshilov Date: Thu, 26 May 2022 17:05:51 +0700 Subject: [PATCH 5/5] Fix review comments, remove unused function. --- .../net/http/transport_security_state.cc | 10 ---------- chromium_src/net/http/transport_security_state.h | 5 +---- net/http/partitioned_host_state_map.cc | 2 +- net/http/transport_security_state_unittest.cc | 16 ---------------- 4 files changed, 2 insertions(+), 31 deletions(-) diff --git a/chromium_src/net/http/transport_security_state.cc b/chromium_src/net/http/transport_security_state.cc index b39e0cb9339a..6920123c8eb0 100644 --- a/chromium_src/net/http/transport_security_state.cc +++ b/chromium_src/net/http/transport_security_state.cc @@ -171,16 +171,6 @@ bool TransportSecurityState::AddHSTSHeader(const IsolationInfo& isolation_info, return TransportSecurityState_ChromiumImpl::AddHSTSHeader(host, value); } -bool TransportSecurityState::GetDynamicSTSState( - const NetworkIsolationKey& network_isolation_key, - const std::string& host, - STSState* result) { - DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); - auto auto_reset_partition_hash = enabled_sts_hosts_.SetScopedPartitionHash( - GetPartitionHashForHSTS(network_isolation_key)); - return TransportSecurityState_ChromiumImpl::GetDynamicSTSState(host, result); -} - void TransportSecurityState::AddHSTS(const std::string& host, const base::Time& expiry, bool include_subdomains) { diff --git a/chromium_src/net/http/transport_security_state.h b/chromium_src/net/http/transport_security_state.h index 5366531638de..ae78cdbd3b41 100644 --- a/chromium_src/net/http/transport_security_state.h +++ b/chromium_src/net/http/transport_security_state.h @@ -42,11 +42,8 @@ class NET_EXPORT TransportSecurityState bool AddHSTSHeader(const IsolationInfo& isolation_info, const std::string& host, const std::string& value); - bool GetDynamicSTSState(const NetworkIsolationKey& network_isolation_key, - const std::string& host, - STSState* result); - // This is used only for manual addiing via net-internals page. + // This is used only for manual adding via net-internals page. void AddHSTS(const std::string& host, const base::Time& expiry, bool include_subdomains); diff --git a/net/http/partitioned_host_state_map.cc b/net/http/partitioned_host_state_map.cc index 2fac4a219a42..5a90cca8c1f5 100644 --- a/net/http/partitioned_host_state_map.cc +++ b/net/http/partitioned_host_state_map.cc @@ -1,4 +1,4 @@ -/* Copyright 2021 The Brave Authors. All rights reserved. +/* Copyright 2022 The Brave Authors. All rights reserved. * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at https://mozilla.org/MPL/2.0/. */ diff --git a/net/http/transport_security_state_unittest.cc b/net/http/transport_security_state_unittest.cc index 45e25db2c918..f66413a2523a 100644 --- a/net/http/transport_security_state_unittest.cc +++ b/net/http/transport_security_state_unittest.cc @@ -58,8 +58,6 @@ class TransportSecurityStateTestBase : public ::testing::Test, EXPECT_FALSE(state->ShouldSSLErrorsBeFatal(network_isolation_key, host)); TransportSecurityState::STSState dynamic_sts_state; EXPECT_FALSE(state->GetDynamicSTSState(host, &dynamic_sts_state)); - EXPECT_FALSE(state->GetDynamicSTSState(network_isolation_key, host, - &dynamic_sts_state)); } void ExpectHasHSTS(TransportSecurityState* state, @@ -77,13 +75,6 @@ class TransportSecurityStateTestBase : public ::testing::Test, EXPECT_EQ(dynamic_sts_state.upgrade_mode, TransportSecurityState::STSState::MODE_FORCE_HTTPS); } - { - TransportSecurityState::STSState dynamic_sts_state; - EXPECT_TRUE(state->GetDynamicSTSState(network_isolation_key, host, - &dynamic_sts_state)); - EXPECT_EQ(dynamic_sts_state.upgrade_mode, - TransportSecurityState::STSState::MODE_FORCE_HTTPS); - } } void ExpectHasHSTSOnlyWithNIK( @@ -100,13 +91,6 @@ class TransportSecurityStateTestBase : public ::testing::Test, TransportSecurityState::STSState dynamic_sts_state; EXPECT_FALSE(state->GetDynamicSTSState(host, &dynamic_sts_state)); } - { - TransportSecurityState::STSState dynamic_sts_state; - EXPECT_TRUE(state->GetDynamicSTSState(network_isolation_key, host, - &dynamic_sts_state)); - EXPECT_EQ(dynamic_sts_state.upgrade_mode, - TransportSecurityState::STSState::MODE_FORCE_HTTPS); - } } };