// Copyright 2023 The Chromium Authors
// 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/privacy_sandbox_attestations_component_installer.h"

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

#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/path_service.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "base/values.h"
#include "base/version.h"
#include "chrome/common/chrome_paths.h"
#include "components/component_updater/component_installer.h"
#include "components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations.h"
#include "components/privacy_sandbox/privacy_sandbox_features.h"
#include "components/startup_metric_utils/browser/startup_metric_utils.h"
#include "components/update_client/update_client.h"

namespace {

constexpr base::FilePath::CharType kPrivacySandboxAttestationsFileName[] =
    FILE_PATH_LITERAL("privacy-sandbox-attestations.dat");

constexpr base::FilePath::CharType
    kPrivacySandboxAttestationsRelativeInstallDir[] =
        FILE_PATH_LITERAL("PrivacySandboxAttestationsPreloaded");

// The SHA256 of the SubjectPublicKeyInfo used to sign the extension.
// The extension id is: niikhdgajlphfehepabhhblakbdgeefj
constexpr uint8_t kPrivacySandboxAttestationsPublicKeySHA256[32] = {
    0xd8, 0x8a, 0x73, 0x60, 0x9b, 0xf7, 0x54, 0x74, 0xf0, 0x17, 0x71,
    0xb0, 0xa1, 0x36, 0x44, 0x59, 0xf6, 0x22, 0x38, 0xa8, 0x7c, 0xc9,
    0x7b, 0x7a, 0x43, 0x2c, 0x72, 0xee, 0x04, 0x01, 0xae, 0xc0};

const char kPrivacySandboxAttestationsManifestName[] =
    "Privacy Sandbox Attestations";

}  // namespace

namespace component_updater {

PrivacySandboxAttestationsComponentInstallerPolicy::
    PrivacySandboxAttestationsComponentInstallerPolicy(
        AttestationsReadyRepeatingCallback on_attestations_ready)
    : on_attestations_ready_(std::move(on_attestations_ready)) {}

PrivacySandboxAttestationsComponentInstallerPolicy::
    ~PrivacySandboxAttestationsComponentInstallerPolicy() = default;

bool PrivacySandboxAttestationsComponentInstallerPolicy::VerifyInstallation(
    const base::DictValue& manifest,
    const base::FilePath& install_dir) const {
  return base::PathExists(GetInstalledFilePath(install_dir));
}

bool PrivacySandboxAttestationsComponentInstallerPolicy::
    SupportsGroupPolicyEnabledComponentUpdates() const {
  return true;
}

bool PrivacySandboxAttestationsComponentInstallerPolicy::
    RequiresNetworkEncryption() const {
  // Privacy sandbox attestations are identical for all users.
  return false;
}

update_client::CrxInstaller::Result
PrivacySandboxAttestationsComponentInstallerPolicy::OnCustomInstall(
    const base::DictValue& manifest,
    const base::FilePath& install_dir) {
  // No custom install for privacy sandbox attestations.
  return update_client::CrxInstaller::Result(0);
}

void PrivacySandboxAttestationsComponentInstallerPolicy::OnCustomUninstall() {}

void PrivacySandboxAttestationsComponentInstallerPolicy::ComponentReady(
    const base::Version& version,
    const base::FilePath& install_dir,
    base::DictValue manifest) {
  if (!base::FeatureList::IsEnabled(
          privacy_sandbox::kEnforcePrivacySandboxAttestations)) {
    // Privacy Sandbox Enrollment is not enforced if the feature is disabled.
    return;
  }

  if (on_attestations_ready_.is_null()) {
    return;
  }

  if (install_dir.empty() || !version.IsValid()) {
    return;
  }

  // Record the time taken for the downloaded attestations file to be detected.
  startup_metric_utils::GetBrowser().RecordPrivacySandboxAttestationsFirstReady(
      base::TimeTicks::Now());

  VLOG(1) << "Privacy Sandbox Attestations Component ready, version "
          << version.GetString() << " in " << install_dir.value();

  on_attestations_ready_.Run(
      std::move(version),
      /*installed_file_path=*/
      PrivacySandboxAttestationsComponentInstallerPolicy::GetInstalledFilePath(
          install_dir),
      /*is_pre_installed=*/manifest.FindBool("pre_installed").value_or(false));
}

base::FilePath
PrivacySandboxAttestationsComponentInstallerPolicy::GetRelativeInstallDir()
    const {
  return base::FilePath(kPrivacySandboxAttestationsRelativeInstallDir);
}

void PrivacySandboxAttestationsComponentInstallerPolicy::GetHash(
    std::vector<uint8_t>* hash) const {
  hash->assign_range(kPrivacySandboxAttestationsPublicKeySHA256);
}

std::string PrivacySandboxAttestationsComponentInstallerPolicy::GetName()
    const {
  return kPrivacySandboxAttestationsManifestName;
}

update_client::InstallerAttributes
PrivacySandboxAttestationsComponentInstallerPolicy::GetInstallerAttributes()
    const {
  return update_client::InstallerAttributes();
}

void PrivacySandboxAttestationsComponentInstallerPolicy::
    ComponentReadyForTesting(const base::Version& version,
                             const base::FilePath& install_dir,
                             base::DictValue manifest) {
  ComponentReady(version, install_dir, std::move(manifest));
}

// static
base::FilePath
PrivacySandboxAttestationsComponentInstallerPolicy::GetInstalledFilePath(
    const base::FilePath& base) {
  return base.Append(kPrivacySandboxAttestationsFileName);
}

// static
base::FilePath
PrivacySandboxAttestationsComponentInstallerPolicy::GetInstalledDirectory(
    const base::FilePath& base) {
  return base.Append(kPrivacySandboxAttestationsRelativeInstallDir);
}

void RegisterPrivacySandboxAttestationsComponent(ComponentUpdateService* cus) {
  if (!base::FeatureList::IsEnabled(
          privacy_sandbox::kEnforcePrivacySandboxAttestations)) {
    // Privacy sandbox enrollment is not enforced if the feature is disabled.
    // Any existing version of this component is deleted.
    base::FilePath user_path;
    if (base::PathService::Get(chrome::DIR_USER_DATA, &user_path)) {
      base::ThreadPool::PostTask(
          FROM_HERE, {base::TaskPriority::LOWEST, base::MayBlock()},
          base::GetDeletePathRecursivelyCallback(
              user_path.Append(kPrivacySandboxAttestationsRelativeInstallDir)));
    }
    return;
  }

  VLOG(1) << "Registering Privacy Sandbox Attestations component";

  auto policy =
      std::make_unique<PrivacySandboxAttestationsComponentInstallerPolicy>(
          /*on_attestations_ready=*/base::BindRepeating(
              [](base::Version version, base::FilePath install_dir,
                 bool is_pre_installed) {
                VLOG(1) << "Received privacy sandbox attestations file";
                privacy_sandbox::PrivacySandboxAttestations::GetInstance()
                    ->LoadAttestations(std::move(version),
                                       std::move(install_dir),
                                       is_pre_installed);
              }));

  base::MakeRefCounted<ComponentInstaller>(std::move(policy),
                                           /*action_handler=*/nullptr,
                                           base::TaskPriority::USER_BLOCKING)
      ->Register(cus, base::BindOnce([]() {
                   privacy_sandbox::PrivacySandboxAttestations::GetInstance()
                       ->OnAttestationsFileCheckComplete();
                 }));
}

}  // namespace component_updater
