// Copyright 2024 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/ui/webui/certificate_manager/client_cert_sources.h"

#include <map>
#include <optional>
#include <string>

#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_view_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/bind_post_task.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/resources/certificate_manager/certificate_manager.mojom.h"
#include "chrome/browser/ui/select_file_policy/chrome_select_file_policy.h"
#include "chrome/browser/ui/webui/certificate_manager/certificate_manager_utils.h"
#include "chrome/common/net/x509_certificate_model.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "content/public/browser/browser_thread.h"
#include "crypto/crypto_buildflags.h"
#include "net/base/hash_value.h"
#include "net/base/net_errors.h"
#include "net/cert/x509_certificate.h"
#include "net/ssl/client_cert_identity.h"
#include "net/ssl/client_cert_store.h"
#include "net/ssl/client_cert_store_empty.h"
#include "net/ssl/ssl_cert_request_info.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/shell_dialogs/select_file_dialog.h"
#include "ui/shell_dialogs/selected_file_info.h"

#if BUILDFLAG(USE_NSS_CERTS)
#include "chrome/browser/net/nss_service.h"
#include "chrome/browser/net/nss_service_factory.h"
#include "chrome/browser/ui/crypto_module_delegate_nss.h"
#include "net/cert/nss_cert_database.h"
#include "net/cert/x509_util_nss.h"
#include "net/ssl/client_cert_store_nss.h"
#endif  // BUILDFLAG(USE_NSS_CERTS)

#if BUILDFLAG(IS_WIN)
#include "net/ssl/client_cert_store_win.h"
#endif  // BUILDFLAG(IS_WIN)

#if BUILDFLAG(IS_MAC)
#include "net/ssl/client_cert_store_mac.h"
#endif  // BUILDFLAG(IS_MAC)

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
#include "chrome/browser/browser_process.h"
#include "chrome/browser/enterprise/client_certificates/certificate_provisioning_service_factory.h"
#include "chrome/browser/policy/chrome_browser_policy_connector.h"
#include "components/enterprise/browser/controller/chrome_browser_cloud_management_controller.h"
#include "components/enterprise/client_certificates/core/certificate_provisioning_service.h"
#include "components/enterprise/client_certificates/core/client_certificates_service.h"
#include "components/enterprise/client_certificates/core/features.h"
#endif

#if BUILDFLAG(IS_CHROMEOS)
#include "ash/constants/ash_features.h"
#include "chrome/browser/ash/certificate_provider/certificate_provider_service.h"
#include "chrome/browser/ash/certificate_provider/certificate_provider_service_factory.h"
#include "chrome/browser/ash/kcer/kcer_factory_ash.h"
#include "chrome/browser/ash/net/client_cert_store_kcer.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chromeos/ash/components/kcer/kcer.h"
#include "chromeos/ash/components/kcer/kcer_histograms.h"
#include "chromeos/components/certificate_provider/certificate_provider.h"
#include "chromeos/constants/chromeos_features.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#endif

namespace {

class ClientCertStoreFactory {
 public:
  virtual ~ClientCertStoreFactory() = default;
  virtual std::unique_ptr<net::ClientCertStore> CreateClientCertStore() = 0;
};

// A certificate loader that wraps a ClientCertStoreFactory. Read-only.
class ClientCertStoreLoader {
 public:
  explicit ClientCertStoreLoader(
      std::unique_ptr<ClientCertStoreFactory> factory)
      : factory_(std::move(factory)) {}

  // Lifetimes note: The callback will not be called if the
  // ClientCertStoreLoader (and thus, the ClientCertStore handle held by
  // `active_requests_`) is destroyed first.
  void GetCerts(base::OnceCallback<void(net::CertificateList)> callback) {
    std::unique_ptr<net::ClientCertStore> store =
        factory_->CreateClientCertStore();
    net::ClientCertStore* store_ptr = store.get();
    active_requests_[store_ptr] = std::move(store);
    // Unretained is safe as the callback is not run if `active_requests_` is
    // destroyed.
    store_ptr->GetClientCerts(
        base::MakeRefCounted<net::SSLCertRequestInfo>(),
        base::BindOnce(&ClientCertStoreLoader::HandleClientCertsResult,
                       base::Unretained(this), store_ptr, std::move(callback)));
  }

 private:
  void HandleClientCertsResult(
      net::ClientCertStore* store,
      base::OnceCallback<void(net::CertificateList)> callback,
      net::ClientCertIdentityList identities) {
    net::CertificateList certs;
    certs.reserve(identities.size());
    for (const auto& identity : identities) {
      certs.push_back(identity->certificate());
    }
    active_requests_.erase(store);
    std::move(callback).Run(std::move(certs));
  }

  std::unique_ptr<ClientCertStoreFactory> factory_;
  std::map<net::ClientCertStore*, std::unique_ptr<net::ClientCertStore>>
      active_requests_;
};

#if BUILDFLAG(IS_LINUX)
class ClientCertStoreFactoryNSS : public ClientCertStoreFactory {
 public:
  std::unique_ptr<net::ClientCertStore> CreateClientCertStore() override {
    return std::make_unique<net::ClientCertStoreNSS>(
        base::BindRepeating(&CreateCryptoModuleBlockingPasswordDelegate,
                            kCryptoModulePasswordClientAuth));
  }
};
#elif BUILDFLAG(IS_WIN)
class ClientCertStoreFactoryWin : public ClientCertStoreFactory {
 public:
  std::unique_ptr<net::ClientCertStore> CreateClientCertStore() override {
    return std::make_unique<net::ClientCertStoreWin>();
  }
};
#elif BUILDFLAG(IS_MAC)
class ClientCertStoreFactoryMac : public ClientCertStoreFactory {
 public:
  std::unique_ptr<net::ClientCertStore> CreateClientCertStore() override {
    return std::make_unique<net::ClientCertStoreMac>();
  }
};
#endif

#if !BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_LINUX)
std::unique_ptr<ClientCertStoreLoader> CreatePlatformClientCertLoader(
    Profile* profile) {
#if BUILDFLAG(IS_WIN)
  return std::make_unique<ClientCertStoreLoader>(
      std::make_unique<ClientCertStoreFactoryWin>());
#elif BUILDFLAG(IS_MAC)
  return std::make_unique<ClientCertStoreLoader>(
      std::make_unique<ClientCertStoreFactoryMac>());
#else
  return nullptr;
#endif
}
#endif

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
class ClientCertStoreFactoryProvisioned : public ClientCertStoreFactory {
 public:
  explicit ClientCertStoreFactoryProvisioned(
      client_certificates::CertificateProvisioningService*
          profile_provisioning_service,
      client_certificates::CertificateProvisioningService*
          browser_provisioning_service)
      : profile_provisioning_service_(profile_provisioning_service),
        browser_provisioning_service_(browser_provisioning_service) {}

  std::unique_ptr<net::ClientCertStore> CreateClientCertStore() override {
    return client_certificates::ClientCertificatesService::Create(
        profile_provisioning_service_, browser_provisioning_service_,
        std::make_unique<net::ClientCertStoreEmpty>());
  }

 private:
  raw_ptr<client_certificates::CertificateProvisioningService>
      profile_provisioning_service_;
  raw_ptr<client_certificates::CertificateProvisioningService>
      browser_provisioning_service_;
};

std::unique_ptr<ClientCertStoreLoader> CreateProvisionedClientCertLoader(
    Profile* profile) {
  client_certificates::CertificateProvisioningService*
      profile_provisioning_service = nullptr;
  if (profile) {
    profile_provisioning_service = client_certificates::
        CertificateProvisioningServiceFactory::GetForProfile(profile);
  }

  client_certificates::CertificateProvisioningService*
      browser_provisioning_service =
          g_browser_process->browser_policy_connector()
              ->chrome_browser_cloud_management_controller()
              ->GetCertificateProvisioningService();

  if (!profile_provisioning_service && !browser_provisioning_service) {
    return nullptr;
  }

  return std::make_unique<ClientCertStoreLoader>(
      std::make_unique<ClientCertStoreFactoryProvisioned>(
          profile_provisioning_service, browser_provisioning_service));
}
#endif

void PopulateCertInfosFromCertificateList(
    CertificateManagerPageHandler::GetCertificatesCallback callback,
    const net::CertificateList& certs,
    bool is_deletable) {
  std::vector<certificate_manager::mojom::SummaryCertInfoPtr> out_infos;
  for (const auto& cert : certs) {
    x509_certificate_model::X509CertificateModel model(
        bssl::UpRef(cert->cert_buffer()));
    out_infos.push_back(certificate_manager::mojom::SummaryCertInfo::New(
        model.HashCertSHA256(), model.GetTitle(), is_deletable));
  }
  std::move(callback).Run(std::move(out_infos));
}

net::X509Certificate* FindCertificateFromCertificateList(
    std::string_view sha256_hex_hash,
    const net::CertificateList& certs) {
  net::SHA256HashValue hash;
  if (!base::HexStringToSpan(sha256_hex_hash, hash)) {
    return nullptr;
  }

  for (const auto& cert : certs) {
    if (net::X509Certificate::CalculateFingerprint256(cert->cert_buffer()) ==
        hash) {
      return cert.get();
    }
  }

  return nullptr;
}

void ViewCertificateFromCertificateList(
    const std::string& sha256_hex_hash,
    const net::CertificateList& certs,
    base::WeakPtr<content::WebContents> web_contents) {
  if (!web_contents) {
    return;
  }

  net::X509Certificate* cert =
      FindCertificateFromCertificateList(sha256_hex_hash, certs);
  if (cert) {
    ShowCertificateDialog(std::move(web_contents),
                          bssl::UpRef(cert->cert_buffer()));
  }
}

class ClientCertSource : public CertificateManagerPageHandler::CertSource {
 public:
  explicit ClientCertSource(std::unique_ptr<ClientCertStoreLoader> loader)
      : loader_(std::move(loader)) {}
  ~ClientCertSource() override = default;

  void GetCertificateInfos(
      CertificateManagerPageHandler::GetCertificatesCallback callback)
      override {
    if (certs_) {
      ReplyToGetCertificatesCallback(std::move(callback));
      return;
    }
    RefreshCachedCertificateList(
        base::BindOnce(&ClientCertSource::ReplyToGetCertificatesCallback,
                       base::Unretained(this), std::move(callback)));
  }

  void ViewCertificate(
      const std::string& sha256_hex_hash,
      base::WeakPtr<content::WebContents> web_contents) override {
    if (!loader_ || !certs_) {
      return;
    }
    ViewCertificateFromCertificateList(sha256_hex_hash, *certs_,
                                       std::move(web_contents));
  }

 private:
  // Refresh list of cached certificates and run `callback` when done.
  void RefreshCachedCertificateList(base::OnceClosure callback) {
    if (!loader_) {
      std::move(callback).Run();
      return;
    }
    // Unretained is safe here as if `this` is destroyed, the ClientCertStore
    // will be destroyed, and the ClientCertStore contract is that the callback
    // will not be called after the ClientCertStore object is destroyed.
    loader_->GetCerts(base::BindOnce(&ClientCertSource::SaveCertsAndRespond,
                                     base::Unretained(this),
                                     std::move(callback)));
  }

  void ReplyToGetCertificatesCallback(
      CertificateManagerPageHandler::GetCertificatesCallback callback) const {
    PopulateCertInfosFromCertificateList(std::move(callback), *certs_,
                                         /*is_deletable=*/false);
  }

  void SaveCertsAndRespond(base::OnceClosure callback,
                           net::CertificateList certs) {
    certs_ = std::move(certs);
    std::move(callback).Run();
  }

  std::unique_ptr<ClientCertStoreLoader> loader_;
  std::optional<net::CertificateList> certs_;
};

#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
// ChromeOS currently can use either Kcer or NSS for listing client certs, and
// Linux uses NSS only. This interface provides an abstraction to hide that
// from WritableClientCertSource. Currently this class only handles reading
// from the database and writing is still handled directly inside
// WritableClientCertSource (even when Kcer is enabled, the ChromeOS code still
// writes to both NSS and Kcer). Once NSS client cert support is removed on
// ChromeOS, consider if things should be refactored as then NSS will only be
// used on Linux and Kcer will only be used on ChromeOS.
class WritableCertLoader : public CertificateManagerPageHandler::CertSource {
 public:
  virtual void RefreshCachedCertificateList(base::OnceClosure callback) = 0;

  void GetCertificateInfos(
      CertificateManagerPageHandler::GetCertificatesCallback callback)
      override {
    if (certs_) {
      ReplyToGetCertificatesCallback(std::move(callback));
      return;
    }
    RefreshCachedCertificateList(
        base::BindOnce(&WritableCertLoader::ReplyToGetCertificatesCallback,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void ViewCertificate(
      const std::string& sha256_hex_hash,
      base::WeakPtr<content::WebContents> web_contents) override {
    if (!certs_) {
      return;
    }

    net::X509Certificate* cert = FindCertificate(sha256_hex_hash);
    if (cert) {
      ShowCertificateDialog(std::move(web_contents),
                            bssl::UpRef(cert->cert_buffer()));
    }
  }

  net::X509Certificate* FindCertificate(
      std::string_view sha256_hex_hash) const {
    if (!certs_) {
      return nullptr;
    }

    net::SHA256HashValue hash;
    if (!base::HexStringToSpan(sha256_hex_hash, hash)) {
      return nullptr;
    }

    for (const auto& info : *certs_) {
      if (net::X509Certificate::CalculateFingerprint256(
              info.cert->cert_buffer()) == hash) {
        return info.cert.get();
      }
    }

    return nullptr;
  }

 protected:
  struct CertInfo {
    scoped_refptr<net::X509Certificate> cert;
    bool is_deletable;
  };

  void ReplyToGetCertificatesCallback(
      CertificateManagerPageHandler::GetCertificatesCallback callback) const {
    std::vector<certificate_manager::mojom::SummaryCertInfoPtr> out_infos;
    for (const auto& info : *certs_) {
      x509_certificate_model::X509CertificateModel model(
          bssl::UpRef(info.cert->cert_buffer()));
      out_infos.push_back(certificate_manager::mojom::SummaryCertInfo::New(
          model.HashCertSHA256(), model.GetTitle(), info.is_deletable));
    }
    std::move(callback).Run(std::move(out_infos));
  }

  std::optional<std::vector<CertInfo>> certs_;

 private:
  base::WeakPtrFactory<WritableCertLoader> weak_ptr_factory_{this};
};

#if BUILDFLAG(IS_CHROMEOS)
class KcerLoader : public WritableCertLoader {
 public:
  explicit KcerLoader(
      Profile* profile,
      mojo::Remote<certificate_manager::mojom::CertificateManagerPage>*
          remote_client)
      : profile_(profile),
        remote_client_(remote_client),
        kcer_(kcer::KcerFactoryAsh::GetKcer(profile)) {
    if (kcer_) {
      observer_callback_ = kcer_->AddObserver(base::BindRepeating(
          &KcerLoader::OnCertDbChanged, weak_ptr_factory_.GetWeakPtr()));
    }
  }
  ~KcerLoader() override = default;

  void RefreshCachedCertificateList(base::OnceClosure callback) override {
    if (!kcer_) {
      std::move(callback).Run();
      return;
    }

    kcer_->GetAvailableTokens(base::BindOnce(&KcerLoader::GotKcerTokens,
                                             weak_ptr_factory_.GetWeakPtr(),
                                             std::move(callback)));
  }

  void OnCertDbChanged() {
    RefreshCachedCertificateList(base::BindOnce(
        &KcerLoader::TriggerReload, weak_ptr_factory_.GetWeakPtr()));
  }

 private:
  void TriggerReload() {
    (*remote_client_)
        ->TriggerReload({certificate_manager::mojom::CertificateSource::
                             kPlatformClientCert});
  }

  void GotKcerTokens(base::OnceClosure callback,
                     base::flat_set<kcer::Token> tokens) {
    if (!kcer_) {
      std::move(callback).Run();
      return;
    }

    kcer_->ListCerts(
        std::move(tokens),
        base::BindOnce(&KcerLoader::GotKcerCerts,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void GotKcerCerts(base::OnceClosure callback,
                    std::vector<scoped_refptr<const kcer::Cert>> kcer_certs,
                    base::flat_map<kcer::Token, kcer::Error> kcer_errors) {
    ClientCertManagementAccessControls policy(profile_);
    certs_ = std::vector<CertInfo>();
    certs_->reserve(kcer_certs.size());
    for (scoped_refptr<const kcer::Cert>& cert : kcer_certs) {
      if (!cert || !cert->GetX509Cert()) {
        // Probably shouldn't happen, but double check just in case.
        continue;
      }

      // TODO(crbug.com/40928765): This should be checking each cert for if it
      // is software or hardware backed, however that information isn't in
      // kcer::Cert and requires doing an async GetKeyInfo call for each cert.
      // The only time the difference matters is in guest mode where deleting
      // hardware backed certs isn't allowed, however guest mode doesn't let
      // you import hardware backed certs in the first place. In any case the
      // correct behavior is still enforced if such a cert somehow existed and
      // the user tried to delete it. So while this is theoretically incorrect
      // it's probably not worth bothering to fix.
      bool is_deletable = policy.IsChangeAllowed(
          ClientCertManagementAccessControls::kSoftwareBacked,
          cert->GetToken() == kcer::Token::kDevice
              ? ClientCertManagementAccessControls::kDeviceWide
              : ClientCertManagementAccessControls::kUser);
      certs_->emplace_back(cert->GetX509Cert(), is_deletable);
    }

    std::move(callback).Run();
  }

  raw_ptr<Profile> profile_;
  raw_ptr<mojo::Remote<certificate_manager::mojom::CertificateManagerPage>>
      remote_client_;
  base::WeakPtr<kcer::Kcer> kcer_;
  base::CallbackListSubscription observer_callback_;
  base::WeakPtrFactory<KcerLoader> weak_ptr_factory_{this};
};

#else   // BUILDFLAG(IS_CHROMEOS)

class NSSLoader : public WritableCertLoader {
 public:
  explicit NSSLoader(Profile* profile)
      : profile_(profile),
        loader_(std::make_unique<ClientCertStoreLoader>(
            std::make_unique<ClientCertStoreFactoryNSS>())) {}
  ~NSSLoader() override = default;

  void RefreshCachedCertificateList(base::OnceClosure callback) override {
    if (!loader_) {
      std::move(callback).Run();
      return;
    }

    loader_->GetCerts(base::BindOnce(&NSSLoader::SaveCertsAndRespond,
                                     weak_ptr_factory_.GetWeakPtr(),
                                     std::move(callback)));
  }

 private:
  void SaveCertsAndRespond(base::OnceClosure callback,
                           net::CertificateList certs) {
    ClientCertManagementAccessControls policy(profile_);
    // TODO(crbug.com/40928765): This should actually be set by checking
    // ClientCertManagementAccessControls.IsChangeAllowed on a per-cert basis.
    // However listing certs using kcer is already the default so it's
    // questionable whether spending the effort to implement it correctly for
    // the NSS implementation is worth doing. In any case the correct behavior
    // is still enforced if the user tried to delete a cert where it mattered.
    const bool is_deletable = policy.IsManagementAllowed(
        ClientCertManagementAccessControls::kSoftwareBacked);
    certs_ = std::vector<CertInfo>();
    certs_->reserve(certs.size());
    for (scoped_refptr<net::X509Certificate>& cert : certs) {
      certs_->emplace_back(cert, is_deletable);
    }

    std::move(callback).Run();
  }

 private:
  raw_ptr<Profile> profile_;
  std::unique_ptr<ClientCertStoreLoader> loader_;
  base::WeakPtrFactory<NSSLoader> weak_ptr_factory_{this};
};
#endif  // BUILDFLAG(IS_CHROMEOS)

// Subclass of ClientCertSource that also allows importing client certificates
// to the ChromeOS or Linux client cert store.
class WritableClientCertSource
    : public CertificateManagerPageHandler::CertSource,
      public ui::SelectFileDialog::Listener {
 public:
  explicit WritableClientCertSource(
      mojo::Remote<certificate_manager::mojom::CertificateManagerPage>*
          remote_client,
      Profile* profile)
      : remote_client_(remote_client), profile_(profile) {
#if BUILDFLAG(IS_CHROMEOS)
    cert_loader_ = std::make_unique<KcerLoader>(profile, remote_client);
#else
    cert_loader_ = std::make_unique<NSSLoader>(profile);
#endif
  }

  ~WritableClientCertSource() override {
    if (select_file_dialog_) {
      select_file_dialog_->ListenerDestroyed();
    }
  }

  void GetCertificateInfos(
      CertificateManagerPageHandler::GetCertificatesCallback callback)
      override {
    cert_loader_->GetCertificateInfos(std::move(callback));
  }

  void ViewCertificate(
      const std::string& sha256_hex_hash,
      base::WeakPtr<content::WebContents> web_contents) override {
    cert_loader_->ViewCertificate(sha256_hex_hash, std::move(web_contents));
  }

  void ImportCertificate(
      base::WeakPtr<content::WebContents> web_contents,
      CertificateManagerPageHandler::ImportCertificateCallback callback)
      override {
    BeginImportCertificate(/*hardware_backed=*/false, std::move(web_contents),
                           std::move(callback));
  }

  void ImportAndBindCertificate(
      base::WeakPtr<content::WebContents> web_contents,
      CertificateManagerPageHandler::ImportCertificateCallback callback)
      override {
    BeginImportCertificate(/*hardware_backed=*/true, std::move(web_contents),
                           std::move(callback));
  }

  void DeleteCertificate(
      const std::string& display_name,
      const std::string& sha256hash_hex,
      CertificateManagerPageHandler::DeleteCertificateCallback callback)
      override {
    (*remote_client_)
        ->AskForConfirmation(
            l10n_util::GetStringFUTF8(
                IDS_SETTINGS_CERTIFICATE_MANAGER_V2_DELETE_CERT_TITLE,
                base::UTF8ToUTF16(display_name)),
            l10n_util::GetStringUTF8(
                IDS_SETTINGS_CERTIFICATE_MANAGER_V2_DELETE_CLIENT_CERT_DESCRIPTION),
            base::BindOnce(
                &WritableClientCertSource::GotDeleteCertificateConfirmation,
                weak_ptr_factory_.GetWeakPtr(), sha256hash_hex,
                std::move(callback)));
  }

  void BeginImportCertificate(
      bool hardware_backed,
      base::WeakPtr<content::WebContents> web_contents,
      CertificateManagerPageHandler::ImportCertificateCallback callback) {
    // Containing web contents went away (e.g. user navigated away) or dialog
    // is already open. Don't try to open the dialog.
    if (!web_contents || select_file_dialog_) {
      std::move(callback).Run(nullptr);
      return;
    }

    if (!ClientCertManagementAccessControls(profile_).IsManagementAllowed(
            hardware_backed
                ? ClientCertManagementAccessControls::kHardwareBacked
                : ClientCertManagementAccessControls::kSoftwareBacked)) {
      // This error is not expected to be displayed under normal circumstances,
      // so it's not localized.
      std::move(callback).Run(
          certificate_manager::mojom::ActionResult::NewError("not allowed"));
      return;
    }

    import_hardware_backed_ = hardware_backed;
    import_callback_ = std::move(callback);

    select_file_dialog_ = ui::SelectFileDialog::Create(
        this, std::make_unique<ChromeSelectFilePolicy>(web_contents.get()));

    ui::SelectFileDialog::FileTypeInfo file_type_info;
    file_type_info.extensions = {{FILE_PATH_LITERAL("p12"),
                                  FILE_PATH_LITERAL("pfx"),
                                  FILE_PATH_LITERAL("crt")}};
    file_type_info.include_all_files = true;
    select_file_dialog_->SelectFile(
        ui::SelectFileDialog::SELECT_OPEN_FILE, std::u16string(),
        base::FilePath(), &file_type_info,
        1,  // 1-based index for |file_type_info.extensions| to specify default.
        FILE_PATH_LITERAL("p12"), web_contents->GetTopLevelNativeWindow(),
        /*caller=*/nullptr);
  }

  // ui::SelectFileDialog::Listener
  void FileSelected(const ui::SelectedFileInfo& file, int index) override {
    select_file_dialog_ = nullptr;

    // Use CONTINUE_ON_SHUTDOWN since this is only for reading a file, if it
    // doesn't complete before shutdown the file still exists, and even if the
    // browser blocked on completing this task, the import isn't actually done
    // yet, so just blocking shutdown on the file read wouldn't accomplish
    // anything. CONTINUE_ON_SHUTDOWN should be safe as base::ReadFileToBytes
    // doesn't access any global state.
    base::ThreadPool::PostTaskAndReplyWithResult(
        FROM_HERE,
        {base::MayBlock(), base::TaskPriority::USER_BLOCKING,
         base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
        base::BindOnce(&base::ReadFileToBytes, file.path()),
        base::BindOnce(&WritableClientCertSource::FileRead,
                       weak_ptr_factory_.GetWeakPtr()));
  }

  void FileSelectionCanceled() override {
    select_file_dialog_ = nullptr;

    std::move(import_callback_).Run(nullptr);
  }

 private:
  void FileRead(std::optional<std::vector<uint8_t>> file_bytes) {
    if (!file_bytes) {
      std::move(import_callback_)
          .Run(certificate_manager::mojom::ActionResult::NewError(
              l10n_util::GetStringUTF8(
                  IDS_SETTINGS_CERTIFICATE_MANAGER_V2_READ_FILE_ERROR)));
      return;
    }

    (*remote_client_)
        ->AskForImportPassword(base::BindOnce(
            &WritableClientCertSource::GotImportPassword,
            weak_ptr_factory_.GetWeakPtr(), std::move(*file_bytes)));
  }

  void GotImportPassword(std::vector<uint8_t> file_bytes,
                         const std::optional<std::string>& password) {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
    if (!password) {
      std::move(import_callback_).Run(nullptr);
      return;
    }

    content::GetIOThreadTaskRunner({})->PostTask(
        FROM_HERE,
        base::BindOnce(
            &WritableClientCertSource::GetCertDBOnIOThread,
            NssServiceFactory::GetForContext(profile_)
                ->CreateNSSCertDatabaseGetterForIOThread(),
            base::BindOnce(
                &WritableClientCertSource::
                    GotNSSCertDatabaseForImportOnIOThread,
                import_hardware_backed_, std::move(file_bytes), *password,
                base::BindOnce(&WritableClientCertSource::FinishedNSSImport,
                               weak_ptr_factory_.GetWeakPtr()))));
  }

  static void GetCertDBOnIOThread(
      NssCertDatabaseGetter database_getter,
      base::OnceCallback<void(net::NSSCertDatabase*)> callback) {
    DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

    auto split_callback = base::SplitOnceCallback(std::move(callback));

    net::NSSCertDatabase* cert_db =
        std::move(database_getter).Run(std::move(split_callback.first));
    // If the NSS database was already available, |cert_db| is non-null and
    // |did_get_cert_db_callback| has not been called. Call it explicitly.
    if (cert_db) {
      std::move(split_callback.second).Run(cert_db);
    }
  }

  static void GotNSSCertDatabaseForImportOnIOThread(
      bool use_hardware_backed,
      std::vector<uint8_t> file_bytes,
      std::string password,
      base::OnceCallback<void(std::vector<uint8_t> file_bytes,
                              std::string password,
                              int nss_import_result)> finished_import_callback,
      net::NSSCertDatabase* cert_db) {
    DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

    crypto::ScopedPK11Slot slot;
    if (use_hardware_backed) {
      slot = cert_db->GetPrivateSlot();
    } else {
      slot = cert_db->GetPublicSlot();
    }
    bool is_extractable = !use_hardware_backed;
    // TODO(crbug.com/40928765): Should do the NSS import on worker thread, not
    // IO thread. (Would need to add an ImportFromPKCS12Async method on
    // NSSCertDatabase.)
    int nss_import_result = cert_db->ImportFromPKCS12(
        slot.get(), std::string(base::as_string_view(file_bytes)),
        base::UTF8ToUTF16(password), is_extractable, nullptr);

    content::GetUIThreadTaskRunner({})->PostTask(
        FROM_HERE, base::BindOnce(std::move(finished_import_callback),
                                  std::move(file_bytes), std::move(password),
                                  nss_import_result));
  }

  void FinishedNSSImport(std::vector<uint8_t> file_bytes,
                         std::string password,
                         int nss_import_result) {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

#if BUILDFLAG(IS_CHROMEOS)
    if (nss_import_result == net::OK) {
      kcer::RecordPkcs12MigrationUmaEvent(
          kcer::Pkcs12MigrationUmaEvent::kPkcs12ImportNssSuccess);
      // `import_hardware_backed_` == false indicates that the cert came from
      // the "Import" button. By default it's imported into the software NSS
      // database (aka public slot). With the experiment enabled it should also
      // be imported into Chaps. `import_hardware_backed_` == true means that
      // the cert came from the "Import and Bind" button and it's import into
      // Chaps by default.
      if (!import_hardware_backed_) {
        // Record the dual-write event. Even if the import fails, it's
        // theoretically possible that some related objects are still created
        // and would need to be deleted in case of a rollback.
        base::WeakPtr<kcer::Kcer> kcer =
            kcer::KcerFactoryAsh::GetKcer(profile_);
        if (kcer) {
          kcer::KcerFactoryAsh::RecordPkcs12CertDualWritten();
          return kcer->ImportPkcs12Cert(
              kcer::Token::kUser, kcer::Pkcs12Blob(std::move(file_bytes)),
              std::move(password),
              /*hardware_backed=*/import_hardware_backed_,
              /*mark_as_migrated=*/true,
              base::BindOnce(&WritableClientCertSource::FinishedKcerImport,
                             weak_ptr_factory_.GetWeakPtr(),
                             nss_import_result));
        }
      }
    } else {
      kcer::RecordPkcs12MigrationUmaEvent(
          kcer::Pkcs12MigrationUmaEvent::kPkcs12ImportNssFailed);
    }
#endif

    ReplyToImportCallback(nss_import_result);
  }

#if BUILDFLAG(IS_CHROMEOS)
  void FinishedKcerImport(
      int nss_import_result,
      base::expected<void, kcer::Error> kcer_import_result) {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

    if (kcer_import_result.has_value()) {
      kcer::RecordPkcs12MigrationUmaEvent(
          kcer::Pkcs12MigrationUmaEvent::kPkcs12ImportKcerSuccess);
    } else {
      kcer::RecordPkcs12MigrationUmaEvent(
          kcer::Pkcs12MigrationUmaEvent::kPkcs12ImportKcerFailed);
    }

    // Just return the nss_import_result. Kcer will attempt to import only if
    // NSS succeeds and even if Kcer fails, the cert should be usable.
    ReplyToImportCallback(nss_import_result);
  }
#endif

  void ReplyToImportCallback(int nss_import_result) {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

    if (nss_import_result == net::OK) {
      // Refresh the certificate list to include the newly imported cert, and
      // call the import complete callback once the list has been updated.
      cert_loader_->RefreshCachedCertificateList(base::BindOnce(
          std::move(import_callback_),
          certificate_manager::mojom::ActionResult::NewSuccess(
              certificate_manager::mojom::SuccessResult::kSuccess)));
    } else {
      // TODO(crbug.com/40928765): If the error was bad password, could prompt
      // the user to try again rather than just failing and requiring the user
      // to reselect the file to try again.
      int message_id;
      switch (nss_import_result) {
        case net::ERR_PKCS12_IMPORT_BAD_PASSWORD:
          message_id = IDS_SETTINGS_CERTIFICATE_MANAGER_V2_IMPORT_BAD_PASSWORD;
          break;
        case net::ERR_PKCS12_IMPORT_INVALID_MAC:
          message_id = IDS_SETTINGS_CERTIFICATE_MANAGER_V2_IMPORT_INVALID_MAC;
          break;
        case net::ERR_PKCS12_IMPORT_INVALID_FILE:
          message_id = IDS_SETTINGS_CERTIFICATE_MANAGER_V2_IMPORT_INVALID_FILE;
          break;
        case net::ERR_PKCS12_IMPORT_UNSUPPORTED:
          message_id = IDS_SETTINGS_CERTIFICATE_MANAGER_V2_IMPORT_UNSUPPORTED;
          break;
        default:
          message_id = IDS_SETTINGS_CERTIFICATE_MANAGER_V2_IMPORT_FAILED;
      }
      std::move(import_callback_)
          .Run(certificate_manager::mojom::ActionResult::NewError(
              l10n_util::GetStringUTF8(message_id)));
    }
  }

  void GotDeleteCertificateConfirmation(
      const std::string& sha256hash_hex,
      CertificateManagerPageHandler::DeleteCertificateCallback callback,
      bool confirmed) {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
    if (!confirmed) {
      std::move(callback).Run(nullptr);
      return;
    }

    scoped_refptr<net::X509Certificate> cert =
        cert_loader_->FindCertificate(sha256hash_hex);
    if (!cert) {
      // This error is not expected to be displayed under normal circumstances,
      // so it's not localized.
      std::move(callback).Run(
          certificate_manager::mojom::ActionResult::NewError("cert not found"));
      return;
    }

    content::GetIOThreadTaskRunner({})->PostTask(
        FROM_HERE,
        base::BindOnce(
            &WritableClientCertSource::GetCertDBOnIOThread,
            NssServiceFactory::GetForContext(profile_)
                ->CreateNSSCertDatabaseGetterForIOThread(),
            base::BindOnce(
                &WritableClientCertSource::
                    GotNSSCertDatabaseForDeleteOnIOThread,
                cert, ClientCertManagementAccessControls(profile_),
                base::BindOnce(&WritableClientCertSource::FinishedDelete,
                               weak_ptr_factory_.GetWeakPtr(),
                               std::move(callback)))));
  }

  static void GotNSSCertDatabaseForDeleteOnIOThread(
      scoped_refptr<net::X509Certificate> cert,
      ClientCertManagementAccessControls client_cert_policy,
      base::OnceCallback<void(bool nss_delete_result)> finished_delete_callback,
      net::NSSCertDatabase* cert_db) {
    DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

    net::ScopedCERTCertificate nss_cert =
        net::x509_util::CreateCERTCertificateFromX509Certificate(cert.get());

    if (!nss_cert) {
      content::GetUIThreadTaskRunner({})->PostTask(
          FROM_HERE,
          base::BindOnce(std::move(finished_delete_callback), false));
      return;
    }

    const auto hardware_backed =
        cert_db->IsHardwareBacked(nss_cert.get())
            ? ClientCertManagementAccessControls::kHardwareBacked
            : ClientCertManagementAccessControls::kSoftwareBacked;
    const auto device_wide =
#if BUILDFLAG(IS_CHROMEOS)
        cert_db->IsCertificateOnSlot(nss_cert.get(),
                                     cert_db->GetSystemSlot().get())
            ? ClientCertManagementAccessControls::kDeviceWide
            : ClientCertManagementAccessControls::kUser
#else
        ClientCertManagementAccessControls::kUser
#endif
        ;
    if (!client_cert_policy.IsChangeAllowed(hardware_backed, device_wide)) {
      content::GetUIThreadTaskRunner({})->PostTask(
          FROM_HERE,
          base::BindOnce(std::move(finished_delete_callback), false));
      return;
    }

    cert_db->DeleteCertAndKeyAsync(
        std::move(nss_cert),
        base::BindPostTask(content::GetUIThreadTaskRunner({}),
                           std::move(finished_delete_callback)));
  }

  void FinishedDelete(
      CertificateManagerPageHandler::DeleteCertificateCallback callback,
      bool delete_result) {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

    if (delete_result) {
      // Refresh the certificate list to remove the deleted cert, and
      // call the deletion complete callback once the list has been updated.
      cert_loader_->RefreshCachedCertificateList(base::BindOnce(
          std::move(callback),
          certificate_manager::mojom::ActionResult::NewSuccess(
              certificate_manager::mojom::SuccessResult::kSuccess)));
    } else {
      // TODO(crbug.com/40928765): pass through better error status codes from
      // the lower level deletion code?
      std::move(callback).Run(
          certificate_manager::mojom::ActionResult::NewError(
              l10n_util::GetStringUTF8(
                  IDS_SETTINGS_CERTIFICATE_MANAGER_V2_DELETE_ERROR)));
    }
  }

  std::unique_ptr<WritableCertLoader> cert_loader_;
  scoped_refptr<ui::SelectFileDialog> select_file_dialog_;
  bool import_hardware_backed_;
  CertificateManagerPageHandler::ImportCertificateCallback import_callback_;
  raw_ptr<mojo::Remote<certificate_manager::mojom::CertificateManagerPage>>
      remote_client_;
  raw_ptr<Profile> profile_;
  base::WeakPtrFactory<WritableClientCertSource> weak_ptr_factory_{this};
};
#endif  // BUILDFLAG(IS_CHROMEOS)

#if BUILDFLAG(IS_CHROMEOS)
class ExtensionsClientCertSource
    : public CertificateManagerPageHandler::CertSource {
 public:
  explicit ExtensionsClientCertSource(
      std::unique_ptr<chromeos::certificate_provider::CertificateProvider>
          provider)
      : provider_(std::move(provider)) {}
  ~ExtensionsClientCertSource() override = default;

  void GetCertificateInfos(
      CertificateManagerPageHandler::GetCertificatesCallback callback)
      override {
    if (!provider_) {
      std::move(callback).Run({});
      return;
    }
    if (certs_) {
      PopulateCertInfosFromCertificateList(std::move(callback), *certs_,
                                           /*is_deletable=*/false);
      return;
    }

    provider_->GetCertificates(
        base::BindOnce(&ExtensionsClientCertSource::SaveCertsAndRespond,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void ViewCertificate(
      const std::string& sha256_hex_hash,
      base::WeakPtr<content::WebContents> web_contents) override {
    if (!provider_ || !certs_) {
      return;
    }
    ViewCertificateFromCertificateList(sha256_hex_hash, *certs_,
                                       std::move(web_contents));
  }

 private:
  void SaveCertsAndRespond(
      CertificateManagerPageHandler::GetCertificatesCallback callback,
      net::ClientCertIdentityList cert_identities) {
    certs_ = net::CertificateList();
    certs_->reserve(cert_identities.size());
    for (const auto& identity : cert_identities) {
      certs_->push_back(identity->certificate());
    }
    PopulateCertInfosFromCertificateList(std::move(callback), *certs_,
                                         /*is_deletable=*/false);
  }

  std::unique_ptr<chromeos::certificate_provider::CertificateProvider>
      provider_;
  std::optional<net::CertificateList> certs_;
  base::WeakPtrFactory<ExtensionsClientCertSource> weak_ptr_factory_{this};
};
#endif  // BUILDFLAG(IS_CHROMEOS)

}  // namespace

std::unique_ptr<CertificateManagerPageHandler::CertSource>
CreatePlatformClientCertSource(
    mojo::Remote<certificate_manager::mojom::CertificateManagerPage>*
        remote_client,
    Profile* profile) {
#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
  return std::make_unique<WritableClientCertSource>(remote_client, profile);
#else
  return std::make_unique<ClientCertSource>(
      CreatePlatformClientCertLoader(profile));
#endif
}

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
std::unique_ptr<CertificateManagerPageHandler::CertSource>
CreateProvisionedClientCertSource(Profile* profile) {
  return std::make_unique<ClientCertSource>(
      CreateProvisionedClientCertLoader(profile));
}
#endif

#if BUILDFLAG(IS_CHROMEOS)
std::unique_ptr<CertificateManagerPageHandler::CertSource>
CreateExtensionsClientCertSource(Profile* profile) {
  chromeos::CertificateProviderService* certificate_provider_service =
      chromeos::CertificateProviderServiceFactory::GetForBrowserContext(
          profile);
  return std::make_unique<ExtensionsClientCertSource>(
      certificate_provider_service->CreateCertificateProvider());
}

ClientCertManagementAccessControls::ClientCertManagementAccessControls(
    Profile* profile)
    : is_guest_(
          user_manager::UserManager::Get()->IsLoggedInAsGuest() ||
          user_manager::UserManager::Get()->IsLoggedInAsManagedGuestSession()),
      client_cert_policy_(static_cast<ClientCertificateManagementPermission>(
          profile->GetPrefs()->GetInteger(
              prefs::kClientCertificateManagementAllowed))) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
}

bool ClientCertManagementAccessControls::IsManagementAllowed(
    KeyStorage key_storage) const {
  // Kiosks should allow client cert management, as per customer issue.
  // See b/409500766.
  return !(key_storage == kHardwareBacked && is_guest_) &&
         client_cert_policy_ != ClientCertificateManagementPermission::kNone;
}

bool ClientCertManagementAccessControls::IsChangeAllowed(
    KeyStorage key_storage,
    CertLocation cert_location) const {
  if (!IsManagementAllowed(key_storage)) {
    return false;
  }

  if (cert_location == kUser) {
    return client_cert_policy_ != ClientCertificateManagementPermission::kNone;
  }

  return client_cert_policy_ == ClientCertificateManagementPermission::kAll;
}
#elif BUILDFLAG(IS_LINUX)
ClientCertManagementAccessControls::ClientCertManagementAccessControls(
    Profile* profile) {}

// The ClientCertificateManagementAllowed enterprise policy is ChromeOS only.
// There's no technical reason it couldn't be supported on Linux too, but
// unless someone asks for it, keeping the status quo is fine.
bool ClientCertManagementAccessControls::IsManagementAllowed(
    KeyStorage key_storage) const {
  return true;
}

bool ClientCertManagementAccessControls::IsChangeAllowed(
    KeyStorage key_storage,
    CertLocation cert_location) const {
  return true;
}
#endif  // BUILDFLAG(IS_LINUX)
