0

Lacros: Implement chrome://settings/certificates

This CL fixes and re-enables chrome://settings/certificates page
in Lacros. The implementation for the settings page itself is mostly
untouched. The main change is to reuse the code of
*CertificateManagementAllowed policies to disable certificates for
secondary profiles. Client certificates are not supported for secondary
profiles at the moment. CA certificates are shared between all profiles
and therefore should only be managed by the main profile (secondary
ones can still see them).

The second half of the CL implements NSSCertDatabase in Lacros. It
required updating the CertDbInitiazer* classes and the CertDatabase
mojo interface that they use.

Under the existing implementation, Ash directs Lacros about the path
to use for the NSS software database and whether to load Chaps via
the GetCertDatabaseInfoResult.

Although Chaps is loaded by Lacros, it does not have knowledge about
the slot associated with the user (the private slot) or whether or not
to explicitly enable management of the system slot; NSS simply loads
all slots into process memory space. Ash is expected to only tell
Lacros to load chaps if both slots should be loaded.

chrome://settings/certificates is disabled because it was never
properly implemented for Lacros. The code inherited would not create
a correct NSSCertDatabase.

With this new implementation, the user's NSS database is passed through
the BrowserInitParams. It is done to enable loading the software NSS
database earlier. The GetCertDatabaseInfoResult is modified to indicate
which slot(s) the main profile can manage and this is used to create an
NSSCertDatabase for it. Secondary profiles are not allowed to manage
any certificates for now.

Because Ash and Lacros update independently, the code needs to account
for (old-Ash, new-Lacros) and (new-Ash, new-Lacros):
- (old-Ash, new-Lacros) will work mostly the same as
(old-Ash, old-Lacros). The only difference is that the
chrome://settings/certificates page will be present, but modifications
won’t work (see Legacy* methods in this CL).
- (new-Ash, new-Lacros) will properly process the new mojo message,
load slots with specified IDs and create a correct NSSCertDatabase that
will allow modification on the chrome://settings/certificates page.
For now Ash will still only tell to load chaps if both slots should be
used. The use case without the system slot will be covered in a
separate CL.

A DD for the change with some additional details:
go/certificatessettingspagelacros

Bug: b:191336664
Test: Manual
Change-Id: Id5ea17ff20f44277fa7219d66ead61da2b436bbc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3185903
Commit-Queue: Michael Ershov <miersh@google.com>
Reviewed-by: Pavol Marko <pmarko@chromium.org>
Reviewed-by: dpapad <dpapad@chromium.org>
Reviewed-by: Roland Bock <rbock@google.com>
Reviewed-by: Dominic Battré <battre@chromium.org>
Reviewed-by: Ryan Sleevi <rsleevi@chromium.org>
Cr-Commit-Position: refs/heads/main@{#937862}
This commit is contained in:
Michael Ershov
2021-11-03 13:55:27 +00:00
committed by Chromium LUCI CQ
parent 1869b79654
commit d9a627ec82
40 changed files with 1012 additions and 287 deletions

@ -5061,6 +5061,8 @@ static_library("browser") {
"lacros/cert_db_initializer_factory.h",
"lacros/cert_db_initializer_impl.cc",
"lacros/cert_db_initializer_impl.h",
"lacros/cert_db_initializer_io_impl.cc",
"lacros/cert_db_initializer_io_impl.h",
"lacros/chrome_browser_main_extra_parts_lacros.cc",
"lacros/chrome_browser_main_extra_parts_lacros.h",
"lacros/client_cert_store_lacros.cc",
@ -7069,9 +7071,12 @@ static_library("browser") {
"net/nss_service_chromeos_factory.h",
]
}
if (is_linux || is_chromeos_lacros) {
if (is_linux) {
sources += [ "net/nss_context_linux.cc" ]
}
if (is_chromeos_lacros) {
sources += [ "net/nss_context_lacros.cc" ]
}
}
if (trial_comparison_cert_verifier_supported) {

@ -4,35 +4,87 @@
#include "chrome/browser/ash/crosapi/cert_database_ash.h"
#include "base/bind.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chromeos/crosapi/mojom/cert_database.mojom.h"
#include "chromeos/login/login_state/login_state.h"
#include "chrome/browser/net/nss_context.h"
#include "chromeos/tpm/tpm_token_info_getter.h"
#include "components/account_id/account_id.h"
#include "components/user_manager/user.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "crypto/nss_util_internal.h"
#include "net/cert/nss_cert_database.h"
namespace {
using GotDbCallback =
base::OnceCallback<void(unsigned long private_slot_id,
absl::optional<unsigned long> system_slot_id)>;
void GotCertDbOnIOThread(GotDbCallback ui_callback,
net::NSSCertDatabase* cert_db) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
DCHECK(cert_db);
// Technically `PK11_GetSlotID` returns CK_SLOT_ID, but it is guaranteed by
// PKCS #11 standard to be unsigned long and it is more convenient to use here
// because it will be sent through mojo later.
unsigned long private_slot_id =
PK11_GetSlotID(cert_db->GetPrivateSlot().get());
absl::optional<unsigned long> system_slot_id;
crypto::ScopedPK11Slot system_slot = cert_db->GetSystemSlot();
if (system_slot)
system_slot_id = PK11_GetSlotID(system_slot.get());
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(std::move(ui_callback), private_slot_id, system_slot_id));
}
void GetCertDbOnIOThread(GotDbCallback ui_callback,
NssCertDatabaseGetter database_getter) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
auto split_callback = base::SplitOnceCallback(
base::BindOnce(&GotCertDbOnIOThread, std::move(ui_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);
}
} // namespace
namespace crosapi {
CertDatabaseAsh::CertDatabaseAsh() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(chromeos::LoginState::IsInitialized());
chromeos::LoginState::Get()->AddObserver(this);
}
CertDatabaseAsh::~CertDatabaseAsh() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
chromeos::LoginState::Get()->RemoveObserver(this);
}
void CertDatabaseAsh::BindReceiver(
mojo::PendingReceiver<mojom::CertDatabase> pending_receiver) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
receivers_.Add(this, std::move(pending_receiver));
}
void CertDatabaseAsh::GetCertDatabaseInfo(
GetCertDatabaseInfoCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// TODO(crbug.com/1146430): For now Lacros-Chrome will initialize certificate
// database only in session. Revisit later to decide what to do on the login
// screen.
@ -42,18 +94,18 @@ void CertDatabaseAsh::GetCertDatabaseInfo(
return;
}
// If this is the first attempt to load the TPM, begin the async load.
if (!is_tpm_token_ready_.has_value()) {
WaitForTpmTokenReady(std::move(callback));
if (!is_cert_database_ready_.has_value()) {
WaitForCertDatabaseReady(std::move(callback));
return;
}
const user_manager::User* user =
user_manager::UserManager::Get()->GetPrimaryUser();
// If user is not available or the TPM was previously attempted to be loaded,
// and failed, don't retry, just return an empty result that indicates error.
if (!user || !is_tpm_token_ready_.value()) {
// If user is not available or the database was previously attempted to be
// loaded, and failed, don't retry, just return an empty result that indicates
// error.
if (!user || !is_cert_database_ready_.value()) {
std::move(callback).Run(nullptr);
return;
}
@ -67,38 +119,47 @@ void CertDatabaseAsh::GetCertDatabaseInfo(
mojom::GetCertDatabaseInfoResultPtr result =
mojom::GetCertDatabaseInfoResult::New();
result->should_load_chaps = user->IsAffiliated();
result->software_nss_db_path =
result->private_slot_id = private_slot_id_;
result->enable_system_slot = system_slot_id_.has_value();
result->system_slot_id =
result->enable_system_slot ? system_slot_id_.value() : 0;
// TODO(b/200784079): This is backwards compatibility code. It can be
// removed in ChromeOS-M100.
result->DEPRECATED_software_nss_db_path =
crypto::GetSoftwareNSSDBPath(
ProfileManager::GetPrimaryUserProfile()->GetPath())
.value();
std::move(callback).Run(std::move(result));
}
void CertDatabaseAsh::WaitForTpmTokenReady(
void CertDatabaseAsh::WaitForCertDatabaseReady(
GetCertDatabaseInfoCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
Profile* profile = ProfileManager::GetPrimaryUserProfile();
AccountId account_id =
chromeos::ProfileHelper::Get()->GetUserByProfile(profile)->GetAccountId();
DCHECK(profile);
std::unique_ptr<chromeos::TPMTokenInfoGetter> scoped_token_info_getter =
chromeos::TPMTokenInfoGetter::CreateForUserToken(
account_id, chromeos::CryptohomePkcs11Client::Get(),
base::ThreadTaskRunnerHandle::Get());
chromeos::TPMTokenInfoGetter* token_info_getter =
scoped_token_info_getter.get();
auto got_db_callback =
base::BindOnce(&CertDatabaseAsh::OnCertDatabaseReady,
weak_factory_.GetWeakPtr(), std::move(callback));
token_info_getter->Start(base::BindOnce(
&CertDatabaseAsh::OnTpmTokenReady, weak_factory_.GetWeakPtr(),
std::move(scoped_token_info_getter), std::move(callback)));
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&GetCertDbOnIOThread, std::move(got_db_callback),
CreateNSSCertDatabaseGetter(profile)));
}
void CertDatabaseAsh::OnTpmTokenReady(
std::unique_ptr<chromeos::TPMTokenInfoGetter> token_getter,
void CertDatabaseAsh::OnCertDatabaseReady(
GetCertDatabaseInfoCallback callback,
absl::optional<user_data_auth::TpmTokenInfo> token_info) {
is_tpm_token_ready_ = token_info.has_value();
unsigned long private_slot_id,
absl::optional<unsigned long> system_slot_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
is_cert_database_ready_ = true;
private_slot_id_ = private_slot_id;
system_slot_id_ = system_slot_id;
// Calling the initial method again. Since |is_tpm_token_ready_| is not empty
// this time, it will return some result via mojo.
@ -106,10 +167,11 @@ void CertDatabaseAsh::OnTpmTokenReady(
}
void CertDatabaseAsh::LoggedInStateChanged() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Cached result is valid only within one session and should be reset on
// sign out. Currently it is not necessary to reset it on sign in, but doesn't
// hurt.
is_tpm_token_ready_.reset();
is_cert_database_ready_.reset();
}
} // namespace crosapi

@ -13,10 +13,6 @@
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace chromeos {
class TPMTokenInfoGetter;
} // namespace chromeos
namespace crosapi {
// Implements the crosapi interface for certificate database. Lives in
@ -48,15 +44,14 @@ class CertDatabaseAsh : public mojom::CertDatabase,
// chromeos::LoginState::Observer
void LoggedInStateChanged() override;
// The fact that TpmTokenInfo can be retrieved is used as a signal that
// certificate database is ready to be initialized in Lacros-Chrome.
void WaitForTpmTokenReady(GetCertDatabaseInfoCallback callback);
void OnTpmTokenReady(
std::unique_ptr<chromeos::TPMTokenInfoGetter> token_getter,
GetCertDatabaseInfoCallback callback,
absl::optional<user_data_auth::TpmTokenInfo> token_info);
void WaitForCertDatabaseReady(GetCertDatabaseInfoCallback callback);
void OnCertDatabaseReady(GetCertDatabaseInfoCallback callback,
unsigned long private_slot_id,
absl::optional<unsigned long> system_slot_id);
absl::optional<bool> is_tpm_token_ready_;
absl::optional<bool> is_cert_database_ready_;
unsigned long private_slot_id_;
absl::optional<unsigned long> system_slot_id_;
// This class supports any number of connections. This allows the client to
// have multiple, potentially thread-affine, remotes.

@ -18,6 +18,7 @@
#include "components/policy/core/common/cloud/cloud_policy_constants.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "crypto/nss_util_internal.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace crosapi {
@ -80,6 +81,8 @@ mojom::DefaultPathsPtr EnvironmentProvider::GetDefaultPaths() {
// Typically /home/chronos/u-<hash>/MyFiles/Downloads.
default_paths->downloads =
file_manager::util::GetDownloadsFolderForProfile(profile);
default_paths->user_nss_database =
crypto::GetSoftwareNSSDBPath(profile->GetPath());
auto* integration_service =
drive::DriveIntegrationServiceFactory::FindForProfile(profile);
if (integration_service && integration_service->is_enabled() &&

@ -7,20 +7,22 @@
#include "base/callback.h"
#include "base/callback_list.h"
#include "chrome/browser/net/nss_context.h"
class CertDbInitializer {
public:
using ReadyCallback = base::OnceCallback<void(bool is_success)>;
virtual ~CertDbInitializer() = default;
// Registers `callback` to be notified once initialization is complete.
// If initialization has already been completed, `callback` will be
// synchronously invoked and an empty subscription returned; otherwise,
// `callback` will be invoked when initialization is completed, as long
// as the subscription is still live.
// Registers `callback` to be notified once initialization is complete (as
// long as the subscription is still live).
virtual base::CallbackListSubscription WaitUntilReady(
ReadyCallback callback) = 0;
base::OnceClosure callback) = 0;
// Must be called on the UI thread. Returns a Getter that may only be invoked
// on the IO thread. To avoid UAF, the getter must be immediately posted to
// the IO thread and then invoked.
// TODO(crbug.com/1186373): Rework the getter interface.
virtual NssCertDatabaseGetter CreateNssCertDatabaseGetterForIOThread() = 0;
};
#endif // CHROME_BROWSER_LACROS_CERT_DB_INITIALIZER_H_

@ -4,6 +4,7 @@
#include "chrome/browser/lacros/cert_db_initializer_factory.h"
#include "base/system/sys_info.h"
#include "chrome/browser/lacros/cert_db_initializer_impl.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_manager_factory.h"
@ -11,7 +12,6 @@
#include "components/keyed_service/content/browser_context_dependency_manager.h"
class CertDbInitializer;
class Profile;
// static
CertDbInitializerFactory* CertDbInitializerFactory::GetInstance() {
@ -20,10 +20,10 @@ CertDbInitializerFactory* CertDbInitializerFactory::GetInstance() {
}
// static
CertDbInitializer* CertDbInitializerFactory::GetForProfileIfExists(
Profile* profile) {
CertDbInitializer* CertDbInitializerFactory::GetForBrowserContext(
content::BrowserContext* context) {
return static_cast<CertDbInitializerImpl*>(
GetInstance()->GetServiceForBrowserContext(profile, /*create=*/false));
GetInstance()->GetServiceForBrowserContext(context, /*create=*/false));
}
CertDbInitializerFactory::CertDbInitializerFactory()
@ -34,19 +34,20 @@ CertDbInitializerFactory::CertDbInitializerFactory()
}
bool CertDbInitializerFactory::ServiceIsCreatedWithBrowserContext() const {
return true;
// Here `IsRunningOnChromeOS()` is equivalent to "is not running in a test".
// In production the service must be created together with its profile. But
// most tests don't need it. If they do, this still allows to create it
// manually.
// TODO(b/202098971): When certificate verification is blocked on the NSS
// database being loaded in lacros, there will need to be a
// FakeCertDbInitializer in lacros tests by default.
return base::SysInfo::IsRunningOnChromeOS();
}
KeyedService* CertDbInitializerFactory::BuildServiceInstanceFor(
content::BrowserContext* context) const {
Profile* profile = Profile::FromBrowserContext(context);
if (!chromeos::LacrosService::Get() ||
!chromeos::LacrosService::Get()
->IsAvailable<crosapi::mojom::CertDatabase>()) {
return nullptr;
}
CertDbInitializerImpl* result = new CertDbInitializerImpl(profile);
result->Start();
return result;

@ -9,12 +9,12 @@
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
class CertDbInitializer;
class Profile;
class CertDbInitializerFactory : public BrowserContextKeyedServiceFactory {
public:
static CertDbInitializerFactory* GetInstance();
static CertDbInitializer* GetForProfileIfExists(Profile* profile);
static CertDbInitializer* GetForBrowserContext(
content::BrowserContext* context);
private:
friend class base::NoDestructor<CertDbInitializerFactory>;

@ -4,84 +4,130 @@
#include "chrome/browser/lacros/cert_db_initializer_impl.h"
#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/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "cert_db_initializer_io_impl.h"
#include "chrome/browser/profiles/profile.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 "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "crypto/chaps_support.h"
#include "crypto/nss_util.h"
#include "crypto/nss_util_internal.h"
#include "crypto/scoped_nss_types.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace {
bool InitializeCertDbOnWorkerThread(bool should_load_chaps,
base::FilePath software_nss_db_path) {
crypto::EnsureNSSInit();
if (should_load_chaps) {
// NSS functions may reenter //net via extension hooks. If the reentered
// code needs to synchronously wait for a task to run but the thread pool in
// which that task must run doesn't have enough threads to schedule it, a
// deadlock occurs. To prevent that, the base::ScopedBlockingCall below
// increments the thread pool capacity for the duration of the TPM
// initialization.
base::ScopedBlockingCall scoped_blocking_call(
FROM_HERE, base::BlockingType::WILL_BLOCK);
// There's no need to save the result of `LoadChaps()`, chaps will stay
// loaded and can be used anyway. Using the result only to determine
// success/failure.
if (!crypto::LoadChaps()) {
LOG(ERROR) << "Failed to load chaps.";
return false;
}
}
// The slot doesn't need to be saved either. `description` doesn't affect
// anything. `software_nss_db_path` file path should be already created by
// Ash-Chrome.
auto slot = crypto::OpenSoftwareNSSDB(software_nss_db_path,
/*description=*/"cert_db");
if (!slot) {
LOG(ERROR) << "Failed to open user certificate database";
return false;
}
return true;
}
} // namespace
// =============================================================================
// 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_);
DCHECK(chromeos::LacrosService::Get()
->IsAvailable<crosapi::mojom::CertDatabase>());
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(false);
OnCertDbInitializationFinished();
content::GetIOThreadTaskRunner({})->DeleteSoon(
FROM_HERE, std::move(cert_db_initializer_io_));
}
void CertDbInitializerImpl::Start() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!profile_->IsMainProfile()) {
OnCertDbInitializationFinished(false);
return;
if (!profile_->IsMainProfile() || !chromeos::LacrosService::Get() ||
!chromeos::LacrosService::Get()
->IsAvailable<crosapi::mojom::CertDatabase>()) {
// TODO(b/191336028): Implement fully functional and separated certificate
// database for secondary profiles when NSS library is replaced with
// something more flexible.
return InitializeReadOnlyCertDb();
}
// TODO(b/200784079): This is backwards compatibility code. It can be
// removed in ChromeOS-M100.
if (chromeos::LacrosService::Get()->GetInterfaceVersion(
crosapi::mojom::CertDatabase::Uuid_) == 0) {
return LegacyInitializeForMainProfile();
}
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::SequencedTaskRunnerHandle::Get()->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::BindPostTask(
base::SequencedTaskRunnerHandle::Get(),
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() {
auto software_db_loaded_callback = base::BindPostTask(
base::SequencedTaskRunnerHandle::Get(),
base::BindOnce(&CertDbInitializerImpl::DidLoadSoftwareNssDb,
weak_factory_.GetWeakPtr()));
const crosapi::mojom::BrowserInitParams* init_params =
chromeos::LacrosService::Get()->init_params();
base::FilePath nss_db_path;
if (init_params->default_paths->user_nss_database) {
nss_db_path = init_params->default_paths->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(
@ -89,41 +135,65 @@ void CertDbInitializerImpl::Start() {
weak_factory_.GetWeakPtr()));
}
base::CallbackListSubscription CertDbInitializerImpl::WaitUntilReady(
ReadyCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (is_ready_.has_value()) {
std::move(callback).Run(is_ready_.value());
return {};
}
return callbacks_.Add(std::move(callback));
}
void CertDbInitializerImpl::OnCertDbInfoReceived(
crosapi::mojom::GetCertDatabaseInfoResultPtr cert_db_info) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!cert_db_info) {
LOG(WARNING) << "Certificate database is not accesible";
OnCertDbInitializationFinished(false);
return;
}
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&InitializeCertDbOnWorkerThread,
cert_db_info->should_load_chaps,
base::FilePath(cert_db_info->software_nss_db_path)),
auto init_database_callback = base::BindPostTask(
base::SequencedTaskRunnerHandle::Get(),
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(bool is_success) {
void CertDbInitializerImpl::OnCertDbInitializationFinished() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
callbacks_.Notify(is_success);
is_ready_ = is_success;
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()));
}
// ======================= Backwards compatibility code ========================
// TODO(b/200784079): This is backwards compatibility code. It can be
// removed in ChromeOS-M100.
void CertDbInitializerImpl::LegacyInitializeForMainProfile() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
chromeos::LacrosService::Get()
->GetRemote<crosapi::mojom::CertDatabase>()
->GetCertDatabaseInfo(
base::BindOnce(&CertDbInitializerImpl::OnLegacyCertDbInfoReceived,
weak_factory_.GetWeakPtr()));
}
// TODO(b/200784079): This is backwards compatibility code. It can be
// removed in ChromeOS-M100.
void CertDbInitializerImpl::OnLegacyCertDbInfoReceived(
crosapi::mojom::GetCertDatabaseInfoResultPtr cert_db_info) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto init_database_callback = base::BindPostTask(
base::SequencedTaskRunnerHandle::Get(),
base::BindOnce(&CertDbInitializerImpl::OnCertDbInitializationFinished,
weak_factory_.GetWeakPtr()));
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&CertDbInitializerIOImpl::InitializeLegacyNssCertDatabase,
base::Unretained(cert_db_initializer_io_.get()),
std::move(cert_db_info),
std::move(init_database_callback)));
}

@ -8,47 +8,121 @@
#include "base/callback_list.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/lacros/cert_db_initializer.h"
#include "chrome/browser/lacros/cert_db_initializer_io_impl.h"
#include "chromeos/crosapi/mojom/cert_database.mojom.h"
#include "chromeos/lacros/lacros_service.h"
#include "components/keyed_service/core/keyed_service.h"
#include "crypto/scoped_nss_types.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
class Profile;
// Initializes certificate database in Lacros-Chrome. Public methods should be
// called from the UI thread. Relies on CertDatabase mojo interface to be
// available.
// Initializes the certificate database in Lacros-Chrome for the specified
// profile. This object should only be accessed on the UI thread, and requires
// the Mojo CertDatabase interface to be available on the `LacrosService`.
//
// On Lacros-Chrome, how a database is initialized depends on which profile is
// being used as well as the device capabilities.
//
// - For devices with a dedicated TPM, for the main profile the public slot will
// be in the current user's cryptohome (a path is provided by Ash) and the
// private slot will be on the TPM (its slot id is provided by Ash).
//
// - For devices with a dedicated TPM, Ash may also indicate that Lacros should
// use the system slot stored on the TPM. If indicated, the main profile will
// attempt to load the system slot (its slot id is provided by Ash). In case
// of failure, the profile won't have access to certificates from the system
// slot (it will log an error, but won't crash).
//
// - Devices without a dedicated TPM are currently not fully
// supported. For the main profile the public slot be in the current user's
// cryptohome (a path is provided by Ash), the private slot will be set to a
// read-only slot that does not support modification or permanent objects.
// TODO(b/197082753): Make the private slot reference the public slot (same as
// in Ash).
//
// - For all devices, secondary profiles will be initialized with both
// public and private slots set to read-only slots that do not
// support modification or permanent objects.
//
// - If Ash provides a path/TPM slot, and Lacros is unable to load
// it, Lacros will intentionally crash, to avoid ignoring
// settings from Ash.
//
// - If Ash does not provide a path, this is an unexpected/invalid
// state, but Lacros will fail into read-only mode.
class CertDbInitializerImpl : public CertDbInitializer, public KeyedService {
public:
explicit CertDbInitializerImpl(Profile* profile);
~CertDbInitializerImpl() override;
// Starts the initialization.
// Starts the initialization. For the main profile the database will be
// initialized based on the information provided by Ash. Secondary profiles
// will get a read-only database without any user certificates.
void Start();
// CertDbInitializer
base::CallbackListSubscription WaitUntilReady(
ReadyCallback callback) override;
base::OnceClosure callback) override;
NssCertDatabaseGetter CreateNssCertDatabaseGetterForIOThread() override;
private:
// Checks that the current profile is the main profile and, if yes, makes a
// mojo request to Ash-Chrome to get information about certificate database.
void WaitForCertDbReady();
void InitializeForMainProfile();
// Checks from the result that the certificate database should be initialized.
// If yes, loads Chaps and opens user's certificate database.
// Initializes a read-only cert database. It only has access to the built-in
// certs and doesn't allow any modifications.
void InitializeReadOnlyCertDb();
// Initialize with a "legacy" cert database. If Ash-Chrome's CertDatabase
// implementation is before M97, then the chaps slot IDs will not be provided.
// While this is an unsupported configuration, fail gracefully by using
// read-only tokens for the public and private slot.
//
// Pre-M97 Ash-Chrome sends the path to software NSS database and whether
// Lacros-Chrome should load Chaps through `GetCertDatabaseInfo` mojo
// interface. M97+ Lacros-Chrome will process that data exactly like Pre-M97
// Lacros-Chrome if Ash-Chrome is pre-M97.
//
// M97+ Ash-Chrome sends whether Lacros-Chrome should load chaps, slot IDs and
// whether the system slot should be enabled through `GetCertDatabaseInfo`.
// The path to software NSS database is also included in the message, but only
// for backwards compatibility, M97+ Lacros-Chrome doesn't read it.
// M97+ Ash-Chrome sends the path through `BrowserInitParams` mojo interface.
//
// TODO(b/200784079): This is backwards compatibility code. It can be
// removed in ChromeOS-M100.
void LegacyInitializeForMainProfile();
// Queries Ash-Chrome for the user and system slots to use for the profile.
// Should only be called after the software database has been loaded.
void DidLoadSoftwareNssDb();
// Receives information for initializing the main cert database and forwards
// it to `cert_db_initializer_io_`.
void OnCertDbInfoReceived(
crosapi::mojom::GetCertDatabaseInfoResultPtr result);
crosapi::mojom::GetCertDatabaseInfoResultPtr cert_db_info);
// Recieves initialization result from a worker thread and notifies observers
// about the result.
void OnCertDbInitializationFinished(bool is_success);
// A part of the legacy initialization flow. Triggers legacy initialization in
// `cert_db_initializer_io_`.
void OnLegacyCertDbInfoReceived(
crosapi::mojom::GetCertDatabaseInfoResultPtr cert_db_info);
// Receives the signal that initialization is finished and notifies observers
// about that.
void OnCertDbInitializationFinished();
// This class is a `KeyedService` based on the `Profile`. An instance is
// created together with a new profile and never outlives it.`
Profile* profile_ = nullptr;
absl::optional<bool> is_ready_;
base::OnceCallbackList<ReadyCallback::RunType> callbacks_;
bool is_ready_ = false;
base::OnceClosureList callbacks_;
// Created on the UI thread, but after that, initialized, accessed, and
// destroyed exclusively on the IO thread. It is safe to pass unretained
// pointers to this object into IO thread tasks because the earliest it can be
// destroyed is in the following task posted from the destructor.
std::unique_ptr<CertDbInitializerIOImpl> cert_db_initializer_io_;
base::WeakPtrFactory<CertDbInitializerImpl> weak_factory_{this};
};

@ -0,0 +1,254 @@
// Copyright 2021 The Chromium Authors. All rights reserved.
// 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_db_initializer_io_impl.h"
#include "base/debug/dump_without_crashing.h"
#include "base/files/file_util.h"
#include "base/task/bind_post_task.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "cert_db_initializer_io_impl.h"
#include "chromeos/crosapi/mojom/cert_database.mojom.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "crypto/chaps_support.h"
#include "crypto/nss_util.h"
#include "crypto/nss_util_internal.h"
namespace {
// Loads software NSS database a returns a slot referencing it (a.k.a. public
// slot). Should be called on a worked thread because it performs blocking
// operations. Crashes Chrome if it fails to load the database (mostly because
// it'd crash anyway in the CHECK(public_slot) in NSSCertDatabase).
crypto::ScopedPK11Slot LoadSoftwareNssDbOnWorkerThread(
const base::FilePath software_nss_database_path) {
crypto::EnsureNSSInit();
if (software_nss_database_path.empty()) {
CHECK(false);
return {};
}
if (!base::CreateDirectory(software_nss_database_path)) {
CHECK(false) << "Failed to create " << software_nss_database_path.value()
<< " directory.";
return {};
}
// `description` doesn't affect anything.
crypto::ScopedPK11Slot public_slot =
crypto::OpenSoftwareNSSDB(software_nss_database_path,
/*description=*/"cert_db");
CHECK(public_slot);
return public_slot;
}
void LoadSlotsOnWorkerThread(
crosapi::mojom::GetCertDatabaseInfoResultPtr cert_db_info,
base::OnceCallback<void(crypto::ScopedPK11Slot private_slot,
crypto::ScopedPK11Slot system_slot)> callback) {
crypto::EnsureNSSInit();
if (!cert_db_info || !cert_db_info->should_load_chaps) {
// Lacros failed to retrieve initialization data from Ash or Ash explicitly
// replied that chaps shouldn't be loaded.
std::move(callback).Run(
/*private_slot=*/crypto::ScopedPK11Slot(PK11_GetInternalKeySlot()),
/*system_slot=*/crypto::ScopedPK11Slot());
return;
}
SECMODModule* chaps = crypto::LoadChaps();
if (!chaps) {
// Chaps should never fail loading, but try to gracefully recover while
// also logging a crashdump.
base::debug::DumpWithoutCrashing();
std::move(callback).Run(
/*private_slot=*/crypto::ScopedPK11Slot(PK11_GetInternalKeySlot()),
/*system_slot=*/crypto::ScopedPK11Slot());
return;
}
crypto::ScopedPK11Slot private_slot =
crypto::GetChapsSlot(chaps, cert_db_info->private_slot_id);
if (!private_slot) {
base::debug::DumpWithoutCrashing();
std::move(callback).Run(
/*private_slot=*/crypto::ScopedPK11Slot(PK11_GetInternalKeySlot()),
/*system_slot=*/crypto::ScopedPK11Slot());
return;
}
if (!cert_db_info->enable_system_slot) {
std::move(callback).Run(std::move(private_slot),
/*system_slot=*/crypto::ScopedPK11Slot());
return;
}
crypto::ScopedPK11Slot system_slot =
crypto::GetChapsSlot(chaps, cert_db_info->system_slot_id);
if (!system_slot) {
base::debug::DumpWithoutCrashing();
// Proceed with creating the normal database to at least give users
// certificates from the private slot.
}
std::move(callback).Run(std::move(private_slot), std::move(system_slot));
}
// TODO(b/200784079): This is backwards compatibility code. It can be
// removed in ChromeOS-M100.
void LegacyInitializeCertDbOnWorkerThread(
crosapi::mojom::GetCertDatabaseInfoResultPtr cert_db_info) {
crypto::EnsureNSSInit();
if (cert_db_info->should_load_chaps) {
// NSS functions may reenter //net via extension hooks. If the reentered
// code needs to synchronously wait for a task to run but the thread pool in
// which that task must run doesn't have enough threads to schedule it, a
// deadlock occurs. To prevent that, the base::ScopedBlockingCall below
// increments the thread pool capacity for the duration of the TPM
// initialization.
base::ScopedBlockingCall scoped_blocking_call(
FROM_HERE, base::BlockingType::WILL_BLOCK);
// There's no need to save the result of `LoadChaps()`, chaps will stay
// loaded and can be used anyway. Using the result only to determine
// success/failure.
if (!crypto::LoadChaps()) {
return;
}
}
// The slot doesn't need to be saved either. `description` doesn't affect
// anything. `software_nss_db_path` file path should be already created by
// Ash-Chrome.
auto slot = crypto::OpenSoftwareNSSDB(
base::FilePath(cert_db_info->DEPRECATED_software_nss_db_path),
/*description=*/"cert_db");
if (!slot) {
LOG(ERROR) << "Failed to open user certificate database";
}
}
} // namespace
//------------------------------------------------------------------------------
CertDbInitializerIOImpl::CertDbInitializerIOImpl() = default;
CertDbInitializerIOImpl::~CertDbInitializerIOImpl() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
}
net::NSSCertDatabase* CertDbInitializerIOImpl::GetNssCertDatabase(
GetNSSCertDatabaseCallback callback) {
if (nss_cert_database_) {
return nss_cert_database_.get();
}
ready_callback_list_.AddUnsafe(std::move(callback));
return nullptr;
}
void CertDbInitializerIOImpl::LoadSoftwareNssDb(
const base::FilePath& user_nss_database_path,
base::OnceClosure load_callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
DCHECK(!pending_public_slot_);
DCHECK(!nss_cert_database_);
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&LoadSoftwareNssDbOnWorkerThread, user_nss_database_path),
base::BindOnce(&CertDbInitializerIOImpl::DidLoadSoftwareNssDb,
weak_factory_.GetWeakPtr(), std::move(load_callback)));
}
void CertDbInitializerIOImpl::DidLoadSoftwareNssDb(
base::OnceClosure load_callback,
crypto::ScopedPK11Slot public_slot) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
DCHECK(!pending_public_slot_);
DCHECK(!nss_cert_database_);
pending_public_slot_ = std::move(public_slot);
std::move(load_callback).Run();
}
void CertDbInitializerIOImpl::InitializeNssCertDatabase(
crosapi::mojom::GetCertDatabaseInfoResultPtr cert_db_info,
base::OnceClosure init_callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
DCHECK(pending_public_slot_) << "LoadSoftwareNssDb() must be called first.";
DCHECK(!nss_cert_database_);
auto load_slots_callback = base::BindPostTask(
base::SequencedTaskRunnerHandle::Get(),
base::BindOnce(&CertDbInitializerIOImpl::DidLoadSlots,
weak_factory_.GetWeakPtr(), std::move(init_callback)));
base::ThreadPool::PostTask(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&LoadSlotsOnWorkerThread, std::move(cert_db_info),
std::move(load_slots_callback)));
}
void CertDbInitializerIOImpl::DidLoadSlots(base::OnceClosure init_callback,
crypto::ScopedPK11Slot private_slot,
crypto::ScopedPK11Slot system_slot) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
DCHECK(!nss_cert_database_);
nss_cert_database_ = std::make_unique<net::NSSCertDatabaseChromeOS>(
std::move(pending_public_slot_), std::move(private_slot));
nss_cert_database_->SetSystemSlot(std::move(system_slot));
std::move(init_callback).Run();
ready_callback_list_.Notify(nss_cert_database_.get());
}
void CertDbInitializerIOImpl::InitializeReadOnlyNssCertDatabase(
base::OnceClosure init_callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
DCHECK(!pending_public_slot_);
DCHECK(!nss_cert_database_);
nss_cert_database_ = std::make_unique<net::NSSCertDatabaseChromeOS>(
/*public_slot=*/crypto::ScopedPK11Slot(PK11_GetInternalKeySlot()),
/*private_slot=*/crypto::ScopedPK11Slot(PK11_GetInternalKeySlot()));
std::move(init_callback).Run();
ready_callback_list_.Notify(nss_cert_database_.get());
}
// TODO(b/200784079): This is backwards compatibility code. It can be
// removed in ChromeOS-M100.
void CertDbInitializerIOImpl::InitializeLegacyNssCertDatabase(
crosapi::mojom::GetCertDatabaseInfoResultPtr cert_db_info,
base::OnceClosure init_callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
DCHECK(!pending_public_slot_);
DCHECK(!nss_cert_database_);
base::ThreadPool::PostTaskAndReply(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&LegacyInitializeCertDbOnWorkerThread,
std::move(cert_db_info)),
base::BindOnce(&CertDbInitializerIOImpl::DidLegacyInitialize,
weak_factory_.GetWeakPtr(), std::move(init_callback)));
}
// TODO(b/200784079): This is backwards compatibility code. It can be
// removed in ChromeOS-M100.
void CertDbInitializerIOImpl::DidLegacyInitialize(
base::OnceClosure init_callback) {
InitializeReadOnlyNssCertDatabase(std::move(init_callback));
}

@ -0,0 +1,105 @@
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_LACROS_CERT_DB_INITIALIZER_IO_IMPL_H_
#define CHROME_BROWSER_LACROS_CERT_DB_INITIALIZER_IO_IMPL_H_
#include <memory>
#include "base/callback_forward.h"
#include "base/callback_list.h"
#include "base/files/file_path.h"
#include "base/memory/weak_ptr.h"
#include "base/task/sequenced_task_runner.h"
#include "chromeos/crosapi/mojom/cert_database.mojom.h"
#include "crypto/scoped_nss_types.h"
#include "net/cert/nss_cert_database_chromeos.h"
// This class is a part of CertDbInitializerImpl that lives on the IO thread.
// All the methods (except for the constructor) must be called on the IO thread.
// The main purpose of the class is to create NSSCertDatabase and provide
// access to it.
class CertDbInitializerIOImpl {
public:
using GetNSSCertDatabaseCallback =
base::OnceCallback<void(net::NSSCertDatabase*)>;
CertDbInitializerIOImpl();
~CertDbInitializerIOImpl();
// If `nss_cert_database_` is already created, returns a pointer to it and
// never calls the `callback`. Otherwise, returns nullptr and will call the
// `callback` when it is created.
net::NSSCertDatabase* GetNssCertDatabase(GetNSSCertDatabaseCallback callback);
// Loads the software NSS certificate database (a.k.a. public slot). This step
// should be executed as soon as possbile because the database may contain
// user defined CA trust settings that are required for loading web pages.
// `load_callback` will be called when the load is done.
void LoadSoftwareNssDb(const base::FilePath& user_nss_database_path,
base::OnceClosure load_callback);
// Creates an NSSCertDatabase based upon the initialization data from
// `cert_db_info`. This database will then be used for all waiting and
// future calls to `GetNssCertDatabase`. If `cert_db_info` is nullptr, a
// read-only database will be created, without any user certificates.
// `init_callback` will be called when the initialization is done.
//
// Summary of what slots are used and when:
// Public slot:
// * It is expected to reference the software NSS database in the user
// directory.
// * If the path to the directory was not provided by Ash, it will reference
// the internal slot.
// * In case of errors it will be empty which will cause a crash if Chrome
// tries to read it.
//
// Private slot:
// * It is expected to reference a per-user cert storage in Chaps (i.e. in
// TPM).
// * If Chaps should not be loaded for the current session (e.g. for guest
// sessions) or if it failed to load, the private slot will reference the
// internal slot.
//
// System slot:
// * If it should be used it is expected to reference the device-wide cert
// storage in Chaps. The decision is made in Ash-Chrome.
// * Otherwise or if fails to load, system slot will be empty. This will limit
// access to some certs, but otherwise won't break Chrome.
void InitializeNssCertDatabase(
crosapi::mojom::GetCertDatabaseInfoResultPtr cert_db_info,
base::OnceClosure init_callback);
// Initializes a read-only database without any user certificates.
// `init_callback` will be called when the initialization is done.
void InitializeReadOnlyNssCertDatabase(base::OnceClosure init_callback);
// Initializes a read-only database with the access to user and system
// certificates. `init_callback` will be called when
// the initialization is done.
// TODO(b/200784079): This is backwards compatibility code. It can be
// removed in ChromeOS-M100.
void InitializeLegacyNssCertDatabase(
crosapi::mojom::GetCertDatabaseInfoResultPtr cert_db_info,
base::OnceClosure init_callback);
private:
void DidLoadSoftwareNssDb(base::OnceClosure load_callback,
crypto::ScopedPK11Slot public_slot);
void DidLoadSlots(base::OnceClosure init_callback,
crypto::ScopedPK11Slot private_slot,
crypto::ScopedPK11Slot system_slot);
void DidLegacyInitialize(base::OnceClosure init_callback);
crypto::ScopedPK11Slot pending_public_slot_;
std::unique_ptr<net::NSSCertDatabaseChromeOS> nss_cert_database_;
base::OnceCallbackList<GetNSSCertDatabaseCallback::RunType>
ready_callback_list_;
base::WeakPtrFactory<CertDbInitializerIOImpl> weak_factory_{this};
};
#endif // CHROME_BROWSER_LACROS_CERT_DB_INITIALIZER_IO_IMPL_H_

@ -39,10 +39,7 @@ void ClientCertStoreLacros::WaitForCertDb() {
&ClientCertStoreLacros::OnCertDbReady, weak_factory_.GetWeakPtr()));
}
void ClientCertStoreLacros::OnCertDbReady(bool /*is_cert_db_ready*/) {
// Ignore the initialization result. Even if it failed, some certificates
// could be accessible.
void ClientCertStoreLacros::OnCertDbReady() {
// Ensure any new requests (e.g. that result from invoking the
// callbacks) aren't queued.
are_certs_loaded_ = true;

@ -37,7 +37,7 @@ class ClientCertStoreLacros final : public net::ClientCertStore {
ClientCertListCallback>>;
void WaitForCertDb();
void OnCertDbReady(bool is_cert_db_ready);
void OnCertDbReady();
bool are_certs_loaded_ = false;
CertDbInitializer* cert_db_initializer_ = nullptr;

@ -32,7 +32,10 @@ class MockCertDbInitializer : public CertDbInitializer {
public:
MOCK_METHOD(base::CallbackListSubscription,
WaitUntilReady,
(ReadyCallback callback));
(base::OnceClosure callback));
MOCK_METHOD(NssCertDatabaseGetter,
CreateNssCertDatabaseGetterForIOThread,
());
};
class ClientCertStoreLacrosTest : public ::testing::Test {
@ -67,12 +70,11 @@ class ClientCertStoreLacrosTest : public ::testing::Test {
// Captures callback from `CertDbInitializer::WaitUntilReady(...)` and allows
// to imitate the "ready" notification by calling the `callback`.
struct DbInitCallbackHolder {
base::CallbackListSubscription SaveCallback(
CertDbInitializer::ReadyCallback cb) {
base::CallbackListSubscription SaveCallback(base::OnceClosure cb) {
callback = std::move(cb);
return {};
}
CertDbInitializer::ReadyCallback callback;
base::OnceClosure callback;
};
// Provides callback for `ClientCertStore::GetClientCerts(...)` and allows to
@ -124,7 +126,7 @@ TEST_F(ClientCertStoreLacrosTest, WaitsForInitialization) {
// Imitate signal from cert_db_initializer_ that the initialization is done.
// Even if it failed, the cert store should try to continue.
std::move(db_init_callback_holder.callback).Run(/*is_success=*/false);
std::move(db_init_callback_holder.callback).Run();
get_certs_callback_observer.WaitUntilGotCerts();
}
@ -143,7 +145,7 @@ TEST_F(ClientCertStoreLacrosTest, RunsImmediatelyIfReady) {
// Imitate signal from cert_db_initializer_ that the initialization is
// done before calling `GetClientCerts`.
std::move(db_init_callback_holder.callback).Run(/*is_success=*/true);
std::move(db_init_callback_holder.callback).Run();
EXPECT_CALL(*underlying_store,
GetClientCerts(AddressEq(cert_request_.get()), /*callback=*/_))
@ -198,7 +200,7 @@ TEST_F(ClientCertStoreLacrosTest, QueueMultupleRequests) {
.WillOnce(Invoke(this, &ClientCertStoreLacrosTest::FakeGetClientCerts));
// Imitate signal from cert_db_initializer_ that the initialization is done.
std::move(db_init_callback_holder.callback).Run(/*is_success=*/true);
std::move(db_init_callback_holder.callback).Run();
get_certs_callback_observer_1.WaitUntilGotCerts();
get_certs_callback_observer_2.WaitUntilGotCerts();
@ -248,7 +250,7 @@ TEST_F(ClientCertStoreLacrosTest, DeletedFromLastCallback) {
// Imitate signal from cert_db_initializer_ that the initialization is
// done.
std::move(db_init_callback_holder.callback).Run(/*is_success=*/true);
std::move(db_init_callback_holder.callback).Run();
get_certs_callback_observer_1.WaitUntilGotCerts();
get_certs_callback_observer_2.WaitUntilGotCerts();
@ -293,7 +295,7 @@ TEST_F(ClientCertStoreLacrosTest, HandlesReentrancy) {
Invoke(this, &ClientCertStoreLacrosTest::FakeGetClientCerts));
// Imitate signal from cert_db_initializer_ that the initialization is done.
std::move(db_init_callback_holder.callback).Run(/*is_success=*/true);
std::move(db_init_callback_holder.callback).Run();
// Verify that both callbacks are called.
get_certs_callback_observer_1.WaitUntilGotCerts();

@ -0,0 +1,18 @@
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/net/nss_context.h"
#include "base/bind.h"
#include "chrome/browser/lacros/cert_db_initializer.h"
#include "chrome/browser/lacros/cert_db_initializer_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/browser_thread.h"
NssCertDatabaseGetter CreateNSSCertDatabaseGetter(
content::BrowserContext* browser_context) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
return CertDbInitializerFactory::GetForBrowserContext(browser_context)
->CreateNssCertDatabaseGetterForIOThread();
}

@ -18,12 +18,6 @@ net::NSSCertDatabase* GetNSSCertDatabaseForResourceContext(
// is only run on a single thread.
CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
#if BUILDFLAG(IS_CHROMEOS_LACROS)
// TODO(crbug.com/1147032): remove the CHECK after the certificates settings
// page is updated.
CHECK(false) << "Currently disabled for Lacros-Chrome.";
#endif
if (!g_nss_cert_database) {
// Linux has only a single persistent slot compared to ChromeOS's separate
// public and private slot.

@ -26,6 +26,7 @@ class NssServiceChromeOS : public KeyedService {
// then invoked. While the returned getter must be invoked on the IO thread,
// this method itself may only be invoked on the UI thread, where the
// NssServiceChromeOS lives.
// TODO(crbug.com/1186373): Rework the getter interface.
NssCertDatabaseGetter CreateNSSCertDatabaseGetterForIOThread();
private:

@ -590,14 +590,14 @@ ProfileNetworkContextService::CreateClientCertStore() {
base::BindRepeating(&CreateCryptoModuleBlockingPasswordDelegate,
kCryptoModulePasswordClientAuth));
#if BUILDFLAG(IS_CHROMEOS_LACROS)
CertDbInitializer* cert_db_initializer =
CertDbInitializerFactory::GetForProfileIfExists(profile_);
if (!cert_db_initializer || !profile_->IsMainProfile()) {
if (!profile_->IsMainProfile()) {
// TODO(crbug.com/1148298): return some cert store for secondary profiles in
// Lacros-Chrome.
return nullptr;
}
CertDbInitializer* cert_db_initializer =
CertDbInitializerFactory::GetForBrowserContext(profile_);
store = std::make_unique<ClientCertStoreLacros>(cert_db_initializer,
std::move(store));
#endif // BUILDFLAG(IS_CHROMEOS_LACROS)

@ -778,12 +778,6 @@ const PolicyToPreferenceMapEntry kSimplePolicyMap[] = {
#endif // !defined(OS_CHROMEOS)
#if BUILDFLAG(IS_CHROMEOS_ASH)
{ key::kClientCertificateManagementAllowed,
prefs::kClientCertificateManagementAllowed,
base::Value::Type::INTEGER },
{ key::kCACertificateManagementAllowed,
prefs::kCACertificateManagementAllowed,
base::Value::Type::INTEGER },
{ key::kChromeOsLockOnIdleSuspend,
ash::prefs::kEnableAutoScreenLock,
base::Value::Type::BOOLEAN },
@ -1404,6 +1398,12 @@ const PolicyToPreferenceMapEntry kSimplePolicyMap[] = {
{ key::kLacrosSecondaryProfilesAllowed,
prefs::kLacrosSecondaryProfilesAllowed,
base::Value::Type::BOOLEAN },
{ key::kClientCertificateManagementAllowed,
prefs::kClientCertificateManagementAllowed,
base::Value::Type::INTEGER },
{ key::kCACertificateManagementAllowed,
prefs::kCACertificateManagementAllowed,
base::Value::Type::INTEGER },
#endif // defined(OS_CHROMEOS)
#if !defined(OS_MAC) && !defined(OS_CHROMEOS)
@ -1817,7 +1817,7 @@ std::unique_ptr<ConfigurationPolicyHandlerList> BuildHandlerList(
key::kPrintingAPIExtensionsAllowlist,
prefs::kPrintingAPIExtensionsAllowlist, /*allow_wildcards=*/false));
#endif // defined(USE_CUPS)
#else // defined(OS_CHROMEOS)
#else // defined(OS_CHROMEOS)
std::vector<std::unique_ptr<ConfigurationPolicyHandler>>
signin_legacy_policies;
signin_legacy_policies.push_back(std::make_unique<SimplePolicyHandler>(

@ -261,6 +261,7 @@
#if defined(OS_CHROMEOS)
#include "chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.h"
#include "chrome/browser/ui/webui/certificates_handler.h"
#if defined(USE_CUPS)
#include "chrome/browser/extensions/api/printing/printing_api_handler.h"
#endif // defined(USE_CUPS)
@ -364,7 +365,6 @@
#include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h"
#include "chrome/browser/ui/app_list/search/arc/arc_app_reinstall_search_provider.h"
#include "chrome/browser/ui/ash/shelf/chrome_shelf_prefs.h"
#include "chrome/browser/ui/webui/certificates_handler.h"
#include "chrome/browser/ui/webui/chromeos/login/enable_debugging_screen_handler.h"
#include "chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h"
#include "chrome/browser/ui/webui/settings/chromeos/os_settings_ui.h"
@ -982,7 +982,7 @@ void RegisterLocalState(PrefRegistrySimple* registry) {
registry->RegisterIntegerPref(first_run::kTosDialogBehavior, 0);
registry->RegisterBooleanPref(lens::kLensCameraAssistedSearchEnabled, true);
#else // defined(OS_ANDROID)
#else // defined(OS_ANDROID)
gcm::RegisterPrefs(registry);
IntranetRedirectDetector::RegisterPrefs(registry);
media_router::RegisterLocalStatePrefs(registry);
@ -1273,7 +1273,7 @@ void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry,
video_tutorials::RegisterPrefs(registry);
feed::prefs::RegisterFeedSharedProfilePrefs(registry);
feed::RegisterProfilePrefs(registry);
#else // defined(OS_ANDROID)
#else // defined(OS_ANDROID)
AppShortcutManager::RegisterProfilePrefs(registry);
browser_sync::ForeignSessionHandler::RegisterProfilePrefs(registry);
captions::LiveCaptionController::RegisterProfilePrefs(registry);
@ -1312,6 +1312,7 @@ void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry,
#if defined(OS_CHROMEOS)
extensions::platform_keys::RegisterProfilePrefs(registry);
certificate_manager::CertificatesHandler::RegisterProfilePrefs(registry);
#if defined(USE_CUPS)
extensions::PrintingAPIHandler::RegisterProfilePrefs(registry);
#endif // defined(USE_CUPS)
@ -1324,7 +1325,6 @@ void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry,
apps::webapk_prefs::RegisterProfilePrefs(registry);
arc::prefs::RegisterProfilePrefs(registry);
ArcAppListPrefs::RegisterProfilePrefs(registry);
certificate_manager::CertificatesHandler::RegisterProfilePrefs(registry);
account_manager::AccountManager::RegisterPrefs(registry);
ash::ApkWebAppService::RegisterProfilePrefs(registry);
ash::app_time::AppActivityRegistry::RegisterProfilePrefs(registry);

@ -23,9 +23,7 @@ function addPrivacyChildRoutes(r: SettingsRoutes) {
r.COOKIES = r.PRIVACY.createChild('/cookies');
r.SECURITY = r.PRIVACY.createChild('/security');
// TODO(crbug.com/1147032): The certificates settings page is temporarily
// disabled for Lacros-Chrome until a better solution is found.
// <if expr="use_nss_certs and not lacros">
// <if expr="use_nss_certs">
r.CERTIFICATES = r.SECURITY.createChild('/certificates');
// </if>

@ -357,7 +357,9 @@ void CertificatesHandler::RegisterMessages() {
}
void CertificatesHandler::CertificatesRefreshed() {
PopulateTree("personalCerts", net::USER_CERT);
if (ShouldDisplayClientCertificates()) {
PopulateTree("personalCerts", net::USER_CERT);
}
PopulateTree("serverCerts", net::SERVER_CERT);
PopulateTree("caCerts", net::CA_CERT);
PopulateTree("otherCerts", net::OTHER_CERT);
@ -575,14 +577,12 @@ void CertificatesHandler::ExportPersonalFileWritten(const int* write_errno,
}
void CertificatesHandler::HandleImportPersonal(const base::ListValue* args) {
#if BUILDFLAG(IS_CHROMEOS_ASH)
// When policy changes while user on the certificate manager page, the UI
// doesn't update without page refresh and user can still see and use import
// button. Because of this 'return' the button will do nothing.
if (!IsClientCertificateManagementAllowedPolicy(Slot::kUser)) {
// When the "allowed" value changes while user on the certificate manager
// page, the UI doesn't update without page refresh and user can still see and
// use import button. Because of this 'return' the button will do nothing.
if (!IsClientCertificateManagementAllowed(Slot::kUser)) {
return;
}
#endif
CHECK_EQ(2U, args->GetList().size());
AssignWebUICallbackId(args);
@ -812,14 +812,12 @@ void CertificatesHandler::ImportServerFileRead(const int* read_errno,
}
void CertificatesHandler::HandleImportCA(const base::ListValue* args) {
#if BUILDFLAG(IS_CHROMEOS_ASH)
// When policy changes while user on the certificate manager page, the UI
// doesn't update without page refresh and user can still see and use import
// button. Because of this 'return' the button will do nothing.
if (!IsCACertificateManagementAllowedPolicy(CertificateSource::kImported)) {
// When the "allowed" value changes while user on the certificate manager
// page, the UI doesn't update without page refresh and user can still see and
// use import button. Because of this 'return' the button will do nothing.
if (!IsCACertificateManagementAllowed(CertificateSource::kImported)) {
return;
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
CHECK_EQ(1U, args->GetList().size());
AssignWebUICallbackId(args);
@ -965,19 +963,13 @@ void CertificatesHandler::OnCertificateManagerModelCreated(
}
void CertificatesHandler::CertificateManagerModelReady() {
bool client_import_allowed = true;
bool ca_import_allowed = true;
#if BUILDFLAG(IS_CHROMEOS_ASH)
client_import_allowed =
IsClientCertificateManagementAllowedPolicy(Slot::kUser);
ca_import_allowed =
IsCACertificateManagementAllowedPolicy(CertificateSource::kImported);
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
if (IsJavascriptAllowed()) {
FireWebUIListener("client-import-allowed-changed",
base::Value(client_import_allowed));
FireWebUIListener(
"client-import-allowed-changed",
base::Value(IsClientCertificateManagementAllowed(Slot::kUser)));
FireWebUIListener("ca-import-allowed-changed",
base::Value(ca_import_allowed));
base::Value(IsCACertificateManagementAllowed(
CertificateSource::kImported)));
}
certificate_manager_model_->Refresh();
}
@ -1159,10 +1151,66 @@ CertificatesHandler::GetCertInfoFromCallbackArgs(const base::Value& args,
return cert_info_id_map_.Lookup(cert_info_id);
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
bool CertificatesHandler::IsClientCertificateManagementAllowed(Slot slot) {
#if defined(OS_CHROMEOS)
if (!IsClientCertificateManagementAllowedPolicy(slot)) {
return false;
}
#endif // defined(OS_CHROMEOS)
return ShouldDisplayClientCertificates();
}
bool CertificatesHandler::IsCACertificateManagementAllowed(
CertificateSource source) {
#if BUILDFLAG(IS_CHROMEOS_LACROS)
// TODO(b/194781831): Currently CA certificates are shared between all
// profiles for technical reasons. Evaluating the policy independently in each
// profile would create a policy escape (e.g. if one of profiles is not
// managed). Therefore make the main profile "own" CA certificates and allow
// management based on its policy.
if (!Profile::FromWebUI(web_ui())->IsMainProfile()) {
return false;
}
#endif // #if BUILDFLAG(IS_CHROMEOS_LACROS)
#if defined(OS_CHROMEOS)
if (!IsCACertificateManagementAllowedPolicy(source)) {
return false;
}
#endif // defined(OS_CHROMEOS)
return true;
}
bool CertificatesHandler::ShouldDisplayClientCertificates() {
#if BUILDFLAG(IS_CHROMEOS_LACROS)
// TODO(b/194781831): When secondary profiles in Lacros-Chrome support client
// certificates, this should be removed and the page should be updated to
// support them.
if (!Profile::FromWebUI(web_ui())->IsMainProfile()) {
return false;
}
#endif // #if BUILDFLAG(IS_CHROMEOS_LACROS)
return true;
}
#if defined(OS_CHROMEOS)
bool CertificatesHandler::IsClientCertificateManagementAllowedPolicy(
Slot slot) {
Profile* profile = Profile::FromWebUI(web_ui());
#if BUILDFLAG(IS_CHROMEOS_LACROS)
if (!profile->IsMainProfile()) {
// TODO(b/194781831): Currently client certificates are not supported in
// secondary profiles on Lacros-Chrome. This "return" disables some buttons
// (e.g. Import, Import&Bind) that wouldn't work anyway. This can be changed
// when client certificates for secondary profiles are implemented.
return false;
}
#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
PrefService* prefs = profile->GetPrefs();
auto policy_value = static_cast<ClientCertificateManagementPermission>(
prefs->GetInteger(prefs::kClientCertificateManagementAllowed));
@ -1176,6 +1224,17 @@ bool CertificatesHandler::IsClientCertificateManagementAllowedPolicy(
bool CertificatesHandler::IsCACertificateManagementAllowedPolicy(
CertificateSource source) {
Profile* profile = Profile::FromWebUI(web_ui());
#if BUILDFLAG(IS_CHROMEOS_LACROS)
if (!profile->IsMainProfile()) {
// TODO(b/194781831): Currently CA certificates are shared between all
// profiles for technical reasons. Therefore only the main profile should
// decide if they are allowed to be managed. This can be changed when a
// proper separation of CA certificates between profiles is implemented.
return false;
}
#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
PrefService* prefs = profile->GetPrefs();
auto policy_value = static_cast<CACertificateManagementPermission>(
prefs->GetInteger(prefs::kCACertificateManagementAllowed));
@ -1187,7 +1246,7 @@ bool CertificatesHandler::IsCACertificateManagementAllowedPolicy(
return policy_value != CACertificateManagementPermission::kNone;
}
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
#endif // defined(OS_CHROMEOS)
bool CertificatesHandler::CanDeleteCertificate(
const CertificateManagerModel::CertInfo* cert_info) {
@ -1197,18 +1256,16 @@ bool CertificatesHandler::CanDeleteCertificate(
return false;
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
if (cert_info->type() == net::CertType::USER_CERT) {
return IsClientCertificateManagementAllowedPolicy(
return IsClientCertificateManagementAllowed(
cert_info->device_wide() ? Slot::kSystem : Slot::kUser);
}
if (cert_info->type() == net::CertType::CA_CERT) {
CertificateSource source = cert_info->can_be_deleted()
? CertificateSource::kImported
: CertificateSource::kBuiltIn;
return IsCACertificateManagementAllowedPolicy(source);
return IsCACertificateManagementAllowed(source);
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
return true;
}
@ -1219,17 +1276,14 @@ bool CertificatesHandler::CanEditCertificate(
CertificateManagerModel::CertInfo::Source::kPolicy)) {
return false;
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
CertificateSource source = cert_info->can_be_deleted()
? CertificateSource::kImported
: CertificateSource::kBuiltIn;
return IsCACertificateManagementAllowedPolicy(source);
#else
return true;
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
return IsCACertificateManagementAllowed(source);
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
#if defined(OS_CHROMEOS)
void CertificatesHandler::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
// Allow users to manage all client certificates by default. This can be
@ -1244,6 +1298,6 @@ void CertificatesHandler::RegisterProfilePrefs(
prefs::kCACertificateManagementAllowed,
static_cast<int>(CACertificateManagementPermission::kAll));
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
#endif // defined(OS_CHROMEOS)
} // namespace certificate_manager

@ -24,7 +24,6 @@ namespace user_prefs {
class PrefRegistrySyncable;
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
enum class Slot { kUser, kSystem };
enum class CertificateSource { kBuiltIn, kImported };
@ -51,7 +50,6 @@ enum class CACertificateManagementPermission : int {
// Disallow users from managing certificates
kNone = 2
};
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
namespace certificate_manager {
@ -80,10 +78,10 @@ class CertificatesHandler : public content::WebUIMessageHandler,
void* params) override;
void FileSelectionCanceled(void* params) override;
#if BUILDFLAG(IS_CHROMEOS_ASH)
#if defined(OS_CHROMEOS)
// Register profile preferences.
static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
#endif
#endif // defined(OS_CHROMEOS)
private:
// View certificate.
@ -206,7 +204,17 @@ class CertificatesHandler : public content::WebUIMessageHandler,
const base::Value& args,
size_t arg_index);
#if BUILDFLAG(IS_CHROMEOS_ASH)
// Returns true if it is allowed to display the list of client certificates
// for the current profile.
bool ShouldDisplayClientCertificates();
// Returns true if the user may manage client certificates on |slot|.
bool IsClientCertificateManagementAllowed(Slot slot);
// Returns true if the user may manage CA certificates.
bool IsCACertificateManagementAllowed(CertificateSource source);
#if defined(OS_CHROMEOS)
// Returns true if the user may manage certificates on |slot| according
// to ClientCertificateManagementAllowed policy.
bool IsClientCertificateManagementAllowedPolicy(Slot slot);
@ -214,7 +222,7 @@ class CertificatesHandler : public content::WebUIMessageHandler,
// Returns true if the user may manage certificates according
// to CACertificateManagementAllowed policy.
bool IsCACertificateManagementAllowedPolicy(CertificateSource source);
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
#endif // defined(OS_CHROMEOS)
// Returns true if the certificate represented by |cert_info| can be deleted.
bool CanDeleteCertificate(const CertificateManagerModel::CertInfo* cert_info);

@ -20,13 +20,17 @@ class CertificateHandlerTest : public ChromeRenderViewHostTestHarness {
web_ui_.set_web_contents(web_contents());
cert_handler_.set_web_ui(&web_ui_);
pref_service_ = profile()->GetTestingPrefService();
#if BUILDFLAG(IS_CHROMEOS_LACROS)
profile()->SetIsMainProfile(true);
#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
#if defined(OS_CHROMEOS)
bool IsCACertificateManagementAllowedPolicy(CertificateSource source) {
return cert_handler_.IsCACertificateManagementAllowedPolicy(source);
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
#endif // defined(OS_CHROMEOS)
bool CanDeleteCertificate(
const CertificateManagerModel::CertInfo* cert_info) {
@ -43,7 +47,7 @@ class CertificateHandlerTest : public ChromeRenderViewHostTestHarness {
sync_preferences::TestingPrefServiceSyncable* pref_service_ = nullptr;
};
#if BUILDFLAG(IS_CHROMEOS_ASH)
#if defined(OS_CHROMEOS)
TEST_F(CertificateHandlerTest, IsCACertificateManagementAllowedPolicyTest) {
{
pref_service_->SetInteger(
@ -78,7 +82,7 @@ TEST_F(CertificateHandlerTest, IsCACertificateManagementAllowedPolicyTest) {
IsCACertificateManagementAllowedPolicy(CertificateSource::kBuiltIn));
}
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
#endif // defined(OS_CHROMEOS)
TEST_F(CertificateHandlerTest, CanDeleteCertificateCommonTest) {
CertificateManagerModel::CertInfo default_cert_info(
@ -126,7 +130,7 @@ TEST_F(CertificateHandlerTest, CanDeleteUserCertificateTest) {
EXPECT_TRUE(CanDeleteCertificate(&cert_info));
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
#if defined(OS_CHROMEOS)
{
pref_service_->SetInteger(
prefs::kClientCertificateManagementAllowed,
@ -162,7 +166,7 @@ TEST_F(CertificateHandlerTest, CanDeleteUserCertificateTest) {
cert_info.device_wide_ = true;
EXPECT_FALSE(CanDeleteCertificate(&cert_info));
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
#endif // defined(OS_CHROMEOS)
}
TEST_F(CertificateHandlerTest, CanDeleteCACertificateTest) {
@ -180,7 +184,7 @@ TEST_F(CertificateHandlerTest, CanDeleteCACertificateTest) {
EXPECT_TRUE(CanDeleteCertificate(&cert_info));
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
#if defined(OS_CHROMEOS)
{
pref_service_->SetInteger(
prefs::kCACertificateManagementAllowed,
@ -215,7 +219,7 @@ TEST_F(CertificateHandlerTest, CanDeleteCACertificateTest) {
cert_info.can_be_deleted_ = true;
EXPECT_FALSE(CanDeleteCertificate(&cert_info));
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
#endif // defined(OS_CHROMEOS)
}
TEST_F(CertificateHandlerTest, CanEditCertificateCommonTest) {
@ -259,7 +263,7 @@ TEST_F(CertificateHandlerTest, CanEditUserCertificateTest) {
EXPECT_FALSE(CanEditCertificate(&cert_info));
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
#if defined(OS_CHROMEOS)
{
pref_service_->SetInteger(
prefs::kClientCertificateManagementAllowed,
@ -295,7 +299,7 @@ TEST_F(CertificateHandlerTest, CanEditUserCertificateTest) {
cert_info.device_wide_ = true;
EXPECT_FALSE(CanEditCertificate(&cert_info));
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
#endif // defined(OS_CHROMEOS)
}
TEST_F(CertificateHandlerTest, CanEditCACertificateTest) {
@ -313,7 +317,7 @@ TEST_F(CertificateHandlerTest, CanEditCACertificateTest) {
EXPECT_TRUE(CanEditCertificate(&cert_info));
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
#if defined(OS_CHROMEOS)
{
pref_service_->SetInteger(
prefs::kCACertificateManagementAllowed,
@ -349,5 +353,48 @@ TEST_F(CertificateHandlerTest, CanEditCACertificateTest) {
cert_info.can_be_deleted_ = true;
EXPECT_FALSE(CanEditCertificate(&cert_info));
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
#endif // defined(OS_CHROMEOS)
}
#if BUILDFLAG(IS_CHROMEOS_LACROS)
TEST_F(CertificateHandlerTest, CannotManageFromSecondaryLacrosProfileTest) {
profile()->SetIsMainProfile(false);
pref_service_->SetInteger(
prefs::kCACertificateManagementAllowed,
static_cast<int>(CACertificateManagementPermission::kAll));
pref_service_->SetInteger(
prefs::kClientCertificateManagementAllowed,
static_cast<int>(ClientCertificateManagementPermission::kAll));
EXPECT_FALSE(
IsCACertificateManagementAllowedPolicy(CertificateSource::kImported));
EXPECT_FALSE(
IsCACertificateManagementAllowedPolicy(CertificateSource::kBuiltIn));
{
CertificateManagerModel::CertInfo cert_info(
{} /* cert */, net::CertType::USER_CERT, {} /* cert_name */,
true /* can_be_deleted */, false /* untrusted */,
CertificateManagerModel::CertInfo::Source::kPolicy,
true /* web_trust_anchor */, false /* hardware_backed */,
false /* device_wide */);
EXPECT_FALSE(CanDeleteCertificate(&cert_info));
EXPECT_FALSE(CanEditCertificate(&cert_info));
}
{
CertificateManagerModel::CertInfo cert_info(
{} /* cert */, net::CertType::CA_CERT, {} /* cert_name */,
true /* can_be_deleted */, false /* untrusted */,
CertificateManagerModel::CertInfo::Source::kPolicy,
true /* web_trust_anchor */, false /* hardware_backed */,
false /* device_wide */);
EXPECT_FALSE(CanDeleteCertificate(&cert_info));
EXPECT_FALSE(CanEditCertificate(&cert_info));
}
}
#endif // BUILDFLAG(IS_CHROMEOS_LACROS)

@ -818,12 +818,8 @@ WebUIFactoryFunction GetWebUIFactoryFunction(WebUI* web_ui,
}
if (url.host_piece() == chrome::kChromeUIBluetoothPairingHost)
return &NewWebUI<chromeos::BluetoothPairingDialogUI>;
// TODO(crbug.com/1147032): The certificates settings page is temporarily
// disabled for Lacros-Chrome until a better solution is found.
#if !BUILDFLAG(IS_CHROMEOS_LACROS)
if (url.host_piece() == chrome::kChromeUICertificateManagerHost)
return &NewWebUI<chromeos::CertificateManagerDialogUI>;
#endif // !BUILDFLAG(IS_CHROMEOS_LACROS)
if (url.host_piece() == ash::kChromeUIConnectivityDiagnosticsHost)
return &NewWebUI<ash::ConnectivityDiagnosticsUI>;
if (url.host_piece() == chrome::kChromeUICrostiniInstallerHost)

@ -173,9 +173,6 @@ SettingsUI::SettingsUI(content::WebUI* web_ui)
AddSettingsPageUIHandler(std::make_unique<AppearanceHandler>(web_ui));
// TODO(crbug.com/1147032): The certificates settings page is temporarily
// disabled for Lacros-Chrome until a better solution is found.
#if !BUILDFLAG(IS_CHROMEOS_LACROS)
#if defined(USE_NSS_CERTS)
AddSettingsPageUIHandler(
std::make_unique<certificate_manager::CertificatesHandler>());
@ -187,7 +184,6 @@ SettingsUI::SettingsUI(content::WebUI* web_ui)
chromeos::cert_provisioning::CertificateProvisioningUiHandler::
CreateForProfile(profile));
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
#endif // !BUILDFLAG(IS_CHROMEOS_LACROS)
AddSettingsPageUIHandler(std::make_unique<AccessibilityMainHandler>());
AddSettingsPageUIHandler(std::make_unique<BrowserLifetimeHandler>());

@ -3173,7 +3173,7 @@ const char kSignedHTTPExchangeEnabled[] = "web_package.signed_exchange.enabled";
// TODO(https://crbug.com/1003101): Remove this in Chrome 88.
const char kAllowSyncXHRInPageDismissal[] = "allow_sync_xhr_in_page_dismissal";
#if BUILDFLAG(IS_CHROMEOS_ASH)
#if defined(OS_CHROMEOS)
// Enum that specifies client certificate management permissions for user. It
// can have one of the following values.
// 0: Users can manage all certificates.

@ -1101,7 +1101,7 @@ extern const char kAllowSyncXHRInPageDismissal[];
extern const char kUsageStatsEnabled[];
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
#if defined(OS_CHROMEOS)
extern const char kClientCertificateManagementAllowed[];
extern const char kCACertificateManagementAllowed[];
#endif

@ -607,8 +607,13 @@ bool TestingProfile::IsOffTheRecord() const {
#if BUILDFLAG(IS_CHROMEOS_LACROS)
bool TestingProfile::IsMainProfile() const {
return false;
return is_main_profile_;
}
void TestingProfile::SetIsMainProfile(bool is_main_profile) {
is_main_profile_ = is_main_profile;
}
#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
const Profile::OTRProfileID& TestingProfile::GetOTRProfileID() const {

@ -286,6 +286,7 @@ class TestingProfile : public Profile {
bool IsOffTheRecord() const final;
#if BUILDFLAG(IS_CHROMEOS_LACROS)
bool IsMainProfile() const override;
void SetIsMainProfile(bool is_main_profile);
#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
const OTRProfileID& GetOTRProfileID() const override;
content::DownloadManagerDelegate* GetDownloadManagerDelegate() override;
@ -438,6 +439,10 @@ class TestingProfile : public Profile {
bool is_new_profile_ = false;
#if BUILDFLAG(IS_CHROMEOS_LACROS)
bool is_main_profile_ = false;
#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
std::string supervised_user_id_;
scoped_refptr<HostContentSettingsMap> host_content_settings_map_;

@ -6,8 +6,20 @@ module crosapi.mojom;
[Stable, Extensible]
struct GetCertDatabaseInfoResult {
string software_nss_db_path@0;
// Deprecated on 10/2021. Use `user_nss_database` from
// crosapi.BrowserInitParams instead.
// TODO(b/191958831): Delete the field when possible.
[RenamedFrom="software_nss_db_path"]
string DEPRECATED_software_nss_db_path@0;
bool should_load_chaps@1;
[MinVersion=1]
uint32 private_slot_id@2;
[MinVersion=1]
bool enable_system_slot@3;
[MinVersion=1]
uint32 system_slot_id@4;
};
// This interface is implemented by Ash-Chrome.
@ -15,5 +27,6 @@ struct GetCertDatabaseInfoResult {
interface CertDatabase {
// Waits until Ash-Chrome finishes certificate database initialization and
// returns necessary data for Lacros-Chrome to connect to it.
[MinVersion=1]
GetCertDatabaseInfo@0() => (GetCertDatabaseInfoResult? result);
};

@ -475,6 +475,12 @@ struct DefaultPaths {
// compatibility, to avoid assumptions about where on disk the directory is
// located.
[MinVersion=23] mojo_base.mojom.FilePath? drivefs@2;
// The (non-configurable) path of the software user NSS database. For
// example, /home/chronos/u-<hash>/.pki/nssdb. We send the full path for
// future compatibility, to avoid assumptions about where on disk the
// directory is located.
[MinVersion=30] mojo_base.mojom.FilePath? user_nss_database@3;
};
// The device specific data needed in Lacros.
@ -588,7 +594,7 @@ enum CreationResult {
// If ash-chrome is newer than the browser, then some fields may not be
// processed by the browser.
//
// Next version: 30
// Next version: 31
// Next id: 30
[Stable, RenamedFrom="crosapi.mojom.LacrosInitParams"]
struct BrowserInitParams {

@ -5,10 +5,12 @@
#include "crypto/chaps_support.h"
#include <dlfcn.h>
#include <secmod.h>
#include <secmodt.h>
#include "base/logging.h"
#include "base/threading/scoped_blocking_call.h"
#include "crypto/scoped_nss_types.h"
#include "nss_util_internal.h"
namespace crypto {
@ -76,6 +78,30 @@ SECMODModule* LoadChaps() {
"NSS=\"slotParams=(0={slotFlags=[PublicCerts] askpw=only})\"");
}
ScopedPK11Slot GetChapsSlot(SECMODModule* chaps_module, CK_SLOT_ID slot_id) {
DCHECK(chaps_module);
// NSS functions may reenter //net via extension hooks. If the reentered
// code needs to synchronously wait for a task to run but the thread pool in
// which that task must run doesn't have enough threads to schedule it, a
// deadlock occurs. To prevent that, the base::ScopedBlockingCall below
// increments the thread pool capacity for the duration of the TPM
// initialization.
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::WILL_BLOCK);
DVLOG(3) << "Poking chaps module.";
SECStatus rv = SECMOD_UpdateSlotList(chaps_module);
if (rv != SECSuccess)
LOG(ERROR) << "SECMOD_UpdateSlotList failed: " << PORT_GetError();
ScopedPK11Slot slot =
ScopedPK11Slot(SECMOD_LookupSlot(chaps_module->moduleID, slot_id));
if (!slot)
LOG(ERROR) << "TPM slot " << slot_id << " not found.";
return slot;
}
bool IsSlotProvidedByChaps(PK11SlotInfo* slot) {
if (!slot)
return false;

@ -8,13 +8,20 @@
#include <secmodt.h>
#include "crypto/crypto_export.h"
#include "crypto/scoped_nss_types.h"
namespace crypto {
// Loads chaps module for this NSS session.
// Loads chaps module for this NSS session. Should be called on a worker thread.
CRYPTO_EXPORT SECMODModule* LoadChaps();
// Returns true if chaps is the module to which |slot| is attached.
// Returns a slot with `slot_id` from the `chaps_module`. Should be called on a
// worker thread.
CRYPTO_EXPORT ScopedPK11Slot GetChapsSlot(SECMODModule* chaps_module,
CK_SLOT_ID slot_id);
// Returns true if chaps is the module to which |slot| is attached. Should be
// called on a worker thread.
CRYPTO_EXPORT bool IsSlotProvidedByChaps(PK11SlotInfo* slot);
} // namespace crypto

@ -226,8 +226,7 @@ class ChromeOSTokenManager {
tpm_args->chaps_module = LoadChaps();
}
if (tpm_args->chaps_module) {
tpm_args->tpm_slot =
GetTPMSlotForIdInThreadPool(tpm_args->chaps_module, token_slot_id);
tpm_args->tpm_slot = GetChapsSlot(tpm_args->chaps_module, token_slot_id);
}
}
@ -277,24 +276,6 @@ class ChromeOSTokenManager {
/*is_tpm_enabled=*/(state_ == State::kTpmTokenEnabled)));
}
// Note that CK_SLOT_ID is an unsigned long, but cryptohome gives us the slot
// id as an int. This should be safe since this is only used with chaps, which
// we also control.
static ScopedPK11Slot GetTPMSlotForIdInThreadPool(SECMODModule* chaps_module,
CK_SLOT_ID slot_id) {
DCHECK(chaps_module);
DVLOG(3) << "Poking chaps module.";
SECStatus rv = SECMOD_UpdateSlotList(chaps_module);
if (rv != SECSuccess)
PLOG(ERROR) << "SECMOD_UpdateSlotList failed: " << PORT_GetError();
PK11SlotInfo* slot = SECMOD_LookupSlot(chaps_module->moduleID, slot_id);
if (!slot)
LOG(ERROR) << "TPM slot " << slot_id << " not found.";
return ScopedPK11Slot(slot);
}
bool InitializeNSSForChromeOSUser(const std::string& username_hash,
const base::FilePath& path) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

@ -1173,7 +1173,7 @@ component("net") {
]
}
if (is_chromeos_ash && use_nss_certs) {
if ((is_chromeos_ash || is_chromeos_lacros) && use_nss_certs) {
sources += [
"cert/nss_cert_database_chromeos.cc",
"cert/nss_cert_database_chromeos.h",

@ -133,7 +133,7 @@ void NSSCertDatabase::ListCertsInfo(ListCertsInfoCallback callback) {
std::move(callback));
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
#if defined(OS_CHROMEOS)
crypto::ScopedPK11Slot NSSCertDatabase::GetSystemSlot() const {
return crypto::ScopedPK11Slot();
}
@ -146,7 +146,7 @@ bool NSSCertDatabase::IsCertificateOnSlot(CERTCertificate* cert,
return PK11_FindCertInSlot(slot, cert, nullptr) != CK_INVALID_HANDLE;
}
#endif
#endif // defined(OS_CHROMEOS)
crypto::ScopedPK11Slot NSSCertDatabase::GetPublicSlot() const {
return crypto::ScopedPK11Slot(PK11_ReferenceSlot(public_slot_.get()));

@ -159,14 +159,14 @@ class NET_EXPORT NSSCertDatabase {
// deleted.
virtual void ListCertsInfo(ListCertsInfoCallback callback);
#if BUILDFLAG(IS_CHROMEOS_ASH)
#if defined(OS_CHROMEOS)
// Get the slot for system-wide key data. May be NULL if the system token was
// not enabled for this database.
virtual crypto::ScopedPK11Slot GetSystemSlot() const;
// Checks whether |cert| is stored on |slot|.
static bool IsCertificateOnSlot(CERTCertificate* cert, PK11SlotInfo* slot);
#endif
#endif // defined(OS_CHROMEOS)
// Get the default slot for public key data.
crypto::ScopedPK11Slot GetPublicSlot() const;

@ -18,7 +18,7 @@
<cr-button id="import" on-click="onImportTap_"
hidden="[[!canImport_(certificateType, importAllowed, isKiosk_)]]">
[[i18n('certificateManagerImport')]]</cr-button>
<if expr="chromeos">
<if expr="chromeos or lacros">
<cr-button id="importAndBind" on-click="onImportAndBindTap_"
hidden="[[!canImportAndBind_(certificateType, importAllowed,
isGuest_)]]">

@ -43,7 +43,7 @@ export class CertificateListElement extends CertificateListElementBase {
certificateType: String,
importAllowed: Boolean,
// <if expr="chromeos">
// <if expr="chromeos or lacros">
isGuest_: {
type: Boolean,
value() {
@ -66,7 +66,7 @@ export class CertificateListElement extends CertificateListElementBase {
certificates: Array<CertificatesOrgGroup>;
certificateType: CertificateType;
importAllowed: boolean;
// <if expr="chromeos">
// <if expr="chromeos or lacros">
private isGuest_: boolean;
// </if>
private isKiosk_: boolean;
@ -95,7 +95,7 @@ export class CertificateListElement extends CertificateListElementBase {
this.importAllowed;
}
// <if expr="chromeos">
// <if expr="chromeos or lacros">
private canImportAndBind_(): boolean {
return !this.isGuest_ &&
this.certificateType === CertificateType.PERSONAL && this.importAllowed;
@ -141,7 +141,7 @@ export class CertificateListElement extends CertificateListElementBase {
this.handleImport_(false, e.target as HTMLElement);
}
// <if expr="chromeos">
// <if expr="chromeos or lacros">
private onImportAndBindTap_(e: Event) {
this.handleImport_(true, e.target as HTMLElement);
}