// 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/user_cert_sources.h"

#include <vector>

#include "base/containers/to_vector.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/net/server_certificate_database_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/certificate_dialogs.h"
#include "chrome/browser/ui/select_file_policy/chrome_select_file_policy.h"
#include "chrome/browser/ui/webui/certificate_manager/certificate_manager_handler.h"
#include "chrome/browser/ui/webui/certificate_manager/certificate_manager_utils.h"
#include "chrome/browser/ui/webui/certificate_viewer/certificate_viewer_webui.h"
#include "chrome/common/net/x509_certificate_model.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "components/prefs/pref_service.h"
#include "components/server_certificate_database/server_certificate_database.h"
#include "components/server_certificate_database/server_certificate_database.pb.h"
#include "components/server_certificate_database/server_certificate_database_service.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/browser/web_contents.h"
#include "crypto/hash.h"
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util.h"
#include "services/cert_verifier/public/mojom/cert_verifier_service_factory.mojom.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/shell_dialogs/selected_file_info.h"

namespace {

void PopulateUserCertsAsync(
    CertificateManagerPageHandler::GetCertificatesCallback callback,
    chrome_browser_server_certificate_database::CertificateTrust::
        CertificateTrustType trust,
    bool can_delete,
    std::vector<net::ServerCertificateDatabase::CertInformation>
        server_cert_infos) {
  std::vector<certificate_manager::mojom::SummaryCertInfoPtr> cert_infos;
  for (auto const& cert_info : server_cert_infos) {
    if (cert_info.cert_metadata.trust().trust_type() != trust) {
      continue;
    }
    x509_certificate_model::X509CertificateModel model(
        net::x509_util::CreateCryptoBuffer(cert_info.der_cert));
    cert_infos.push_back(certificate_manager::mojom::SummaryCertInfo::New(
        cert_info.sha256hash_hex, model.GetTitle(), can_delete));
  }
  std::move(callback).Run(std::move(cert_infos));
}

void ReloadAllUserCerts(base::WeakPtr<UserCertSource> user_cert_source,
                        base::OnceCallback<void(bool)> update_callback,
                        bool success) {
  if (user_cert_source && success) {
    user_cert_source->TriggerReload();
  }
  std::move(update_callback).Run(success);
}

void UpdateCertificateAsync(
    base::WeakPtr<Profile> profile,
    base::WeakPtr<UserCertSource> user_cert_source,
    net::ServerCertificateDatabase::CertInformation cert_info,
    base::OnceCallback<void(bool)> update_callback) {
  if (!profile || !user_cert_source) {
    std::move(update_callback).Run(false);
    return;
  }
  net::ServerCertificateDatabaseService* server_cert_service =
      net::ServerCertificateDatabaseServiceFactory::GetForBrowserContext(
          profile.get());
  if (!server_cert_service) {
    std::move(update_callback).Run(false);
    return;
  }
  std::vector<net::ServerCertificateDatabase::CertInformation> cert_infos;
  cert_infos.push_back(std::move(cert_info));
  server_cert_service->AddOrUpdateUserCertificates(
      std::move(cert_infos),
      base::BindOnce(&ReloadAllUserCerts, user_cert_source,
                     std::move(update_callback)));
}

void ViewCertificateAsync(
    std::string sha256_hex_hash,
    chrome_browser_server_certificate_database::CertificateTrust::
        CertificateTrustType trust,
    base::WeakPtr<content::WebContents> web_contents,
    base::WeakPtr<Profile> profile,
    base::WeakPtr<UserCertSource> user_cert_source,
    std::vector<net::ServerCertificateDatabase::CertInformation>
        server_cert_infos) {
  // Containing web contents went away (e.g. user navigated away). Don't
  // try to open the dialog.
  if (!web_contents) {
    return;
  }

  std::array<uint8_t, crypto::hash::kSha256Size> hash;
  if (!base::HexStringToSpan(sha256_hex_hash, hash)) {
    return;
  }

  for (auto const& cert_info : server_cert_infos) {
    if (cert_info.cert_metadata.trust().trust_type() != trust) {
      continue;
    }
    if (hash == crypto::hash::Sha256(cert_info.der_cert)) {
      // Found the cert, open cert viewer dialog if able and return.
      if (IsCACertificateManagementAllowed(*profile->GetPrefs())) {
        ShowCertificateDialog(
            std::move(web_contents),
            net::x509_util::CreateCryptoBuffer(cert_info.der_cert),
            cert_info.cert_metadata,
            base::BindRepeating(&UpdateCertificateAsync, profile,
                                user_cert_source));
      } else {
        ShowCertificateDialog(
            std::move(web_contents),
            net::x509_util::CreateCryptoBuffer(cert_info.der_cert),
            cert_info.cert_metadata, base::NullCallback());
      }
      return;
    }
  }
}

void ExportCertificatesAsync(
    base::WeakPtr<content::WebContents> web_contents,
    chrome_browser_server_certificate_database::CertificateTrust::
        CertificateTrustType trust,
    std::string file_name,
    std::vector<net::ServerCertificateDatabase::CertInformation>
        server_cert_infos) {
  // Containing web contents went away (e.g. user navigated away). Don't
  // try to open the dialog.
  if (!web_contents) {
    return;
  }

  std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> export_certs;
  for (auto const& cert_info : server_cert_infos) {
    if (cert_info.cert_metadata.trust().trust_type() != trust) {
      continue;
    }
    export_certs.push_back(
        net::x509_util::CreateCryptoBuffer(cert_info.der_cert));
  }
  ShowCertExportDialogSaveAll(web_contents.get(),
                              web_contents->GetTopLevelNativeWindow(),
                              std::move(export_certs), file_name);
}

}  // namespace

UserCertSource::UserCertSource(
    std::string export_file_name,
    chrome_browser_server_certificate_database::CertificateTrust::
        CertificateTrustType trust,
    raw_ptr<Profile> profile,
    mojo::Remote<certificate_manager::mojom::CertificateManagerPage>*
        remote_client)
    : export_file_name_(std::move(export_file_name)),
      trust_(trust),
      profile_(profile),
      remote_client_(remote_client) {}

UserCertSource::~UserCertSource() {
  if (select_file_dialog_) {
    select_file_dialog_->ListenerDestroyed();
  }
}

void UserCertSource::GetCertificateInfos(
    CertificateManagerPageHandler::GetCertificatesCallback callback) {
  net::ServerCertificateDatabaseService* server_cert_service =
      net::ServerCertificateDatabaseServiceFactory::GetForBrowserContext(
          profile_);
  if (!server_cert_service) {
    std::vector<certificate_manager::mojom::SummaryCertInfoPtr> cert_infos;
    std::move(callback).Run(std::move(cert_infos));
    return;
  }
  bool can_delete = IsCACertificateManagementAllowed(*profile_->GetPrefs());
  server_cert_service->GetAllCertificates(base::BindOnce(
      &PopulateUserCertsAsync, std::move(callback), trust_, can_delete));
}

void UserCertSource::ViewCertificate(
    const std::string& sha256_hex_hash,
    base::WeakPtr<content::WebContents> web_contents) {
  net::ServerCertificateDatabaseService* server_cert_service =
      net::ServerCertificateDatabaseServiceFactory::GetForBrowserContext(
          profile_);
  if (!server_cert_service) {
    return;
  }
  server_cert_service->GetAllCertificates(base::BindOnce(
      &ViewCertificateAsync, sha256_hex_hash, trust_, web_contents,
      profile_->GetWeakPtr(), weak_ptr_factory_.GetWeakPtr()));
}

void UserCertSource::ExportCertificates(
    base::WeakPtr<content::WebContents> web_contents) {
  net::ServerCertificateDatabaseService* server_cert_service =
      net::ServerCertificateDatabaseServiceFactory::GetForBrowserContext(
          profile_);
  if (!server_cert_service) {
    return;
  }
  server_cert_service->GetAllCertificates(base::BindOnce(
      &ExportCertificatesAsync, web_contents, trust_, export_file_name_));
}

void UserCertSource::DeleteCertificate(
    const std::string& display_name,
    const std::string& sha256hash_hex,
    CertificateManagerPageHandler::DeleteCertificateCallback callback) {
  PrefService* prefs = profile_->GetPrefs();
  // This error string does not need localization since it will not be shown on
  // UI. If the pref is not set, the UI to delete certificates will not be
  // shown.
  if (!IsCACertificateManagementAllowed(*prefs)) {
    std::move(callback).Run(certificate_manager::mojom::ActionResult::NewError(
        "Deleting certificates is not allowed"));
    return;
  }
  (*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_SERVER_CERT_DESCRIPTION),
          base::BindOnce(&UserCertSource::GotDeleteConfirmation,
                         weak_ptr_factory_.GetWeakPtr(), sha256hash_hex,
                         std::move(callback)));
}

void UserCertSource::GotDeleteConfirmation(
    const std::string& sha256hash_hex,
    CertificateManagerPageHandler::DeleteCertificateCallback callback,
    bool confirmed) {
  if (confirmed) {
    net::ServerCertificateDatabaseService* server_cert_service =
        net::ServerCertificateDatabaseServiceFactory::GetForBrowserContext(
            profile_.get());
    if (server_cert_service) {
      server_cert_service->DeleteCertificate(
          sha256hash_hex,
          base::BindOnce(&UserCertSource::DeleteCertificateResultAsync,
                         weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
      return;
    }
  }
  std::move(callback).Run(nullptr);
}

void UserCertSource::DeleteCertificateResultAsync(
    CertificateManagerPageHandler::DeleteCertificateCallback callback,
    bool result) {
  if (result) {
    // Trigger metadata refresh on local certs page to update count.
    (*remote_client_)->TriggerMetadataUpdate();

    std::move(callback).Run(
        certificate_manager::mojom::ActionResult::NewSuccess(
            certificate_manager::mojom::SuccessResult::kSuccess));
    return;
  }
  std::move(callback).Run(certificate_manager::mojom::ActionResult::NewError(
      "Error deleting certificate"));
}

void UserCertSource::ImportCertificate(
    base::WeakPtr<content::WebContents> web_contents,
    CertificateManagerPageHandler::ImportCertificateCallback callback) {
  if (!web_contents || select_file_dialog_) {
    std::move(callback).Run(nullptr);
    return;
  }

  PrefService* prefs = profile_->GetPrefs();
  // This error string does not need localization since it will not be shown on
  // UI. If the pref is not set, the UI to delete certificates will not be
  // shown.
  if (!IsCACertificateManagementAllowed(*prefs)) {
    std::move(callback).Run(certificate_manager::mojom::ActionResult::NewError(
        "Importing certificates is not allowed"));
    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("der"), FILE_PATH_LITERAL("cer"),
       FILE_PATH_LITERAL("crt"), FILE_PATH_LITERAL("pem"),
       FILE_PATH_LITERAL("p7b"), FILE_PATH_LITERAL("p7c")}};
  file_type_info.include_all_files = true;
  select_file_dialog_->SelectFile(
      ui::SelectFileDialog::SELECT_OPEN_FILE, std::u16string(),
      base::FilePath(), &file_type_info, 1, FILE_PATH_LITERAL("der"),
      web_contents->GetTopLevelNativeWindow(), nullptr);
}

void UserCertSource::FileSelected(const ui::SelectedFileInfo& file, int index) {
  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(&UserCertSource::FileRead,
                     weak_ptr_factory_.GetWeakPtr()));
}

void UserCertSource::FileSelectionCanceled() {
  select_file_dialog_ = nullptr;
  std::move(import_callback_).Run(nullptr);
}

void UserCertSource::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;
  }
  net::CertificateList certs_to_import =
      net::X509Certificate::CreateCertificateListFromBytes(
          file_bytes.value(), net::X509Certificate::FORMAT_AUTO);

  if (certs_to_import.size() != 1) {
    if (certs_to_import.size() == 0) {
      std::move(import_callback_)
          .Run(certificate_manager::mojom::ActionResult::NewError(
              l10n_util::GetStringUTF8(
                  IDS_SETTINGS_CERTIFICATE_MANAGER_V2_IMPORT_INVALID_FILE)));
      return;
    }
    std::move(import_callback_)
        .Run(certificate_manager::mojom::ActionResult::NewError(
            l10n_util::GetStringUTF8(
                IDS_SETTINGS_CERTIFICATE_MANAGER_V2_MULTIPLE_CERT_ERROR)));
    return;
  }

  scoped_refptr<net::X509Certificate> cert_to_import = certs_to_import[0];

  net::ServerCertificateDatabaseService* server_cert_service =
      net::ServerCertificateDatabaseServiceFactory::GetForBrowserContext(
          profile_);
  if (!server_cert_service) {
    std::move(import_callback_).Run(nullptr);
    return;
  }

  net::ServerCertificateDatabase::CertInformation cert_info(
      cert_to_import->cert_span());
  cert_info.cert_metadata.mutable_trust()->set_trust_type(trust_);

  std::vector<net::ServerCertificateDatabase::CertInformation> cert_infos;
  cert_infos.push_back(std::move(cert_info));
  server_cert_service->AddOrUpdateUserCertificates(
      std::move(cert_infos),
      base::BindOnce(&UserCertSource::ImportCertificateResult,
                     weak_ptr_factory_.GetWeakPtr()));
}

void UserCertSource::ImportCertificateResult(bool success) {
  if (success) {
    // Trigger metadata refresh on local certs page to update count.
    (*remote_client_)->TriggerMetadataUpdate();

    std::move(import_callback_)
        .Run(certificate_manager::mojom::ActionResult::NewSuccess(
            certificate_manager::mojom::SuccessResult::kSuccess));
    return;
  }
  std::move(import_callback_)
      .Run(certificate_manager::mojom::ActionResult::NewError(
          l10n_util::GetStringUTF8(
              IDS_SETTINGS_CERTIFICATE_MANAGER_V2_IMPORT_ERROR_TITLE)));
}

void UserCertSource::TriggerReload() {
  (*remote_client_)
      ->TriggerReload(
          {certificate_manager::mojom::CertificateSource::kUserTrustedCerts,
           certificate_manager::mojom::CertificateSource::
               kUserIntermediateCerts,
           certificate_manager::mojom::CertificateSource::
               kUserDistrustedCerts});
}
