| // 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/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/chrome_features.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/sha2.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::kSHA256Length> 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::SHA256Hash(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}); |
| } |