Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added ComponentContentsVerifier that checks verified_contents.json shipped with a component. #26660

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
2 changes: 2 additions & 0 deletions browser/brave_browser_main_parts.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "base/command_line.h"
#include "base/path_service.h"
#include "brave/browser/browsing_data/brave_clear_browsing_data.h"
#include "brave/browser/component_updater/brave_component_contents_verifier.h"
#include "brave/browser/ethereum_remote_client/buildflags/buildflags.h"
#include "brave/components/brave_component_updater/browser/brave_on_demand_updater.h"
#include "brave/components/brave_rewards/common/rewards_flags.h"
Expand Down Expand Up @@ -81,6 +82,7 @@ ChromeBrowserMainParts::ChromeBrowserMainParts(bool is_integration_test,
ChromeBrowserMainParts::~ChromeBrowserMainParts() = default;

int ChromeBrowserMainParts::PreMainMessageLoopRun() {
component_updater::SetupComponentContentsVerifier();
brave_component_updater::BraveOnDemandUpdater::GetInstance()
->RegisterOnDemandUpdater(
&g_browser_process->component_updater()->GetOnDemandUpdater());
Expand Down
76 changes: 73 additions & 3 deletions browser/brave_shields/ad_block_service_browsertest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@
#include "base/path_service.h"
#include "base/strings/stringprintf.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/thread_test_helper.h"
#include "base/threading/thread_restrictions.h"
#include "brave/app/brave_command_ids.h"
#include "brave/browser/brave_browser_process.h"
#include "brave/browser/net/brave_ad_block_tp_network_delegate_helper.h"
#include "brave/components/brave_component_updater/browser/component_contents_accessor.h"
#include "brave/components/brave_component_updater/browser/features.h"
#include "brave/components/brave_shields/content/browser/ad_block_custom_filters_provider.h"
#include "brave/components/brave_shields/content/browser/ad_block_engine.h"
#include "brave/components/brave_shields/content/browser/ad_block_service.h"
Expand Down Expand Up @@ -245,13 +248,25 @@ base::FilePath AdBlockServiceTest::MakeTestDataCopy(
return temp_path.Append(source_location.BaseName());
}

brave_shields::AdBlockResourceProvider*
AdBlockServiceTest::InstallDefaultAdBlockResources(
const base::FilePath& component_path) {
brave_shields::AdBlockService* service =
g_brave_browser_process->ad_block_service();
base::ScopedAllowBlockingForTesting allow_blocking;
service->default_resource_provider()->OnComponentReady(
component_updater::ComponentContentsAccessor::Create(component_path));
return service->default_resource_provider();
}

void AdBlockServiceTest::UpdateAdBlockResources(const std::string& resources) {
auto component_path = MakeFileInTempDir("resources.json", resources);

brave_shields::AdBlockService* service =
g_brave_browser_process->ad_block_service();

service->default_resource_provider()->OnComponentReady(component_path);
service->default_resource_provider()->OnComponentReady(
component_updater::ComponentContentsAccessor::Create(component_path));
}

void AdBlockServiceTest::UpdateAdBlockInstanceWithRules(
Expand All @@ -267,7 +282,8 @@ void AdBlockServiceTest::UpdateAdBlockInstanceWithRules(
std::string uuid = "default";
auto& provider = component_providers.at(uuid);
EXPECT_TRUE(provider);
provider->OnComponentReady(component_path);
provider->OnComponentReady(
component_updater::ComponentContentsAccessor::Create(component_path));

auto* engine = service->default_engine_.get();
EngineTestObserver engine_observer(engine);
Expand Down Expand Up @@ -342,7 +358,8 @@ void AdBlockServiceTest::InstallComponent(

auto& provider = component_providers.at(catalog_entry.uuid);
EXPECT_TRUE(provider);
provider->OnComponentReady(component_path);
provider->OnComponentReady(
component_updater::ComponentContentsAccessor::Create(component_path));

auto* engine = catalog_entry.first_party_protections
? service->default_engine_.get()
Expand Down Expand Up @@ -1604,6 +1621,59 @@ IN_PROC_BROWSER_TEST_F(Default1pBlockingFlagDisabledTest, Custom1pBlocking) {
EXPECT_EQ(profile()->GetPrefs()->GetUint64(kAdsBlocked), 2ULL);
}

class AdBlockServiceSignedComponentsTest : public AdBlockServiceTest {
public:
AdBlockServiceSignedComponentsTest() {
feature_list_.InitAndEnableFeature(
brave_component_updater::kComponentContentsVerifier);
}
~AdBlockServiceSignedComponentsTest() override = default;

private:
base::test::ScopedFeatureList feature_list_;
};

IN_PROC_BROWSER_TEST_F(AdBlockServiceSignedComponentsTest,
SafeResourcesComponentAccess) {
const auto components_path =
GetTestDataDir().AppendASCII("adblock-components");
{
auto* resource_provider = InstallDefaultAdBlockResources(
components_path.AppendASCII("resources_ok"));
base::RunLoop run_loop;
resource_provider->LoadResources(
base::BindLambdaForTesting([&run_loop](const std::string& resources) {
constexpr const char kExpectedResources[] =
"[{\"name\":\"brave-fix.js\",\"aliases\":[],\"kind\":{\"mime\":"
"\"application/javascript\"}}]";
EXPECT_EQ(kExpectedResources, resources);
run_loop.Quit();
}));
run_loop.Run();
}

{
auto* resource_provider = InstallDefaultAdBlockResources(
components_path.AppendASCII("resources_corrupted"));
base::RunLoop run_loop;
resource_provider->LoadResources(
base::BindLambdaForTesting([&run_loop](const std::string& resources) {
#if BUILDFLAG(ENABLE_EXTENSIONS)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why does it matter if extensions are enabled for adblock? It's not an extension

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it uses the chromium code which placed under this flag.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this have its own buildflag then and use that here?

constexpr const char kExpectedResources[] = "[]";
#else
// In case of extensions are disabled we don't check the signature.
constexpr const char kExpectedResources[] =
"[{\"name\":\"edited.js\",\"aliases\":[],\"kind\":{\"mime\":"
"\"application/javascript\"}}]";
#endif

EXPECT_EQ(kExpectedResources, resources);
run_loop.Quit();
}));
run_loop.Run();
}
}

// Load a page with a script which uses a redirect data URL.
IN_PROC_BROWSER_TEST_F(AdBlockServiceTest, RedirectRulesAreRespected) {
UpdateAdBlockResources(R"(
Expand Down
3 changes: 3 additions & 0 deletions browser/brave_shields/ad_block_service_browsertest.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class HostContentSettingsMap;

namespace brave_shields {
class AdBlockService;
class AdBlockResourceProvider;
class FilterListCatalogEntry;
} // namespace brave_shields

Expand All @@ -47,6 +48,8 @@ class AdBlockServiceTest : public PlatformBrowserTest {
void AddNewRules(const std::string& rules,
uint8_t permission_mask = 0,
bool first_party_protections = false);
brave_shields::AdBlockResourceProvider* InstallDefaultAdBlockResources(
const base::FilePath& component_path);
void UpdateAdBlockResources(const std::string& resources);
void UpdateAdBlockInstanceWithRules(const std::string& rules);
void EnableDeveloperMode(bool enabled);
Expand Down
9 changes: 9 additions & 0 deletions browser/component_updater/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@
# 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/. */

import("//extensions/buildflags/buildflags.gni")

source_set("component_updater") {
sources = [
"brave_component_contents_verifier.cc",
"brave_component_contents_verifier.h",
"brave_component_updater_configurator.cc",
"brave_component_updater_configurator.h",
]

deps = [
"//base",
"//brave/components/brave_component_updater/browser",
"//brave/components/constants",
"//chrome/common",
"//components/component_updater",
Expand All @@ -25,6 +30,10 @@ source_set("component_updater") {
"//services/network/public/cpp",
]

if (enable_extensions) {
deps += [ "//extensions/browser" ]
}

if (is_win) {
deps += [ "//chrome/installer/util:with_no_strings" ]
}
Expand Down
151 changes: 151 additions & 0 deletions browser/component_updater/brave_component_contents_verifier.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// Copyright (c) 2024 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/browser/component_updater/brave_component_contents_verifier.h"

#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/command_line.h"
#include "base/containers/span_reader.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "brave/components/brave_component_updater/browser/component_contents_verifier.h"
#include "brave/components/brave_component_updater/browser/features.h"
#include "crypto/sha2.h"
#include "extensions/buildflags/buildflags.h"

#if BUILDFLAG(ENABLE_EXTENSIONS)

#include "extensions/browser/content_hash_tree.h"
#include "extensions/browser/verified_contents.h"

namespace {

constexpr uint8_t kComponentContentsVerifierPublicKey[] = {
0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00,
0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xc3, 0x8a, 0x2d,
0x68, 0x10, 0xb3, 0x52, 0xf1, 0xcc, 0xae, 0xd7, 0x70, 0xb7, 0xc9, 0xc7,
0xe3, 0x1c, 0x91, 0x4c, 0x1a, 0x73, 0xf4, 0xbd, 0xb9, 0x54, 0x01, 0xb2,
0xd0, 0x03, 0xfa, 0x23, 0x43, 0xb9, 0x52, 0xf4, 0xc8, 0x27, 0xbf, 0xed,
0x6c, 0x9b, 0xba, 0x26, 0xa6, 0xe9, 0x43, 0x0f, 0x71, 0xf6, 0xa2, 0x2c,
0x1e, 0xf9, 0xb3, 0xfc, 0xec, 0xfb, 0x12, 0xe1, 0xea, 0xbb, 0x71, 0x6d,
0x80, 0x2b, 0x26, 0x5f, 0xfb, 0x20, 0x91, 0x26, 0x5a, 0x6d, 0xdd, 0x0e,
0xd8, 0x88, 0xc2, 0x11, 0x7e, 0xf7, 0x5d, 0xfc, 0xd6, 0x99, 0x50, 0x9b,
0x70, 0xe9, 0xa7, 0xcf, 0x7f, 0x4c, 0xf0, 0x80, 0x2d, 0x8d, 0x97, 0x9f,
0xfc, 0x5c, 0x56, 0xc3, 0xff, 0x37, 0xf8, 0x3a, 0x44, 0x3a, 0x44, 0x3c,
0xbe, 0x96, 0xf9, 0x14, 0x44, 0xb1, 0x8e, 0x08, 0xc2, 0x55, 0x49, 0xf1,
0xe4, 0x3a, 0xa6, 0x63, 0x23, 0x26, 0x00, 0xa4, 0x8b, 0xdd, 0xaf, 0x45,
0xb0, 0xf8, 0xb8, 0x24, 0x80, 0x85, 0x85, 0x95, 0x2e, 0xc5, 0xd4, 0xf1,
0xdc, 0xfb, 0xeb, 0x95, 0xb6, 0x13, 0x3a, 0x46, 0x9d, 0x49, 0xb1, 0x15,
0x72, 0xfd, 0x85, 0xc6, 0x0b, 0x7e, 0xe2, 0x97, 0xf1, 0x66, 0xa6, 0x0b,
0x67, 0xe8, 0x72, 0xb9, 0xf1, 0x3b, 0x30, 0x1a, 0x77, 0x6d, 0xf3, 0xc4,
0x99, 0xc0, 0x86, 0xb2, 0x4e, 0x1d, 0xde, 0x34, 0x86, 0x15, 0x41, 0x68,
0x1e, 0x94, 0x48, 0x0a, 0x54, 0x05, 0x52, 0x01, 0x04, 0xd2, 0xef, 0x57,
0x89, 0x47, 0xd3, 0xae, 0xd9, 0xc1, 0xf5, 0xa9, 0xbf, 0x60, 0x96, 0x4b,
0xa0, 0x12, 0x9c, 0xf3, 0x00, 0xe6, 0x32, 0x40, 0xc7, 0x6e, 0x29, 0xa8,
0x81, 0xfd, 0x2f, 0x1e, 0x92, 0x4a, 0x5e, 0xed, 0xef, 0x13, 0x9f, 0xed,
0x88, 0x77, 0x2c, 0x84, 0xdd, 0x00, 0x87, 0x03, 0x49, 0x09, 0xb7, 0x4b,
0xc7, 0x02, 0x03, 0x01, 0x00, 0x01};

constexpr char kVerifiedContentsPath[] = "_metadata/verified_contents.json";
constexpr char kBraveVerifiedContentsPath[] =
"brave_metadata/verified_contents.json";

std::string GetRootHashForContent(base::span<const uint8_t> contents,
size_t block_size) {
CHECK(block_size % crypto::kSHA256Length == 0);

std::vector<std::string> hashes;
// Even when the contents is empty, we want to output at least one hash
// block (the hash of the empty string).
base::SpanReader reader(contents);
do {
const auto chunk = reader.Read(std::min(block_size, reader.remaining()));
const auto hash = crypto::SHA256Hash(chunk.value());
hashes.emplace_back(hash.begin(), hash.end());
} while (reader.remaining() > 0);

return extensions::ComputeTreeHashRoot(hashes,
block_size / crypto::kSHA256Length);
}

class ExtensionsTreeHashContentsVerifier
: public component_updater::ContentsVerifier {
public:
explicit ExtensionsTreeHashContentsVerifier(
const base::FilePath& component_root) {
if (base::PathExists(
component_root.AppendASCII(kBraveVerifiedContentsPath))) {
verified_contents_ = extensions::VerifiedContents::CreateFromFile(
kComponentContentsVerifierPublicKey,
component_root.AppendASCII(kBraveVerifiedContentsPath));
} else {
verified_contents_ = extensions::VerifiedContents::CreateFromFile(
kComponentContentsVerifierPublicKey,
component_root.AppendASCII(kVerifiedContentsPath));
}
if (verified_contents_ &&
verified_contents_->block_size() % crypto::kSHA256Length != 0) {
// Unsupported block size.
verified_contents_.reset();
}
}

bool VerifyContents(const base::FilePath& relative_path,
base::span<const uint8_t> contents) const override {
if (!verified_contents_ ||
!verified_contents_->HasTreeHashRoot(relative_path)) {
return false;
}

const auto root_hash =
GetRootHashForContent(contents, verified_contents_->block_size());
return verified_contents_->TreeHashRootEquals(relative_path, root_hash);
}

private:
std::unique_ptr<extensions::VerifiedContents> verified_contents_;
cdesouza-chromium marked this conversation as resolved.
Show resolved Hide resolved
};

bool ShouldBypassSignature() {
static const bool kBypass =
!base::FeatureList::IsEnabled(
brave_component_updater::kComponentContentsVerifier) ||
base::CommandLine::ForCurrentProcess()->HasSwitch(
component_updater::kBypassComponentContentsVerifier);
return kBypass;
}

} // namespace
#endif // BUILDFLAG(ENABLE_EXTENSIONS)

namespace component_updater {

namespace {
std::unique_ptr<component_updater::ContentsVerifier>
CreateExtensionsTreeHashContentsVerifier(const base::FilePath& component_root) {
#if BUILDFLAG(ENABLE_EXTENSIONS)
if (!ShouldBypassSignature()) {
return std::make_unique<ExtensionsTreeHashContentsVerifier>(component_root);
}
#endif
// if there is no extensions enabled then we expect that on these platforms
// the component files are protected by the OS.
return nullptr;
}
} // namespace

void SetupComponentContentsVerifier() {
auto factory = base::BindRepeating(CreateExtensionsTreeHashContentsVerifier);
SetContentsVerifierFactory(std::move(factory));
}

} // namespace component_updater
18 changes: 18 additions & 0 deletions browser/component_updater/brave_component_contents_verifier.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) 2024 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_BROWSER_COMPONENT_UPDATER_BRAVE_COMPONENT_CONTENTS_VERIFIER_H_
#define BRAVE_BROWSER_COMPONENT_UPDATER_BRAVE_COMPONENT_CONTENTS_VERIFIER_H_

namespace component_updater {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this filename doesn't make sense to me with the contents. The feature should be in features.h and this also seems like a strange filename for SetupComponentContentsVerifier


inline constexpr char kBypassComponentContentsVerifier[] =
"bypass-component-contents-verifier";

void SetupComponentContentsVerifier();

} // namespace component_updater

#endif // BRAVE_BROWSER_COMPONENT_UPDATER_BRAVE_COMPONENT_CONTENTS_VERIFIER_H_
14 changes: 14 additions & 0 deletions chromium_src/chrome/browser/ui/startup/bad_flags_prompt.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) 2024 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/browser/component_updater/brave_component_contents_verifier.h"
#include "services/network/public/cpp/network_switches.h"

#define kHostResolverRules \
kHostResolverRules, component_updater::kBypassComponentContentsVerifier

#include "src/chrome/browser/ui/startup/bad_flags_prompt.cc"

#undef kHostResolverRules
5 changes: 5 additions & 0 deletions components/brave_component_updater/browser/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ static_library("browser") {
"brave_component_updater_delegate.h",
"brave_on_demand_updater.cc",
"brave_on_demand_updater.h",
"component_contents_accessor.cc",
"component_contents_accessor.h",
"component_contents_verifier.cc",
"component_contents_verifier.h",
"dat_file_util.cc",
"dat_file_util.h",
"features.cc",
Expand All @@ -33,6 +37,7 @@ static_library("browser") {
"//components/prefs",
"//components/update_client",
"//crypto",
"//extensions/buildflags",
]
}

Expand Down
3 changes: 0 additions & 3 deletions components/brave_component_updater/browser/DEPS

This file was deleted.

Loading
Loading