diff --git a/browser/BUILD.gn b/browser/BUILD.gn index 66acd8ab9b9c..145b264d923c 100644 --- a/browser/BUILD.gn +++ b/browser/BUILD.gn @@ -111,6 +111,7 @@ source_set("browser_process") { "autoplay", "content_settings", "download", + "farbling", "net", "profiles", "renderer_context_menu", diff --git a/browser/brave_tab_helpers.cc b/browser/brave_tab_helpers.cc index a4b82848cf64..6617dd99bb1a 100644 --- a/browser/brave_tab_helpers.cc +++ b/browser/brave_tab_helpers.cc @@ -7,6 +7,7 @@ #include "base/command_line.h" #include "base/feature_list.h" +#include "brave/browser/farbling/farbling_tab_helper.h" #include "brave/browser/tor/buildflags.h" #include "brave/browser/ui/bookmark/brave_bookmark_tab_helper.h" #include "brave/common/brave_switches.h" @@ -123,6 +124,8 @@ void AttachTabHelpers(content::WebContents* web_contents) { ipfs::IPFSTabHelper::CreateForWebContents(web_contents); } #endif + + FarblingTabHelper::CreateForWebContents(web_contents); } } // namespace brave diff --git a/browser/farbling/BUILD.gn b/browser/farbling/BUILD.gn new file mode 100644 index 000000000000..dc65c8fa0cb2 --- /dev/null +++ b/browser/farbling/BUILD.gn @@ -0,0 +1,18 @@ +source_set("farbling") { + check_includes = false + sources = [ + "farbling_tab_helper.cc", + "farbling_tab_helper.h", + ] + + deps = [ + "//base", + "//brave/components/brave_shields/browser:browser", + "//components/version_info", + "//content/public/browser", + "//content/public/common", + "//chrome/common", + "//net", + "//third_party/blink/public/common", + ] +} diff --git a/browser/farbling/brave_navigator_useragent_farbling_browsertest.cc b/browser/farbling/brave_navigator_useragent_farbling_browsertest.cc new file mode 100644 index 000000000000..80244a128fb9 --- /dev/null +++ b/browser/farbling/brave_navigator_useragent_farbling_browsertest.cc @@ -0,0 +1,161 @@ +/* Copyright (c) 2020 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/stringprintf.h" +#include "base/task/post_task.h" +#include "base/test/thread_test_helper.h" +#include "brave/browser/brave_browser_process_impl.h" +#include "brave/browser/brave_content_browser_client.h" +#include "brave/browser/extensions/brave_base_local_data_files_browsertest.h" +#include "brave/common/brave_paths.h" +#include "brave/common/pref_names.h" +#include "brave/components/brave_component_updater/browser/local_data_files_service.h" +#include "brave/components/brave_shields/browser/brave_shields_util.h" +#include "chrome/browser/content_settings/host_content_settings_map_factory.h" +#include "chrome/browser/extensions/extension_browsertest.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/common/chrome_content_client.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "chrome/test/base/ui_test_utils.h" +#include "components/permissions/permission_request.h" +#include "components/prefs/pref_service.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/test/browser_test.h" +#include "content/public/test/browser_test_utils.h" +#include "net/dns/mock_host_resolver.h" + +using brave_shields::ControlType; + +const char kUserAgentScript[] = + "domAutomationController.send(navigator.userAgent);"; + +class BraveNavigatorUserAgentFarblingBrowserTest : public InProcessBrowserTest { + public: + void SetUpOnMainThread() override { + InProcessBrowserTest::SetUpOnMainThread(); + + content_client_.reset(new ChromeContentClient); + content::SetContentClient(content_client_.get()); + browser_content_client_.reset(new BraveContentBrowserClient()); + content::SetBrowserClientForTesting(browser_content_client_.get()); + + host_resolver()->AddRule("*", "127.0.0.1"); + content::SetupCrossSiteRedirector(embedded_test_server()); + + brave::RegisterPathProvider(); + base::FilePath test_data_dir; + base::PathService::Get(brave::DIR_TEST_DATA, &test_data_dir); + embedded_test_server()->ServeFilesFromDirectory(test_data_dir); + + ASSERT_TRUE(embedded_test_server()->Start()); + } + + void TearDown() override { + browser_content_client_.reset(); + content_client_.reset(); + } + + HostContentSettingsMap* content_settings() { + return HostContentSettingsMapFactory::GetForProfile(browser()->profile()); + } + + void AllowFingerprinting(std::string domain) { + brave_shields::SetFingerprintingControlType( + content_settings(), ControlType::ALLOW, + embedded_test_server()->GetURL(domain, "/")); + } + + void BlockFingerprinting(std::string domain) { + brave_shields::SetFingerprintingControlType( + content_settings(), ControlType::BLOCK, + embedded_test_server()->GetURL(domain, "/")); + } + + void SetFingerprintingDefault(std::string domain) { + brave_shields::SetFingerprintingControlType( + content_settings(), ControlType::DEFAULT, + embedded_test_server()->GetURL(domain, "/")); + } + + template + int ExecScriptGetInt(const std::string& script, T* frame) { + int value; + EXPECT_TRUE(ExecuteScriptAndExtractInt(frame, script, &value)); + return value; + } + + template + std::string ExecScriptGetStr(const std::string& script, T* frame) { + std::string value; + EXPECT_TRUE(ExecuteScriptAndExtractString(frame, script, &value)); + return value; + } + + content::WebContents* contents() { + return browser()->tab_strip_model()->GetActiveWebContents(); + } + + bool NavigateToURLUntilLoadStop(const GURL& url) { + ui_test_utils::NavigateToURL(browser(), url); + return WaitForLoadStop(contents()); + } + + private: + std::unique_ptr content_client_; + std::unique_ptr browser_content_client_; +}; + +// Tests results of farbling user agent +IN_PROC_BROWSER_TEST_F(BraveNavigatorUserAgentFarblingBrowserTest, + FarbleNavigatorUserAgent) { + std::string domain_b = "b.com"; + std::string domain_z = "z.com"; + GURL url_b = embedded_test_server()->GetURL(domain_b, "/simple.html"); + GURL url_z = embedded_test_server()->GetURL(domain_z, "/simple.html"); + + // Farbling level: off + // get real navigator.userAgent + AllowFingerprinting(domain_b); + NavigateToURLUntilLoadStop(url_b); + std::string off_ua_b = ExecScriptGetStr(kUserAgentScript, contents()); + AllowFingerprinting(domain_z); + NavigateToURLUntilLoadStop(url_z); + std::string off_ua_z = ExecScriptGetStr(kUserAgentScript, contents()); + // user agent should be the same on every domain if farbling is off + EXPECT_EQ(off_ua_b, off_ua_z); + + // Farbling level: default + // navigator.userAgent may be farbled, but the farbling is not + // domain-specific + SetFingerprintingDefault(domain_b); + NavigateToURLUntilLoadStop(url_b); + std::string default_ua_b = ExecScriptGetStr(kUserAgentScript, contents()); + SetFingerprintingDefault(domain_z); + NavigateToURLUntilLoadStop(url_z); + std::string default_ua_z = ExecScriptGetStr(kUserAgentScript, contents()); + // user agent should be the same on every domain if farbling is default + EXPECT_EQ(default_ua_b, default_ua_z); + + // Farbling level: maximum + // navigator.userAgent should be the possibly-farbled string from the default + // farbling level, further suffixed by a pseudo-random number of spaces based + // on domain and session key + BlockFingerprinting(domain_b); + NavigateToURLUntilLoadStop(url_b); + std::string max_ua_b = ExecScriptGetStr(kUserAgentScript, contents()); + EXPECT_EQ(max_ua_b, default_ua_b + " "); + BlockFingerprinting(domain_z); + NavigateToURLUntilLoadStop(url_z); + std::string max_ua_z = ExecScriptGetStr(kUserAgentScript, contents()); + EXPECT_EQ(max_ua_z, default_ua_z + " "); + + // Farbling level: off + // verify that user agent is reset properly after having been farbled + AllowFingerprinting(domain_b); + NavigateToURLUntilLoadStop(url_b); + std::string off_ua_b2 = ExecScriptGetStr(kUserAgentScript, contents()); + EXPECT_EQ(off_ua_b, off_ua_b2); +} diff --git a/browser/farbling/farbling_tab_helper.cc b/browser/farbling/farbling_tab_helper.cc new file mode 100644 index 000000000000..1f79140dad96 --- /dev/null +++ b/browser/farbling/farbling_tab_helper.cc @@ -0,0 +1,162 @@ +/* Copyright (c) 2020 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 "brave/browser/farbling/farbling_tab_helper.h" + +#include +#include + +#include "base/system/sys_info.h" +#include "brave/components/brave_shields/browser/brave_shields_util.h" +#include "chrome/browser/chrome_content_browser_client.h" +#include "chrome/browser/content_settings/host_content_settings_map_factory.h" +#include "chrome/browser/profiles/profile.h" +#include "components/version_info/version_info.h" +#include "content/public/browser/navigation_handle.h" +#include "content/public/common/content_client.h" +#include "content/public/common/user_agent.h" +#include "net/http/http_util.h" +#include "third_party/blink/public/common/features.h" +#include "third_party/blink/public/common/user_agent/user_agent_metadata.h" + +using brave_shields::ControlType; +using brave_shields::GetBraveShieldsEnabled; +using brave_shields::GetFingerprintingControlType; + +namespace { + +#if defined(OS_MACOSX) +int32_t GetMinimumBugfixVersion(int32_t os_major_version, + int32_t os_minor_version) { + if (os_major_version == 10) { + switch (os_minor_version) { + case 9: + case 10: + return 5; + case 11: + case 12: + case 13: + case 14: + case 15: + return 6; + } + } + return 0; +} +#endif + +std::string GetUserAgentPlatform() { +#if defined(OS_WIN) + return ""; +#elif defined(OS_MACOSX) + return "Macintosh; "; +#elif defined(USE_X11) + return "X11; "; +#elif defined(OS_ANDROID) + return "Linux; "; +#elif defined(OS_POSIX) + return "Unknown; "; +#endif +} + +std::string GetMinimalProduct() { + return version_info::GetProductNameAndVersionForUserAgent(); +} + +std::string GetMinimalOSVersion() { + std::string os_version; +#if defined(OS_WIN) || defined(OS_MACOSX) + int32_t os_major_version = 0; + int32_t os_minor_version = 0; + int32_t os_bugfix_version = 0; + base::SysInfo::OperatingSystemVersionNumbers( + &os_major_version, &os_minor_version, &os_bugfix_version); +#endif +#if defined(OS_MACOSX) + os_bugfix_version = + std::max(os_bugfix_version, + GetMinimumBugfixVersion(os_major_version, os_minor_version)); +#endif + +#if defined(OS_ANDROID) + std::string android_version_str = base::SysInfo::OperatingSystemVersion(); + std::string android_info_str = + GetAndroidOSInfo(content::IncludeAndroidBuildNumber::Exclude, + content::IncludeAndroidModel::Exclude); +#endif + + base::StringAppendF(&os_version, +#if defined(OS_WIN) + "%d.%d", os_major_version, os_minor_version +#elif defined(OS_MACOSX) + "%d_%d_%d", os_major_version, os_minor_version, + os_bugfix_version +#elif defined(OS_ANDROID) + "%s%s", android_version_str.c_str(), + android_info_str.c_str() +#else + "" +#endif + ); // NOLINT + return os_version; +} + +} // namespace + +namespace brave { + +FarblingTabHelper::~FarblingTabHelper() = default; + +FarblingTabHelper::FarblingTabHelper(content::WebContents* web_contents) + : content::WebContentsObserver(web_contents) {} + +void FarblingTabHelper::DidStartNavigation( + content::NavigationHandle* navigation_handle) { + UpdateUserAgent(navigation_handle); +} + +void FarblingTabHelper::UpdateUserAgent( + content::NavigationHandle* navigation_handle) { + if (!navigation_handle) + return; + content::WebContents* web_contents = navigation_handle->GetWebContents(); + if (!web_contents) + return; + std::string ua = ""; + Profile* profile = static_cast(web_contents->GetBrowserContext()); + auto* map = HostContentSettingsMapFactory::GetForProfile(profile); + // If shields is off or farbling is off, do not override. + // Also, we construct real user agent two different ways, through the browser + // client's higher level utility function and through direct functions. If + // they differ, there's some sort of override happening. Maybe the end + // user is forcing the user agent via command line flags. Or maybe they + // turned on the "freeze user agent" flag. Whatever it is, we want to + // respect it. + if (GetBraveShieldsEnabled(map, navigation_handle->GetURL()) && + (GetFingerprintingControlType(map, navigation_handle->GetURL()) != + ControlType::ALLOW) && + (GetUserAgent() == + content::BuildUserAgentFromProduct( + version_info::GetProductNameAndVersionForUserAgent()))) { + std::string minimal_os_info; + base::StringAppendF(&minimal_os_info, "%s%s", + GetUserAgentPlatform().c_str(), + content::BuildOSCpuInfoFromOSVersionAndCpuType( + GetMinimalOSVersion(), content::BuildCpuInfo()) + .c_str()); + ua = content::BuildUserAgentFromOSAndProduct(minimal_os_info, + GetMinimalProduct()); + navigation_handle->SetIsOverridingUserAgent(true); + web_contents->SetUserAgentOverride( + blink::UserAgentOverride::UserAgentOnly(ua), + false /* override_in_new_tabs */); + } else { + navigation_handle->SetIsOverridingUserAgent(false); + } +} + +WEB_CONTENTS_USER_DATA_KEY_IMPL(FarblingTabHelper) + +} // namespace brave diff --git a/browser/farbling/farbling_tab_helper.h b/browser/farbling/farbling_tab_helper.h new file mode 100644 index 000000000000..1d38e7b9eb4b --- /dev/null +++ b/browser/farbling/farbling_tab_helper.h @@ -0,0 +1,43 @@ +/* Copyright (c) 2020 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_BROWSER_FARBLING_FARBLING_TAB_HELPER_H_ +#define BRAVE_BROWSER_FARBLING_FARBLING_TAB_HELPER_H_ + +#include "content/public/browser/web_contents_observer.h" +#include "content/public/browser/web_contents_user_data.h" + +namespace content { +class NavigationHandle; +class WebContents; +} // namespace content + +namespace brave { + +class FarblingTabHelper + : public content::WebContentsObserver, + public content::WebContentsUserData { + public: + ~FarblingTabHelper() override; + + FarblingTabHelper(const FarblingTabHelper&) = delete; + FarblingTabHelper& operator=(FarblingTabHelper&) = delete; + + private: + friend class content::WebContentsUserData; + explicit FarblingTabHelper(content::WebContents* web_contents); + + void UpdateUserAgent(content::NavigationHandle* navigation_handle); + + // content::WebContentsObserver + void DidStartNavigation( + content::NavigationHandle* navigation_handle) override; + + WEB_CONTENTS_USER_DATA_KEY_DECL(); +}; + +} // namespace brave + +#endif // BRAVE_BROWSER_FARBLING_FARBLING_TAB_HELPER_H_ diff --git a/chromium_src/components/network_session_configurator/browser/network_session_configurator.cc b/chromium_src/components/network_session_configurator/browser/network_session_configurator.cc new file mode 100644 index 000000000000..ea02afccdcef --- /dev/null +++ b/chromium_src/components/network_session_configurator/browser/network_session_configurator.cc @@ -0,0 +1,27 @@ +/* Copyright (c) 2020 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/. */ + +#define ParseCommandLineAndFieldTrials \ + ParseCommandLineAndFieldTrials_ChromiumImpl +#include "../../../../../components/network_session_configurator/browser/network_session_configurator.cc" +#undef ParseCommandLineAndFieldTrials + +namespace network_session_configurator { + +// Never send QUIC user agent. +void ParseCommandLineAndFieldTrials(const base::CommandLine& command_line, + bool is_quic_force_disabled, + const std::string& quic_user_agent_id, + net::HttpNetworkSession::Params* params, + net::QuicParams* quic_params) { + ParseCommandLineAndFieldTrials_ChromiumImpl( + command_line, + is_quic_force_disabled, + "" /* quic_user_agent_id */, + params, + quic_params); +} + +} // namespace network_session_configurator diff --git a/chromium_src/third_party/blink/renderer/core/frame/navigator.cc b/chromium_src/third_party/blink/renderer/core/frame/navigator.cc new file mode 100644 index 000000000000..5f902cb9aa07 --- /dev/null +++ b/chromium_src/third_party/blink/renderer/core/frame/navigator.cc @@ -0,0 +1,43 @@ +/* Copyright (c) 2020 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 + +#include "brave/components/content_settings/renderer/brave_content_settings_agent_impl_helper.h" +#include "third_party/blink/public/platform/web_content_settings_client.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/loader/frame_loader.h" +#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" + +using blink::LocalFrame; +using WTF::String; +using WTF::StringBuilder; + +namespace brave { + +const int kFarbledUserAgentMaxExtraSpaces = 5; + +String FarbledUserAgent(LocalFrame* frame, std::mt19937_64 prng) { + StringBuilder result; + result.Append(frame->Loader().UserAgent()); + int extra = prng() % kFarbledUserAgentMaxExtraSpaces; + for (int i = 0; i < extra; i++) + result.Append(" "); + return result.ToString(); +} + +} // namespace brave + +#define BRAVE_NAVIGATOR_USERAGENT \ + if (!AllowFingerprinting(GetFrame())) \ + return brave::FarbledUserAgent( \ + GetFrame(), \ + brave::BraveSessionCache::From(*(GetFrame()->GetDocument())) \ + .MakePseudoRandomGenerator()); + +#include "../../../../../../../third_party/blink/renderer/core/frame/navigator.cc" + +#undef BRAVE_NAVIGATOR_USERAGENT diff --git a/patches/third_party-blink-renderer-core-frame-navigator.cc.patch b/patches/third_party-blink-renderer-core-frame-navigator.cc.patch new file mode 100644 index 000000000000..885386a1cd28 --- /dev/null +++ b/patches/third_party-blink-renderer-core-frame-navigator.cc.patch @@ -0,0 +1,12 @@ +diff --git a/third_party/blink/renderer/core/frame/navigator.cc b/third_party/blink/renderer/core/frame/navigator.cc +index b9eb3a925a366c6b556237b9c74464979e6c5fec..3a40559475e2aa51ff233e625108674f211b5d50 100644 +--- a/third_party/blink/renderer/core/frame/navigator.cc ++++ b/third_party/blink/renderer/core/frame/navigator.cc +@@ -76,6 +76,7 @@ String Navigator::userAgent() const { + if (!GetFrame() || !GetFrame()->GetPage()) + return String(); + ++ BRAVE_NAVIGATOR_USERAGENT + return GetFrame()->Loader().UserAgent(); + } + diff --git a/test/BUILD.gn b/test/BUILD.gn index 814bf8e09f3e..03a9478b464c 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -529,6 +529,7 @@ test("brave_browser_tests") { "//brave/browser/farbling/brave_enumeratedevices_farbling_browsertest.cc", "//brave/browser/farbling/brave_navigator_hardwareconcurrency_farbling_browsertest.cc", "//brave/browser/farbling/brave_navigator_plugins_farbling_browsertest.cc", + "//brave/browser/farbling/brave_navigator_useragent_farbling_browsertest.cc", "//brave/browser/farbling/brave_offscreencanvas_farbling_browsertest.cc", "//brave/browser/farbling/brave_webaudio_farbling_browsertest.cc", "//brave/browser/farbling/brave_webgl_farbling_browsertest.cc",