chromium/chrome/browser/ui/webui/certificate_manager/client_cert_sources.cc

// 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 "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/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 "crypto/crypto_buildflags.h"
#include "crypto/sha2.h"
#include "net/base/hash_value.h"
#include "net/cert/x509_certificate.h"
#include "net/ssl/client_cert_identity.h"
#include "net/ssl/client_cert_store.h"
#include "net/ssl/ssl_cert_request_info.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/ui/crypto_module_delegate_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/enterprise/client_certificates/certificate_provisioning_service_factory.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 "chrome/browser/certificate_provider/certificate_provider.h"
#include "chrome/browser/certificate_provider/certificate_provider_service.h"
#include "chrome/browser/certificate_provider/certificate_provider_service_factory.h"
#endif

namespace {

// A certificate loader that wraps a ClientCertStore. Read-only.
// Lifetimes note: The callback will not be called if the ClientCertStoreLoader
// (and thus, the ClientCertStore) is destroyed first.
class ClientCertStoreLoader {};

std::unique_ptr<ClientCertStoreLoader> CreatePlatformClientCertLoader() {}

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
// ClientCertStore implementation that always returns an empty list. The
// CertificateProvisioningService implementation expects to wrap a platform
// cert store, but here we only want to get results from the provisioning
// service itself, so instead of a platform cert store we pass an
// implementation that always returns an empty result when queried.
class NullClientCertStore : public net::ClientCertStore {};

std::unique_ptr<ClientCertStoreLoader> CreateProvisionedClientCertLoader(
    Profile* profile) {}
#endif

void PopulateCertInfosFromCertificateList(
    CertificateManagerPageHandler::GetCertificatesCallback callback,
    const net::CertificateList& certs) {}

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

class ClientCertSource : public CertificateManagerPageHandler::CertSource {};

#if BUILDFLAG(IS_CHROMEOS_ASH)
// Subclass of ClientCertSource that also allows importing client certificates
// to the ChromeOS client cert store.
class CrosClientCertSource : public ClientCertSource,
                             public ui::SelectFileDialog::Listener {
 public:
  explicit CrosClientCertSource(
      std::unique_ptr<ClientCertStoreLoader> loader,
      mojo::Remote<certificate_manager_v2::mojom::CertificateManagerPage>*
          remote_client)
      : ClientCertSource(std::move(loader)), remote_client_(remote_client) {}

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

  void ImportCertificate(
      base::WeakPtr<content::WebContents> web_contents,
      CertificateManagerPageHandler::ImportCertificateCallback callback)
      override {
    // 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;
    }

    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(),
        /*params=*/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(&CrosClientCertSource::FileRead,
                       weak_ptr_factory_.GetWeakPtr()));
  }

  void FileSelectionCanceled() override {
    select_file_dialog_ = nullptr;

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

  void FileRead(std::optional<std::vector<uint8_t>> file_bytes) {
    if (!file_bytes) {
      // TODO(crbug.com/40928765): localize
      std::move(import_callback_)
          .Run(certificate_manager_v2::mojom::ImportResult::NewError(
              "error reading file"));
      return;
    }

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

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

    // TODO(crbug.com/40928765): actually do the import
    std::move(import_callback_)
        .Run(certificate_manager_v2::mojom::ImportResult::NewError(
            "not implemented"));
  }

 private:
  scoped_refptr<ui::SelectFileDialog> select_file_dialog_;
  CertificateManagerPageHandler::ImportCertificateCallback import_callback_;
  raw_ptr<mojo::Remote<certificate_manager_v2::mojom::CertificateManagerPage>>
      remote_client_;
  base::WeakPtrFactory<CrosClientCertSource> weak_ptr_factory_{this};
};
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

#if BUILDFLAG(IS_CHROMEOS)
class ExtensionsClientCertSource
    : public CertificateManagerPageHandler::CertSource {
 public:
  explicit ExtensionsClientCertSource(
      std::unique_ptr<chromeos::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_);
      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_);
  }

  std::unique_ptr<chromeos::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_v2::mojom::CertificateManagerPage>*
        remote_client) {}

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
std::unique_ptr<CertificateManagerPageHandler::CertSource>
CreateProvisionedClientCertSource(Profile* 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());
}
#endif