blob: dbd3d9a820ecc37dc615efa541dc5a57df7b1bb0 [file] [log] [blame]
// 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-shared.h"
#include "chrome/browser/resources/certificate_manager/certificate_manager.mojom.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 "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "content/public/browser/browser_thread.h"
#include "crypto/crypto_buildflags.h"
#include "crypto/sha2.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 = nullptr;
if (client_certificates::features::
IsManagedBrowserClientCertificateEnabled()) {
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);
kcer::RecordKcerError(kcer_import_result.error());
}
// 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)