// Copyright 2020 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/lacros/cert/cert_db_initializer_impl.h"
#include <optional>
#include <utility>
#include "base/check.h"
#include "base/files/file_path.h"
#include "base/memory/weak_ptr.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "cert_db_initializer_io_impl.h"
#include "chrome/browser/profiles/profile.h"
#include "chromeos/components/kiosk/kiosk_utils.h"
#include "chromeos/crosapi/cpp/crosapi_constants.h"
#include "chromeos/crosapi/mojom/cert_database.mojom.h"
#include "chromeos/crosapi/mojom/crosapi.mojom.h"
#include "chromeos/lacros/lacros_service.h"
#include "chromeos/startup/browser_params_proxy.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "crypto/scoped_nss_types.h"
#include "mojo/public/cpp/bindings/remote.h"
using CrosapiCertDb = crosapi::mojom::CertDatabase;
constexpr int kAddAshCertDatabaseObserverMinVersion =
CrosapiCertDb::kAddAshCertDatabaseObserverMinVersion;
// This object is split between UI and IO threads:
// * CertDbInitializerImpl - is the outer layer that implements the KeyedService
// interface, implicitly tracks lifetime of its profile, triggers the
// initialization and implements the UI part of the NSSCertDatabaseGetter
// interface;
// * CertDbInitializerIOImpl - is the second (inner) part, it lives on the IO
// thread, manages lifetime of loaded NSS slots, NSSCertDatabase and implements
// the IO part of the NSSCertDatabaseGetter interface.
CertDbInitializerImpl::CertDbInitializerImpl(Profile* profile)
: profile_(profile) {
DCHECK(profile_);
cert_db_initializer_io_ = std::make_unique<CertDbInitializerIOImpl>();
}
CertDbInitializerImpl::~CertDbInitializerImpl() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// In case the initialization didn't finish, notify waiting observers.
OnCertDbInitializationFinished();
content::GetIOThreadTaskRunner({})->DeleteSoon(
FROM_HERE, std::move(cert_db_initializer_io_));
}
void CertDbInitializerImpl::Start() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
chromeos::LacrosService* lacros_service = chromeos::LacrosService::Get();
if (!profile_->IsMainProfile() || !lacros_service ||
!lacros_service->IsAvailable<CrosapiCertDb>()) {
// TODO(b/191336028): Implement fully functional and separated certificate
// database for secondary profiles when NSS library is replaced with
// something more flexible.
return InitializeReadOnlyCertDb();
}
if (lacros_service->GetInterfaceVersion<CrosapiCertDb>() >=
kAddAshCertDatabaseObserverMinVersion) {
lacros_service->GetRemote<CrosapiCertDb>()->AddAshCertDatabaseObserver(
receiver_.BindNewPipeAndPassRemote());
}
InitializeForMainProfile();
}
base::CallbackListSubscription CertDbInitializerImpl::WaitUntilReady(
base::OnceClosure callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (is_ready_) {
// Even if the database is ready, avoid calling the callback synchronously.
// We still want to support returning a CallbackListSubscription, so this
// code goes through callbacks_ in that case too, which will be notified in
// OnCertDbInitializationFinished.
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&CertDbInitializerImpl::OnCertDbInitializationFinished,
weak_factory_.GetWeakPtr()));
}
return callbacks_.Add(std::move(callback));
}
void CertDbInitializerImpl::InitializeReadOnlyCertDb() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto init_database_callback = base::BindPostTaskToCurrentDefault(
base::BindOnce(&CertDbInitializerImpl::OnCertDbInitializationFinished,
weak_factory_.GetWeakPtr()));
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(
&CertDbInitializerIOImpl::InitializeReadOnlyNssCertDatabase,
base::Unretained(cert_db_initializer_io_.get()),
std::move(init_database_callback)));
}
void CertDbInitializerImpl::InitializeForMainProfile() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto software_db_loaded_callback = base::BindPostTaskToCurrentDefault(
base::BindOnce(&CertDbInitializerImpl::DidLoadSoftwareNssDb,
weak_factory_.GetWeakPtr()));
if (chromeos::IsKioskSession()) {
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&CertDbInitializerIOImpl::InitReadOnlyPublicSlot,
base::Unretained(cert_db_initializer_io_.get()),
std::move(software_db_loaded_callback)));
return;
}
const chromeos::BrowserParamsProxy* init_params =
chromeos::BrowserParamsProxy::Get();
base::FilePath nss_db_path;
if (init_params->DefaultPaths()->user_nss_database) {
nss_db_path = init_params->DefaultPaths()->user_nss_database.value();
}
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&CertDbInitializerIOImpl::LoadSoftwareNssDb,
base::Unretained(cert_db_initializer_io_.get()),
std::move(nss_db_path),
std::move(software_db_loaded_callback)));
}
void CertDbInitializerImpl::DidLoadSoftwareNssDb() {
chromeos::LacrosService::Get()
->GetRemote<crosapi::mojom::CertDatabase>()
->GetCertDatabaseInfo(
base::BindOnce(&CertDbInitializerImpl::OnCertDbInfoReceived,
weak_factory_.GetWeakPtr()));
}
void CertDbInitializerImpl::OnCertDbInfoReceived(
crosapi::mojom::GetCertDatabaseInfoResultPtr cert_db_info) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto init_database_callback = base::BindPostTaskToCurrentDefault(
base::BindOnce(&CertDbInitializerImpl::OnCertDbInitializationFinished,
weak_factory_.GetWeakPtr()));
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&CertDbInitializerIOImpl::InitializeNssCertDatabase,
base::Unretained(cert_db_initializer_io_.get()),
std::move(cert_db_info),
std::move(init_database_callback)));
}
void CertDbInitializerImpl::OnCertDbInitializationFinished() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
is_ready_ = true;
callbacks_.Notify();
}
NssCertDatabaseGetter
CertDbInitializerImpl::CreateNssCertDatabaseGetterForIOThread() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
return base::BindOnce(&CertDbInitializerIOImpl::GetNssCertDatabase,
base::Unretained(cert_db_initializer_io_.get()));
}
void CertDbInitializerImpl::OnCertsChangedInAsh(
crosapi::mojom::CertDatabaseChangeType change_type) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
switch (change_type) {
case crosapi::mojom::CertDatabaseChangeType::kUnknown:
net::CertDatabase::GetInstance()->NotifyObserversTrustStoreChanged();
net::CertDatabase::GetInstance()->NotifyObserversClientCertStoreChanged();
break;
case crosapi::mojom::CertDatabaseChangeType::kTrustStore:
net::CertDatabase::GetInstance()->NotifyObserversTrustStoreChanged();
break;
case crosapi::mojom::CertDatabaseChangeType::kClientCertStore:
net::CertDatabase::GetInstance()->NotifyObserversClientCertStoreChanged();
break;
}
}