| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/component_updater/first_party_sets_component_installer.h" |
| |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/cxx17_backports.h" |
| #include "base/feature_list.h" |
| #include "base/files/file.h" |
| #include "base/files/file_util.h" |
| #include "base/logging.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/no_destructor.h" |
| #include "base/path_service.h" |
| #include "base/task/post_task.h" |
| #include "base/task/thread_pool.h" |
| #include "base/version.h" |
| #include "chrome/browser/first_party_sets/first_party_sets_settings.h" |
| #include "components/component_updater/component_installer.h" |
| #include "components/component_updater/component_updater_paths.h" |
| #include "content/public/browser/network_service_instance.h" |
| #include "content/public/common/content_features.h" |
| #include "net/cookies/cookie_util.h" |
| #include "services/network/public/mojom/network_service.mojom.h" |
| |
| using component_updater::ComponentUpdateService; |
| |
| namespace { |
| |
| using SetsReadyOnceCallback = component_updater:: |
| FirstPartySetsComponentInstallerPolicy::SetsReadyOnceCallback; |
| |
| constexpr base::FilePath::CharType kFirstPartySetsSetsFileName[] = |
| FILE_PATH_LITERAL("sets.json"); |
| |
| // The SHA256 of the SubjectPublicKeyInfo used to sign the extension. |
| // The extension id is: gonpemdgkjcecdgbnaabipppbmgfggbe |
| constexpr uint8_t kFirstPartySetsPublicKeySHA256[32] = { |
| 0x6e, 0xdf, 0x4c, 0x36, 0xa9, 0x24, 0x23, 0x61, 0xd0, 0x01, 0x8f, |
| 0xff, 0x1c, 0x65, 0x66, 0x14, 0xa8, 0x46, 0x37, 0xe6, 0xeb, 0x80, |
| 0x8b, 0x8f, 0xb0, 0xb6, 0x18, 0xa7, 0xcd, 0x3d, 0xbb, 0xfb}; |
| |
| constexpr char kFirstPartySetsManifestName[] = "First-Party Sets"; |
| |
| constexpr base::FilePath::CharType kFirstPartySetsRelativeInstallDir[] = |
| FILE_PATH_LITERAL("FirstPartySetsPreloaded"); |
| |
| base::File OpenFile(const base::FilePath& pb_path) { |
| return base::File(pb_path, base::File::FLAG_OPEN | base::File::FLAG_READ); |
| } |
| |
| base::FilePath& GetConfigPathInstance() { |
| static base::NoDestructor<base::FilePath> instance; |
| return *instance; |
| } |
| |
| base::TaskPriority GetTaskPriority() { |
| return FirstPartySetsSettings::Get()->IsFirstPartySetsEnabled() |
| ? base::TaskPriority::USER_BLOCKING |
| : base::TaskPriority::BEST_EFFORT; |
| } |
| |
| // Invokes `on_sets_ready`, if: |
| // * First-Party Sets is enabled; and |
| // * `on_sets_ready` is not null. |
| // |
| // If the component has been installed and can be read, we pass the component |
| // file; otherwise, we pass an invalid file. |
| void SetFirstPartySetsConfig(SetsReadyOnceCallback on_sets_ready) { |
| if (!FirstPartySetsSettings::Get()->IsFirstPartySetsEnabled() || |
| on_sets_ready.is_null()) { |
| return; |
| } |
| |
| const base::FilePath instance_path = GetConfigPathInstance(); |
| if (instance_path.empty()) { |
| // Registration is complete, but no component version exists on disk. |
| std::move(on_sets_ready).Run(base::File()); |
| return; |
| } |
| |
| // We use USER_BLOCKING here since First-Party Set initialization blocks |
| // network navigations at startup. |
| base::ThreadPool::PostTaskAndReplyWithResult( |
| FROM_HERE, {base::MayBlock(), GetTaskPriority()}, |
| base::BindOnce(&OpenFile, instance_path), std::move(on_sets_ready)); |
| } |
| |
| std::string BoolToString(bool b) { |
| return b ? "true" : "false"; |
| } |
| |
| } // namespace |
| |
| namespace component_updater { |
| |
| // static |
| void FirstPartySetsComponentInstallerPolicy::ReconfigureAfterNetworkRestart( |
| SetsReadyOnceCallback on_sets_ready) { |
| SetFirstPartySetsConfig(std::move(on_sets_ready)); |
| } |
| |
| void FirstPartySetsComponentInstallerPolicy::OnRegistrationComplete() { |
| SetFirstPartySetsConfig(std::move(on_sets_ready_)); |
| } |
| |
| FirstPartySetsComponentInstallerPolicy::FirstPartySetsComponentInstallerPolicy( |
| SetsReadyOnceCallback on_sets_ready) |
| : on_sets_ready_(std::move(on_sets_ready)) {} |
| |
| FirstPartySetsComponentInstallerPolicy:: |
| ~FirstPartySetsComponentInstallerPolicy() = default; |
| |
| const char |
| FirstPartySetsComponentInstallerPolicy::kDogfoodInstallerAttributeName[] = |
| "_internal_experimental_sets"; |
| |
| const char FirstPartySetsComponentInstallerPolicy::kV2FormatOptIn[] = |
| "_v2_format_plz"; |
| |
| bool FirstPartySetsComponentInstallerPolicy:: |
| SupportsGroupPolicyEnabledComponentUpdates() const { |
| return true; |
| } |
| |
| bool FirstPartySetsComponentInstallerPolicy::RequiresNetworkEncryption() const { |
| // Update checks and pings associated with this component do not require |
| // confidentiality, since the component is identical for all users. |
| return false; |
| } |
| |
| update_client::CrxInstaller::Result |
| FirstPartySetsComponentInstallerPolicy::OnCustomInstall( |
| const base::Value& manifest, |
| const base::FilePath& install_dir) { |
| return update_client::CrxInstaller::Result(0); // Nothing custom here. |
| } |
| |
| void FirstPartySetsComponentInstallerPolicy::OnCustomUninstall() {} |
| |
| base::FilePath FirstPartySetsComponentInstallerPolicy::GetInstalledPath( |
| const base::FilePath& base) { |
| return base.Append(kFirstPartySetsSetsFileName); |
| } |
| |
| void FirstPartySetsComponentInstallerPolicy::ComponentReady( |
| const base::Version& version, |
| const base::FilePath& install_dir, |
| base::Value manifest) { |
| if (install_dir.empty() || !GetConfigPathInstance().empty()) |
| return; |
| |
| VLOG(1) << "First-Party Sets Component ready, version " << version.GetString() |
| << " in " << install_dir.value(); |
| |
| GetConfigPathInstance() = GetInstalledPath(install_dir); |
| |
| SetFirstPartySetsConfig(std::move(on_sets_ready_)); |
| } |
| |
| // Called during startup and installation before ComponentReady(). |
| bool FirstPartySetsComponentInstallerPolicy::VerifyInstallation( |
| const base::Value& manifest, |
| const base::FilePath& install_dir) const { |
| // No need to actually validate the sets here, since we'll do the validation |
| // in the Network Service. |
| return base::PathExists(GetInstalledPath(install_dir)); |
| } |
| |
| base::FilePath FirstPartySetsComponentInstallerPolicy::GetRelativeInstallDir() |
| const { |
| return base::FilePath(kFirstPartySetsRelativeInstallDir); |
| } |
| |
| void FirstPartySetsComponentInstallerPolicy::GetHash( |
| std::vector<uint8_t>* hash) const { |
| hash->assign(kFirstPartySetsPublicKeySHA256, |
| kFirstPartySetsPublicKeySHA256 + |
| base::size(kFirstPartySetsPublicKeySHA256)); |
| } |
| |
| std::string FirstPartySetsComponentInstallerPolicy::GetName() const { |
| return kFirstPartySetsManifestName; |
| } |
| |
| update_client::InstallerAttributes |
| FirstPartySetsComponentInstallerPolicy::GetInstallerAttributes() const { |
| return { |
| { |
| kDogfoodInstallerAttributeName, |
| BoolToString(features::kFirstPartySetsIsDogfooder.Get()), |
| }, |
| { |
| kV2FormatOptIn, |
| BoolToString(base::FeatureList::IsEnabled( |
| features::kFirstPartySetsV2ComponentFormat)), |
| }, |
| }; |
| } |
| |
| // static |
| void FirstPartySetsComponentInstallerPolicy::ResetForTesting() { |
| GetConfigPathInstance().clear(); |
| } |
| |
| // static |
| void FirstPartySetsComponentInstallerPolicy::SendFileToNetworkService( |
| base::File sets_file) { |
| VLOG(1) << "Received First-Party Sets"; |
| content::GetNetworkService()->SetFirstPartySets(std::move(sets_file)); |
| } |
| |
| void RegisterFirstPartySetsComponent(ComponentUpdateService* cus) { |
| VLOG(1) << "Registering First-Party Sets component."; |
| |
| auto policy = std::make_unique<FirstPartySetsComponentInstallerPolicy>( |
| /*on_sets_ready=*/base::BindOnce( |
| &FirstPartySetsComponentInstallerPolicy::SendFileToNetworkService)); |
| |
| FirstPartySetsComponentInstallerPolicy* raw_policy = policy.get(); |
| // Dereferencing `raw_policy` this way is safe because the closure is invoked |
| // by the ComponentInstaller instance, which owns `policy` (so they have the |
| // same lifetime). Therefore if/when the closure is invoked, `policy` is still |
| // alive. |
| base::MakeRefCounted<ComponentInstaller>(std::move(policy)) |
| ->Register(cus, |
| base::BindOnce( |
| [](FirstPartySetsComponentInstallerPolicy* policy) { |
| policy->OnRegistrationComplete(); |
| }, |
| raw_policy), |
| GetTaskPriority()); |
| } |
| |
| // static |
| void FirstPartySetsComponentInstallerPolicy::WriteComponentForTesting( |
| const base::FilePath& install_dir, |
| base::StringPiece contents) { |
| CHECK(base::WriteFile(GetInstalledPath(install_dir), contents)); |
| |
| GetConfigPathInstance() = GetInstalledPath(install_dir); |
| } |
| |
| } // namespace component_updater |