blob: 82d014ae71e4e098a0badb9f1f09448cb94aaa48 [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 "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 {
public:
explicit ClientCertStoreLoader(std::unique_ptr<net::ClientCertStore> store)
: store_(std::move(store)) {}
void GetCerts(base::OnceCallback<void(net::CertificateList)> callback) {
store_->GetClientCerts(
base::MakeRefCounted<net::SSLCertRequestInfo>(),
base::BindOnce(&ClientCertStoreLoader::HandleClientCertsResult,
std::move(callback)));
}
private:
static void HandleClientCertsResult(
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());
}
std::move(callback).Run(std::move(certs));
}
std::unique_ptr<net::ClientCertStore> store_;
};
std::unique_ptr<ClientCertStoreLoader> CreatePlatformClientCertLoader() {
#if BUILDFLAG(USE_NSS_CERTS)
return std::make_unique<ClientCertStoreLoader>(
std::make_unique<net::ClientCertStoreNSS>(
base::BindRepeating(&CreateCryptoModuleBlockingPasswordDelegate,
kCryptoModulePasswordClientAuth)));
#elif BUILDFLAG(IS_WIN)
return std::make_unique<ClientCertStoreLoader>(
std::make_unique<net::ClientCertStoreWin>());
#elif BUILDFLAG(IS_MAC)
return std::make_unique<ClientCertStoreLoader>(
std::make_unique<net::ClientCertStoreMac>());
#else
return nullptr;
#endif
}
#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 {
public:
~NullClientCertStore() override = default;
void GetClientCerts(
scoped_refptr<const net::SSLCertRequestInfo> cert_request_info,
ClientCertListCallback callback) override {
std::move(callback).Run({});
}
};
std::unique_ptr<ClientCertStoreLoader> CreateProvisionedClientCertLoader(
Profile* profile) {
if (!profile || !client_certificates::features::
IsManagedClientCertificateForUserEnabled()) {
return nullptr;
}
auto* provisioning_service =
client_certificates::CertificateProvisioningServiceFactory::GetForProfile(
profile);
if (!provisioning_service) {
return nullptr;
}
return std::make_unique<ClientCertStoreLoader>(
client_certificates::ClientCertificatesService::Create(
provisioning_service, std::make_unique<NullClientCertStore>()));
}
#endif
void PopulateCertInfosFromCertificateList(
CertificateManagerPageHandler::GetCertificatesCallback callback,
const net::CertificateList& certs) {
std::vector<certificate_manager_v2::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_v2::mojom::SummaryCertInfo::New(
model.HashCertSHA256(), model.GetTitle()));
}
std::move(callback).Run(std::move(out_infos));
}
void ViewCertificateFromCertificateList(
const std::string& sha256_hex_hash,
const net::CertificateList& certs,
base::WeakPtr<content::WebContents> web_contents) {
if (!web_contents) {
return;
}
net::SHA256HashValue hash;
if (!base::HexStringToSpan(sha256_hex_hash, hash.data)) {
return;
}
for (const auto& cert : certs) {
if (net::X509Certificate::CalculateFingerprint256(cert->cert_buffer()) ==
hash) {
ShowCertificateDialog(std::move(web_contents),
bssl::UpRef(cert->cert_buffer()));
return;
}
}
}
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 (!loader_) {
std::move(callback).Run({});
return;
}
if (certs_) {
PopulateCertInfosFromCertificateList(std::move(callback), *certs_);
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 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:
void SaveCertsAndRespond(
CertificateManagerPageHandler::GetCertificatesCallback callback,
net::CertificateList certs) {
certs_ = std::move(certs);
PopulateCertInfosFromCertificateList(std::move(callback), *certs_);
}
std::unique_ptr<ClientCertStoreLoader> loader_;
std::optional<net::CertificateList> certs_;
};
#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_CHROMEOS_ASH)
return std::make_unique<CrosClientCertSource>(
CreatePlatformClientCertLoader(), remote_client);
#else
return std::make_unique<ClientCertSource>(CreatePlatformClientCertLoader());
#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());
}
#endif