0

Certificates for Lacros (MVP)

This allows Lacros-Chrome access to the system-wide TPM slot,
the per-user TPM slot, and per-user storage for certificates and
trust settings, for enterprise-managed devices where the logged-in
user is an affiliate of that enterprise.

Rather than Lacros-Chrome directly interacting with the login services,
it requests that Ash-Chrome notify it when the TPM and Cryptohome are
available, receiving the path to the per-user storage and the result
of TPM initialization. Lacros-Chrome can then initialize initialize a
connection to the TPM (via a PKCS#11 module) and initialize storage at
the path provided by Ash-Chrome.

For users that are not affiliated, Chrome today enables access to both
the per-user TPM and per-user storage. However, for Lacros-Chrome, this
is a known issue and not yet supported (crbug.com/1146430). This is
because loading the TPM unconditionally loads both the system and user
slot today, while non-affiliated users should not have access to the
system slot. The per-user storage will be loaded for all users with
this CL.

Similarly, using the system slot on the login screen is not yet
supported, and will be addressed in a follow-up (crbug.com/1146430).

Bug: 1145946
Change-Id: I2edc6e04c2de09f4c8a4d3bdca9c9ba5a9dee3fa
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2435642
Commit-Queue: Michael Ershov <miersh@google.com>
Reviewed-by: David Roger <droger@chromium.org>
Reviewed-by: Roland Bock <rbock@google.com>
Reviewed-by: Pavol Marko <pmarko@chromium.org>
Reviewed-by: Ryan Sleevi <rsleevi@chromium.org>
Reviewed-by: Erik Chen <erikchen@chromium.org>
Cr-Commit-Position: refs/heads/master@{#832960}
This commit is contained in:
Michael Ershov
2020-12-02 21:01:17 +00:00
committed by Chromium LUCI CQ
parent 1340989ccd
commit 1c50ac9546
31 changed files with 707 additions and 81 deletions

@ -4559,6 +4559,10 @@ static_library("browser") {
"lacros/account_manager_facade_lacros.h",
"lacros/account_manager_util.cc",
"lacros/account_manager_util.h",
"lacros/cert_db_initializer.cc",
"lacros/cert_db_initializer.h",
"lacros/cert_db_initializer_factory.cc",
"lacros/cert_db_initializer_factory.h",
"lacros/feedback_util.cc",
"lacros/feedback_util.h",
"lacros/immersive_context_lacros.cc",

@ -1041,6 +1041,8 @@ source_set("chromeos") {
"crosapi/browser_manager_observer.h",
"crosapi/browser_util.cc",
"crosapi/browser_util.h",
"crosapi/cert_database_ash.cc",
"crosapi/cert_database_ash.h",
"crosapi/environment_provider.cc",
"crosapi/environment_provider.h",
"crosapi/fake_browser_manager.cc",

@ -13,6 +13,7 @@
#include "chrome/browser/browser_process_platform_part.h"
#include "chrome/browser/chromeos/crosapi/account_manager_ash.h"
#include "chrome/browser/chromeos/crosapi/browser_manager.h"
#include "chrome/browser/chromeos/crosapi/cert_database_ash.h"
#include "chrome/browser/chromeos/crosapi/feedback_ash.h"
#include "chrome/browser/chromeos/crosapi/file_manager_ash.h"
#include "chrome/browser/chromeos/crosapi/keystore_service_ash.h"
@ -39,7 +40,8 @@ namespace crosapi {
AshChromeServiceImpl::AshChromeServiceImpl(
mojo::PendingReceiver<mojom::AshChromeService> pending_receiver)
: receiver_(this, std::move(pending_receiver)),
screen_manager_ash_(std::make_unique<ScreenManagerAsh>()) {
screen_manager_ash_(std::make_unique<ScreenManagerAsh>()),
cert_database_ash_(std::make_unique<CertDatabaseAsh>()) {
// TODO(hidehiko): Remove non-critical log from here.
// Currently this is the signal that the connection is established.
LOG(WARNING) << "AshChromeService connected.";
@ -146,6 +148,11 @@ void AshChromeServiceImpl::BindMediaSessionAudioFocusDebug(
std::move(receiver));
}
void AshChromeServiceImpl::BindCertDatabase(
mojo::PendingReceiver<mojom::CertDatabase> receiver) {
cert_database_ash_->BindReceiver(std::move(receiver));
}
void AshChromeServiceImpl::OnLacrosStartup(mojom::LacrosInfoPtr lacros_info) {
BrowserManager::Get()->set_lacros_version(lacros_info->lacros_version);
}

@ -14,6 +14,7 @@
namespace crosapi {
class AccountManagerAsh;
class CertDatabaseAsh;
class FeedbackAsh;
class FileManagerAsh;
class KeystoreServiceAsh;
@ -45,6 +46,8 @@ class AshChromeServiceImpl : public mojom::AshChromeService {
void BindHidManager(
mojo::PendingReceiver<device::mojom::HidManager> receiver) override;
void BindFeedback(mojo::PendingReceiver<mojom::Feedback> receiver) override;
void BindCertDatabase(
mojo::PendingReceiver<mojom::CertDatabase> receiver) override;
void OnLacrosStartup(mojom::LacrosInfoPtr lacros_info) override;
void BindMediaSessionController(
mojo::PendingReceiver<media_session::mojom::MediaControllerManager>
@ -66,6 +69,7 @@ class AshChromeServiceImpl : public mojom::AshChromeService {
std::unique_ptr<ScreenManagerAsh> screen_manager_ash_;
std::unique_ptr<SelectFileAsh> select_file_ash_;
std::unique_ptr<FeedbackAsh> feedback_ash_;
std::unique_ptr<CertDatabaseAsh> cert_database_ash_;
};
} // namespace crosapi

@ -23,6 +23,7 @@
#include "chrome/common/pref_names.h"
#include "chromeos/constants/chromeos_features.h"
#include "chromeos/crosapi/cpp/crosapi_constants.h"
#include "chromeos/crosapi/mojom/cert_database.mojom.h"
#include "chromeos/crosapi/mojom/crosapi.mojom.h"
#include "components/exo/shell_surface_util.h"
#include "components/metrics/metrics_pref_names.h"
@ -156,11 +157,12 @@ bool IsLacrosWindow(const aura::Window* window) {
base::flat_map<base::Token, uint32_t> GetInterfaceVersions() {
static_assert(
crosapi::mojom::AshChromeService::Version_ == 6,
crosapi::mojom::AshChromeService::Version_ == 7,
"if you add a new crosapi, please add it to the version map here");
InterfaceVersions versions;
AddVersion<crosapi::mojom::AccountManager>(&versions);
AddVersion<crosapi::mojom::AshChromeService>(&versions);
AddVersion<crosapi::mojom::CertDatabase>(&versions);
AddVersion<crosapi::mojom::Feedback>(&versions);
AddVersion<crosapi::mojom::FileManager>(&versions);
AddVersion<crosapi::mojom::KeystoreService>(&versions);

@ -0,0 +1,115 @@
// Copyright 2020 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/chromeos/crosapi/cert_database_ash.h"
#include "chrome/browser/chromeos/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 "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_thread.h"
#include "crypto/nss_util_internal.h"
namespace crosapi {
CertDatabaseAsh::CertDatabaseAsh() {
DCHECK(chromeos::LoginState::IsInitialized());
chromeos::LoginState::Get()->AddObserver(this);
}
CertDatabaseAsh::~CertDatabaseAsh() {
chromeos::LoginState::Get()->RemoveObserver(this);
}
void CertDatabaseAsh::BindReceiver(
mojo::PendingReceiver<mojom::CertDatabase> pending_receiver) {
receivers_.Add(this, std::move(pending_receiver));
}
void CertDatabaseAsh::GetCertDatabaseInfo(
GetCertDatabaseInfoCallback callback) {
// 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.
if (!chromeos::LoginState::Get()->IsUserLoggedIn()) {
LOG(ERROR) << "Not implemented";
std::move(callback).Run(nullptr);
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));
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()) {
std::move(callback).Run(nullptr);
return;
}
// Otherwise, if the TPM was already loaded previously, let the
// caller know.
// TODO(crbug.com/1146430) For now Lacros-Chrome loads chaps and has access to
// TPM operations only for affiliated users, because it gives access to
// system token. Find a way to give unaffiliated users access only to user TPM
// token.
mojom::GetCertDatabaseInfoResultPtr result =
mojom::GetCertDatabaseInfoResult::New();
result->should_load_chaps = user->IsAffiliated();
result->software_nss_db_path =
crypto::GetSoftwareNSSDBPath(
ProfileManager::GetPrimaryUserProfile()->GetPath())
.value();
std::move(callback).Run(std::move(result));
}
void CertDatabaseAsh::WaitForTpmTokenReady(
GetCertDatabaseInfoCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
Profile* profile = ProfileManager::GetPrimaryUserProfile();
AccountId account_id =
chromeos::ProfileHelper::Get()->GetUserByProfile(profile)->GetAccountId();
std::unique_ptr<chromeos::TPMTokenInfoGetter> scoped_token_info_getter =
chromeos::TPMTokenInfoGetter::CreateForUserToken(
account_id, chromeos::CryptohomeClient::Get(),
base::ThreadTaskRunnerHandle::Get());
chromeos::TPMTokenInfoGetter* token_info_getter =
scoped_token_info_getter.get();
token_info_getter->Start(base::BindOnce(
&CertDatabaseAsh::OnTpmTokenReady, weak_factory_.GetWeakPtr(),
std::move(scoped_token_info_getter), std::move(callback)));
}
void CertDatabaseAsh::OnTpmTokenReady(
std::unique_ptr<chromeos::TPMTokenInfoGetter> token_getter,
GetCertDatabaseInfoCallback callback,
base::Optional<chromeos::CryptohomeClient::TpmTokenInfo> token_info) {
is_tpm_token_ready_ = token_info.has_value();
// Calling the initial method again. Since |is_tpm_token_ready_| is not empty
// this time, it will return some result via mojo.
GetCertDatabaseInfo(std::move(callback));
}
void CertDatabaseAsh::LoggedInStateChanged() {
// 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();
}
} // namespace crosapi

@ -0,0 +1,70 @@
// Copyright 2020 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_CHROMEOS_CROSAPI_CERT_DATABASE_ASH_H_
#define CHROME_BROWSER_CHROMEOS_CROSAPI_CERT_DATABASE_ASH_H_
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "chromeos/crosapi/mojom/cert_database.mojom.h"
#include "chromeos/dbus/cryptohome/cryptohome_client.h"
#include "chromeos/login/login_state/login_state.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
namespace chromeos {
class TPMTokenInfoGetter;
} // namespace chromeos
namespace crosapi {
// Implements the crosapi interface for certificate database. Lives in
// Ash-Chrome on the UI thread.
//
// It is expected that during Lacros-Chrome initialization when it creates the
// main profile (that contains device account), it will call GetCertDatabaseInfo
// mojo API. If the ChromeOS user session was just started, it can take time for
// Ash-Chrome to initialize TPM and certificate database. When it is done, the
// API call will be resolved. If Lacros-Chrome is restarted, it will call
// GetCertDatabaseInfo again and receive a cached result from the first call.
// The cached result is reset on login state change (i.e. sign in / sign out).
class CertDatabaseAsh : public mojom::CertDatabase,
chromeos::LoginState::Observer {
public:
CertDatabaseAsh();
CertDatabaseAsh(const CertDatabaseAsh&) = delete;
CertDatabaseAsh& operator=(const CertDatabaseAsh&) = delete;
~CertDatabaseAsh() override;
void BindReceiver(mojo::PendingReceiver<mojom::CertDatabase> receiver);
// Returns to Lacros-Chrome all necessary data to initialize certificate
// database when it is ready. Caches the result of first call for all
// subsequent calls during current user session.
void GetCertDatabaseInfo(GetCertDatabaseInfoCallback callback) override;
private:
// 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,
base::Optional<chromeos::CryptohomeClient::TpmTokenInfo> token_info);
base::Optional<bool> is_tpm_token_ready_;
// This class supports any number of connections. This allows the client to
// have multiple, potentially thread-affine, remotes.
mojo::ReceiverSet<mojom::CertDatabase> receivers_;
base::WeakPtrFactory<CertDatabaseAsh> weak_factory_{this};
};
} // namespace crosapi
#endif // CHROME_BROWSER_CHROMEOS_CROSAPI_CERT_DATABASE_ASH_H_

@ -0,0 +1,117 @@
// Copyright 2020 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.h"
#include "base/callback_forward.h"
#include "base/check.h"
#include "base/files/file_path.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/profiles/profile.h"
#include "chromeos/crosapi/cpp/crosapi_constants.h"
#include "chromeos/crosapi/mojom/cert_database.mojom.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "crypto/chaps_support.h"
#include "crypto/nss_util.h"
#include "crypto/nss_util_internal.h"
#include "mojo/public/cpp/bindings/remote.h"
class IdentityManagerObserver : public signin::IdentityManager::Observer {
public:
explicit IdentityManagerObserver(signin::IdentityManager* identity_manager)
: identity_manager_(identity_manager) {
DCHECK(identity_manager_);
identity_manager_->AddObserver(this);
}
IdentityManagerObserver(const IdentityManagerObserver&) = delete;
IdentityManagerObserver& operator==(const IdentityManagerObserver&) = delete;
~IdentityManagerObserver() override {
identity_manager_->RemoveObserver(this);
}
void WaitForRefreshTokensLoaded(base::OnceClosure callback) {
DCHECK(callback_.is_null());
callback_ = std::move(callback);
}
private:
void OnRefreshTokensLoaded() override {
if (!callback_) {
return;
}
std::move(callback_).Run(); // NOTE: may delete `this`.
}
signin::IdentityManager* identity_manager_ = nullptr;
base::OnceClosure callback_;
};
// =============================================================================
CertDbInitializer::CertDbInitializer(
mojo::Remote<crosapi::mojom::CertDatabase>& cert_database_remote,
Profile* profile)
: cert_database_remote_(cert_database_remote), profile_(profile) {}
CertDbInitializer::~CertDbInitializer() = default;
void CertDbInitializer::Start(signin::IdentityManager* identity_manager) {
DCHECK(identity_manager);
// TODO(crbug.com/1148300): This is temporary. Until ~2021
// `Profile::IsMainProfile()` method can return a false negative response if
// called before refresh tokens are loaded. This should be removed together
// with the entire usage of `IdentityManager` in this class when it is not the
// case anymore.
if (!identity_manager->AreRefreshTokensLoaded()) {
identity_manager_observer_ =
std::make_unique<IdentityManagerObserver>(identity_manager);
identity_manager_observer_->WaitForRefreshTokensLoaded(base::BindOnce(
&CertDbInitializer::OnRefreshTokensLoaded, weak_factory_.GetWeakPtr()));
return;
}
WaitForCertDbReady();
}
void CertDbInitializer::OnRefreshTokensLoaded() {
identity_manager_observer_.reset();
WaitForCertDbReady();
}
void CertDbInitializer::WaitForCertDbReady() {
if (!profile_->IsMainProfile()) {
return;
}
cert_database_remote_->GetCertDatabaseInfo(base::BindOnce(
&CertDbInitializer::OnCertDbInfoReceived, weak_factory_.GetWeakPtr()));
}
void CertDbInitializer::OnCertDbInfoReceived(
crosapi::mojom::GetCertDatabaseInfoResultPtr cert_db_info) {
if (!cert_db_info) {
LOG(WARNING) << "Certificate database is not accesible";
return;
}
crypto::EnsureNSSInit();
// 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 (cert_db_info->should_load_chaps && !crypto::LoadChaps()) {
LOG(ERROR) << "Failed to load chaps.";
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.
base::FilePath software_nss_db_path(cert_db_info->software_nss_db_path);
auto slot = crypto::OpenSoftwareNSSDB(software_nss_db_path,
/*description=*/"cert_db");
if (!slot) {
LOG(ERROR) << "Failed to open user certificate database";
}
}

@ -0,0 +1,47 @@
// Copyright 2020 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_H_
#define CHROME_BROWSER_LACROS_CERT_DB_INITIALIZER_H_
#include "base/memory/weak_ptr.h"
#include "chromeos/crosapi/mojom/cert_database.mojom.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "mojo/public/cpp/bindings/remote.h"
class Profile;
class IdentityManagerObserver;
// Initializes certificate database in Lacros-Chrome.
class CertDbInitializer : public KeyedService {
public:
CertDbInitializer(
mojo::Remote<crosapi::mojom::CertDatabase>& cert_database_remote,
Profile* profile);
~CertDbInitializer() override;
void Start(signin::IdentityManager* identity_manager);
private:
// Starts the initialization. The first step is to wait for IdentityManager.
void OnRefreshTokensLoaded();
// 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();
// Checks from the result that the certificate database should be initialized.
// If yes, loads Chaps and opens user's certificate database.
void OnCertDbInfoReceived(
crosapi::mojom::GetCertDatabaseInfoResultPtr result);
mojo::Remote<crosapi::mojom::CertDatabase>& cert_database_remote_;
Profile* profile_ = nullptr;
std::unique_ptr<IdentityManagerObserver> identity_manager_observer_;
base::WeakPtrFactory<CertDbInitializer> weak_factory_{this};
};
#endif // CHROME_BROWSER_LACROS_CERT_DB_INITIALIZER_H_

@ -0,0 +1,49 @@
// Copyright 2020 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_factory.h"
#include "chrome/browser/lacros/cert_db_initializer.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chromeos/lacros/lacros_chrome_service_impl.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
class Profile;
// static
CertDbInitializerFactory* CertDbInitializerFactory::GetInstance() {
static base::NoDestructor<CertDbInitializerFactory> factory;
return factory.get();
}
CertDbInitializerFactory::CertDbInitializerFactory()
: BrowserContextKeyedServiceFactory(
"CertDbInitializerFactory",
BrowserContextDependencyManager::GetInstance()) {
DependsOn(IdentityManagerFactory::GetInstance());
}
bool CertDbInitializerFactory::ServiceIsCreatedWithBrowserContext() const {
return true;
}
KeyedService* CertDbInitializerFactory::BuildServiceInstanceFor(
content::BrowserContext* context) const {
Profile* profile = Profile::FromBrowserContext(context);
if (!chromeos::LacrosChromeServiceImpl::Get()->IsCertDbAvailable()) {
return nullptr;
}
CertDbInitializer* result = new CertDbInitializer(
chromeos::LacrosChromeServiceImpl::Get()->cert_database_remote(),
profile);
// TODO(crbug.com/1145946): Enable certificate database initialization when
// the policy stack is ready (expected to happen before Feb 2021).
if (/* DISABLES CODE */ (false)) {
result->Start(IdentityManagerFactory::GetForProfile(profile));
}
return result;
}

@ -0,0 +1,27 @@
// Copyright 2020 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_FACTORY_H_
#define CHROME_BROWSER_LACROS_CERT_DB_INITIALIZER_FACTORY_H_
#include "base/no_destructor.h"
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
class CertDbInitializerFactory : public BrowserContextKeyedServiceFactory {
public:
static CertDbInitializerFactory* GetInstance();
private:
friend class base::NoDestructor<CertDbInitializerFactory>;
CertDbInitializerFactory();
~CertDbInitializerFactory() override = default;
// BrowserStateKeyedServiceFactory
bool ServiceIsCreatedWithBrowserContext() const override;
KeyedService* BuildServiceInstanceFor(
content::BrowserContext* context) const override;
};
#endif // CHROME_BROWSER_LACROS_CERT_DB_INITIALIZER_FACTORY_H_

@ -4,6 +4,7 @@
#include "chrome/browser/net/nss_context.h"
#include "build/chromeos_buildflags.h"
#include "content/public/browser/browser_thread.h"
#include "crypto/nss_util.h"
#include "net/cert/nss_cert_database.h"
@ -18,6 +19,13 @@ net::NSSCertDatabase* GetNSSCertDatabaseForResourceContext(
// This initialization is not thread safe. This CHECK ensures that this code
// is only run on a single thread.
CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
#if BUILDFLAG(IS_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.

@ -576,6 +576,15 @@ ProfileNetworkContextService::CreateClientCertStore() {
base::BindRepeating(&CreateCryptoModuleBlockingPasswordDelegate,
kCryptoModulePasswordClientAuth));
#elif defined(USE_NSS_CERTS)
#if BUILDFLAG(IS_LACROS)
if (!profile_->IsMainProfile()) {
// TODO(crbug.com/1148298): return some cert store for secondary profiles in
// Lacros-Chrome.
return nullptr;
}
#endif // BUILDFLAG(IS_LACROS)
return std::make_unique<net::ClientCertStoreNSS>(
base::BindRepeating(&CreateCryptoModuleBlockingPasswordDelegate,
kCryptoModulePasswordClientAuth));

@ -189,6 +189,10 @@
#include "chrome/browser/nearby_sharing/nearby_sharing_service_factory.h"
#endif
#if BUILDFLAG(IS_LACROS)
#include "chrome/browser/lacros/cert_db_initializer_factory.h"
#endif
namespace chrome {
void AddProfilesExtraParts(ChromeBrowserMainParts* main_parts) {
@ -432,6 +436,10 @@ void ChromeBrowserMainExtraPartsProfiles::
#endif
WebDataServiceFactory::GetInstance();
webrtc_event_logging::WebRtcEventLogManagerKeyedServiceFactory::GetInstance();
#if BUILDFLAG(IS_LACROS)
CertDbInitializerFactory::GetInstance();
#endif
}
void ChromeBrowserMainExtraPartsProfiles::PreProfileInit() {

@ -18,7 +18,9 @@ function addPrivacyChildRoutes(r) {
r.COOKIES = r.PRIVACY.createChild('/cookies');
r.SECURITY = r.PRIVACY.createChild('/security');
// <if expr="use_nss_certs">
// 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">
r.CERTIFICATES = r.SECURITY.createChild('/certificates');
// </if>

@ -1994,6 +1994,11 @@ IN_PROC_BROWSER_TEST_F(SSLUITestWithHttpDangerous, MarkBlobAsNonSecure) {
EXPECT_EQ(security_state::NONE, helper->GetSecurityLevel());
}
// TODO(crbug.com/1148302): This class directly calls
// `GetNSSCertDatabaseForProfile()` that causes crash at the moment and is never
// called from Lacros-Chrome. This should be revisited when there is a solution
// for the client certificates settings page on Lacros-Chrome.
#if !BUILDFLAG(IS_LACROS)
#if defined(USE_NSS_CERTS)
class SSLUITestWithClientCert : public SSLUITestBase {
public:
@ -2084,6 +2089,7 @@ IN_PROC_BROWSER_TEST_F(SSLUITestWithClientCert, TestWSSClientCert) {
EXPECT_TRUE(base::LowerCaseEqualsASCII(result, "pass"));
}
#endif // defined(USE_NSS_CERTS)
#endif // !BUILDFLAG(IS_LACROS)
// A stub ClientCertStore that returns a FakeClientCertIdentity.
class ClientCertStoreStub : public net::ClientCertStore {

@ -701,8 +701,12 @@ WebUIFactoryFunction GetWebUIFactoryFunction(WebUI* web_ui,
return &NewWebUI<chromeos::BluetoothPairingDialogUI>;
if (url.host_piece() == chrome::kChromeUICellularSetupHost)
return &NewWebUI<chromeos::cellular_setup::CellularSetupDialogUI>;
// TODO(crbug.com/1147032): The certificates settings page is temporarily
// disabled for Lacros-Chrome until a better solution is found.
#if !BUILDFLAG(IS_LACROS)
if (url.host_piece() == chrome::kChromeUICertificateManagerHost)
return &NewWebUI<chromeos::CertificateManagerDialogUI>;
#endif // !BUILDFLAG(IS_LACROS)
if (base::FeatureList::IsEnabled(
chromeos::features::kConnectivityDiagnosticsWebUi) &&
url.host_piece() == chromeos::kChromeUIConnectivityDiagnosticsHost) {

@ -166,6 +166,9 @@ 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_LACROS)
#if defined(USE_NSS_CERTS)
AddSettingsPageUIHandler(
std::make_unique<certificate_manager::CertificatesHandler>());
@ -176,7 +179,8 @@ SettingsUI::SettingsUI(content::WebUI* web_ui)
AddSettingsPageUIHandler(
chromeos::cert_provisioning::CertificateProvisioningUiHandler::
CreateForProfile(profile));
#endif
#endif // defined(OS_CHROMEOS)
#endif // !BUILDFLAG(IS_LACROS)
AddSettingsPageUIHandler(std::make_unique<AccessibilityMainHandler>());
AddSettingsPageUIHandler(std::make_unique<BrowserLifetimeHandler>());

@ -3,7 +3,7 @@
// found in the LICENSE file.
// clang-format off
import {isMac, isWindows} from 'chrome://resources/js/cr.m.js';
import {isLacros, isMac, isWindows} from 'chrome://resources/js/cr.m.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {SafeBrowsingSetting} from 'chrome://settings/lazy_load.js';
@ -87,12 +87,18 @@ suite('CrSettingsSecurityPageTest', function() {
assertTrue(page.$$('#safeBrowsingStandard').expanded);
});
test('LogManageCerfificatesClick', async function() {
page.$$('#manageCertificates').click();
const result =
await testMetricsBrowserProxy.whenCalled('recordSettingsPageHistogram');
assertEquals(PrivacyElementInteractions.MANAGE_CERTIFICATES, result);
});
// TODO(crbug.com/1148302): This class directly calls
// `GetNSSCertDatabaseForProfile()` that causes crash at the moment and is
// never called from Lacros-Chrome. This should be revisited when there is a
// solution for the client certificates settings page on Lacros-Chrome.
if (!isLacros) {
test('LogManageCerfificatesClick', async function() {
page.$$('#manageCertificates').click();
const result = await testMetricsBrowserProxy.whenCalled(
'recordSettingsPageHistogram');
assertEquals(PrivacyElementInteractions.MANAGE_CERTIFICATES, result);
});
}
test('ManageSecurityKeysSubpageVisible', function() {
assertTrue(isChildVisible(page, '#security-keys-subpage-trigger'));

@ -8,6 +8,7 @@ mojom("mojom") {
sources = [
"account_manager.mojom",
"bitmap.mojom",
"cert_database.mojom",
"crosapi.mojom",
"feedback.mojom",
"file_manager.mojom",

@ -0,0 +1,19 @@
// Copyright 2020 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.
module crosapi.mojom;
[Stable, Extensible]
struct GetCertDatabaseInfoResult {
string software_nss_db_path@0;
bool should_load_chaps@1;
};
// This interface is implemented by Ash-Chrome.
[Stable, Uuid="e7f924bf-0e10-4aef-98d3-6e2f216dc914"]
interface CertDatabase {
// Waits until Ash-Chrome finishes certificate database initialization and
// returns necessary data for Lacros-Chrome to connect to it.
GetCertDatabaseInfo@0() => (GetCertDatabaseInfoResult? result);
};

@ -5,6 +5,7 @@
module crosapi.mojom;
import "chromeos/crosapi/mojom/account_manager.mojom";
import "chromeos/crosapi/mojom/cert_database.mojom";
import "chromeos/crosapi/mojom/feedback.mojom";
import "chromeos/crosapi/mojom/file_manager.mojom";
import "chromeos/crosapi/mojom/keystore_service.mojom";
@ -39,8 +40,8 @@ struct LacrosInfo {
// milestone when you added it, to help us reason about compatibility between
// lacros-chrome and older ash-chrome binaries.
//
// Next version: 7
// Next method id: 12
// Next version: 8
// Next method id: 13
[Stable, Uuid="8b79c34f-2bf8-4499-979a-b17cac522c1e"]
interface AshChromeService {
// Binds Chrome OS Account Manager for Identity management.
@ -96,6 +97,11 @@ interface AshChromeService {
[MinVersion=6] BindMediaSessionAudioFocusDebug@11(
pending_receiver<media_session.mojom.AudioFocusManagerDebug> receiver);
// Binds the CertDatabase interface for initializing certificate database in
// Lacros-Chrome.
// Added in M89.
[MinVersion=7] BindCertDatabase@12(pending_receiver<CertDatabase> receiver);
// Passes generic lacros information such as lacros version, etc into ash
// in |lacros_info| during startup.
// Added in M87.

@ -10,6 +10,7 @@
#include "base/logging.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "chromeos/crosapi/mojom/crosapi.mojom.h"
#include "chromeos/lacros/lacros_chrome_service_delegate.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "url/gurl.h"
@ -164,6 +165,12 @@ class LacrosChromeServiceNeverBlockingState
ash_chrome_service_->BindFeedback(std::move(pending_receiver));
}
void BindCertDbReceiver(
mojo::PendingReceiver<crosapi::mojom::CertDatabase> pending_receiver) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ash_chrome_service_->BindCertDatabase(std::move(pending_receiver));
}
void OnLacrosStartup(crosapi::mojom::LacrosInfoPtr lacros_info) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ash_chrome_service_->OnLacrosStartup(std::move(lacros_info));
@ -355,6 +362,15 @@ void LacrosChromeServiceImpl::BindReceiver(
feedback_remote_.BindNewPipeAndPassReceiver()));
}
if (IsCertDbAvailable()) {
never_blocking_sequence_->PostTask(
FROM_HERE,
base::BindOnce(
&LacrosChromeServiceNeverBlockingState::BindCertDbReceiver,
weak_sequenced_state_,
cert_database_remote_.BindNewPipeAndPassReceiver()));
}
if (IsOnLacrosStartupAvailable()) {
never_blocking_sequence_->PostTask(
FROM_HERE,
@ -500,6 +516,13 @@ void LacrosChromeServiceImpl::BindMediaControllerManager(
weak_sequenced_state_, std::move(remote)));
}
bool LacrosChromeServiceImpl::IsCertDbAvailable() {
base::Optional<uint32_t> version = AshChromeServiceVersion();
return version &&
version.value() >=
AshChromeService::MethodMinVersions::kBindCertDatabaseMinVersion;
}
bool LacrosChromeServiceImpl::IsOnLacrosStartupAvailable() {
base::Optional<uint32_t> version = AshChromeServiceVersion();
return version &&

@ -14,6 +14,7 @@
#include "base/sequence_checker.h"
#include "base/sequenced_task_runner.h"
#include "chromeos/crosapi/mojom/account_manager.mojom.h"
#include "chromeos/crosapi/mojom/cert_database.mojom.h"
#include "chromeos/crosapi/mojom/crosapi.mojom.h"
#include "chromeos/crosapi/mojom/feedback.mojom.h"
#include "chromeos/crosapi/mojom/keystore_service.mojom.h"
@ -161,6 +162,15 @@ class COMPONENT_EXPORT(CHROMEOS_LACROS) LacrosChromeServiceImpl {
void BindMediaControllerManager(
mojo::PendingReceiver<media_session::mojom::MediaControllerManager>
remote);
// cert_database_remote() can only be used when this method returns true;
bool IsCertDbAvailable();
// This must be called on the affine sequence.
mojo::Remote<crosapi::mojom::CertDatabase>& cert_database_remote() {
DCHECK_CALLED_ON_VALID_SEQUENCE(affine_sequence_checker_);
DCHECK(IsCertDbAvailable());
return cert_database_remote_;
}
// file_manager_remote() can only be used if this method returns true.
bool IsFileManagerAvailable();
@ -251,6 +261,7 @@ class COMPONENT_EXPORT(CHROMEOS_LACROS) LacrosChromeServiceImpl {
mojo::Remote<crosapi::mojom::SelectFile> select_file_remote_;
mojo::Remote<device::mojom::HidManager> hid_manager_remote_;
mojo::Remote<crosapi::mojom::Feedback> feedback_remote_;
mojo::Remote<crosapi::mojom::CertDatabase> cert_database_remote_;
mojo::Remote<crosapi::mojom::KeystoreService> keystore_service_remote_;
mojo::Remote<crosapi::mojom::FileManager> file_manager_remote_;

@ -123,6 +123,13 @@ component("crypto") {
sources += [ "nss_util_chromeos.cc" ]
}
if (is_chromeos || is_lacros) {
sources += [
"chaps_support.cc",
"chaps_support.h",
]
}
defines = [ "CRYPTO_IMPLEMENTATION" ]
if (is_nacl) {

88
crypto/chaps_support.cc Normal file

@ -0,0 +1,88 @@
// Copyright 2020 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 "crypto/chaps_support.h"
#include <dlfcn.h>
#include <secmodt.h>
#include "base/logging.h"
#include "base/threading/scoped_blocking_call.h"
#include "nss_util_internal.h"
namespace crypto {
namespace {
// Constants for loading the Chrome OS TPM-backed PKCS #11 library.
const char kChapsModuleName[] = "Chaps";
const char kChapsPath[] = "libchaps.so";
class ScopedChapsLoadFixup {
public:
ScopedChapsLoadFixup();
~ScopedChapsLoadFixup();
private:
#if defined(COMPONENT_BUILD)
void* chaps_handle_;
#endif
};
#if defined(COMPONENT_BUILD)
ScopedChapsLoadFixup::ScopedChapsLoadFixup() {
// HACK: libchaps links the system protobuf and there are symbol conflicts
// with the bundled copy. Load chaps with RTLD_DEEPBIND to workaround.
chaps_handle_ = dlopen(kChapsPath, RTLD_LOCAL | RTLD_NOW | RTLD_DEEPBIND);
}
ScopedChapsLoadFixup::~ScopedChapsLoadFixup() {
// LoadNSSModule() will have taken a 2nd reference.
if (chaps_handle_)
dlclose(chaps_handle_);
}
#else
ScopedChapsLoadFixup::ScopedChapsLoadFixup() = default;
ScopedChapsLoadFixup::~ScopedChapsLoadFixup() = default;
#endif // defined(COMPONENT_BUILD)
} // namespace
SECMODModule* LoadChaps() {
// 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);
ScopedChapsLoadFixup chaps_loader;
DVLOG(3) << "Loading chaps...";
return LoadNSSModule(
kChapsModuleName, kChapsPath,
// For more details on these parameters, see:
// https://developer.mozilla.org/en/PKCS11_Module_Specs
// slotFlags=[PublicCerts] -- Certificates and public keys can be
// read from this slot without requiring a call to C_Login.
// askpw=only -- Only authenticate to the token when necessary.
"NSS=\"slotParams=(0={slotFlags=[PublicCerts] askpw=only})\"");
}
bool IsSlotProvidedByChaps(PK11SlotInfo* slot) {
if (!slot)
return false;
SECMODModule* pk11_module = PK11_GetModule(slot);
return pk11_module && base::StringPiece(pk11_module->commonName) ==
base::StringPiece(kChapsModuleName);
}
} // namespace crypto

22
crypto/chaps_support.h Normal file

@ -0,0 +1,22 @@
// Copyright 2020 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 CRYPTO_CHAPS_SUPPORT_H_
#define CRYPTO_CHAPS_SUPPORT_H_
#include <secmodt.h>
#include "crypto/crypto_export.h"
namespace crypto {
// Loads chaps module for this NSS session.
CRYPTO_EXPORT SECMODModule* LoadChaps();
// Returns true if chaps is the module to which |slot| is attached.
CRYPTO_EXPORT bool IsSlotProvidedByChaps(PK11SlotInfo* slot);
} // namespace crypto
#endif // CRYPTO_CHAPS_SUPPORT_H_

@ -35,13 +35,14 @@ namespace crypto {
namespace {
#if BUILDFLAG(IS_CHROMEOS_ASH)
#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_LACROS)
// Fake certificate authority database used for testing.
static const base::FilePath::CharType kReadOnlyCertDB[] =
FILE_PATH_LITERAL("/etc/fake_root_ca/nssdb");
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
#if !BUILDFLAG(IS_CHROMEOS_ASH)
#else
base::FilePath GetDefaultConfigDirectory() {
base::FilePath dir;
base::PathService::Get(base::DIR_HOME, &dir);
@ -57,7 +58,8 @@ base::FilePath GetDefaultConfigDirectory() {
DVLOG(2) << "DefaultConfigDirectory: " << dir.value();
return dir;
}
#endif // !BUILDFLAG(IS_CHROMEOS_ASH)
#endif // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_LACROS)
// On non-Chrome OS platforms, return the default config directory. On Chrome OS
// test images, return a read-only directory with fake root CA certs (which are
@ -65,7 +67,7 @@ base::FilePath GetDefaultConfigDirectory() {
// code). On Chrome OS non-test images (where the read-only directory doesn't
// exist), return an empty path.
base::FilePath GetInitialConfigDirectory() {
#if BUILDFLAG(IS_CHROMEOS_ASH)
#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_LACROS)
base::FilePath database_dir = base::FilePath(kReadOnlyCertDB);
if (!base::PathExists(database_dir))
database_dir.clear();
@ -158,7 +160,7 @@ class NSSInitSingleton {
// Use "sql:" which can be shared by multiple processes safely.
std::string nss_config_dir =
base::StringPrintf("sql:%s", database_dir.value().c_str());
#if BUILDFLAG(IS_CHROMEOS_ASH)
#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_LACROS)
status = NSS_Init(nss_config_dir.c_str());
#else
status = NSS_InitReadWrite(nss_config_dir.c_str());

@ -4,7 +4,6 @@
#include "crypto/nss_util.h"
#include <dlfcn.h>
#include <nss.h>
#include <pk11pub.h>
#include <plarena.h>
@ -35,6 +34,7 @@
#include "base/threading/thread_restrictions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "crypto/chaps_support.h"
#include "crypto/nss_util_internal.h"
namespace crypto {
@ -43,10 +43,6 @@ namespace {
const char kUserNSSDatabaseName[] = "UserNSSDB";
// Constants for loading the Chrome OS TPM-backed PKCS #11 library.
const char kChapsModuleName[] = "Chaps";
const char kChapsPath[] = "libchaps.so";
class ChromeOSUserData {
public:
using SlotReadyCallback = base::OnceCallback<void(ScopedPK11Slot)>;
@ -110,38 +106,6 @@ class ChromeOSUserData {
SlotReadyCallbackList tpm_ready_callback_list_;
};
class ScopedChapsLoadFixup {
public:
ScopedChapsLoadFixup();
~ScopedChapsLoadFixup();
private:
#if defined(COMPONENT_BUILD)
void* chaps_handle_;
#endif
};
#if defined(COMPONENT_BUILD)
ScopedChapsLoadFixup::ScopedChapsLoadFixup() {
// HACK: libchaps links the system protobuf and there are symbol conflicts
// with the bundled copy. Load chaps with RTLD_DEEPBIND to workaround.
chaps_handle_ = dlopen(kChapsPath, RTLD_LOCAL | RTLD_NOW | RTLD_DEEPBIND);
}
ScopedChapsLoadFixup::~ScopedChapsLoadFixup() {
// LoadNSSModule() will have taken a 2nd reference.
if (chaps_handle_)
dlclose(chaps_handle_);
}
#else
ScopedChapsLoadFixup::ScopedChapsLoadFixup() {}
ScopedChapsLoadFixup::~ScopedChapsLoadFixup() {}
#endif // defined(COMPONENT_BUILD)
class ChromeOSTokenManager {
public:
// Used with PostTaskAndReply to pass handles to worker thread and back.
@ -160,7 +124,7 @@ class ChromeOSTokenManager {
// the current thread, due to NSS's internal locking requirements
base::ThreadRestrictions::ScopedAllowIO allow_io;
base::FilePath nssdb_path = path.AppendASCII(".pki").AppendASCII("nssdb");
base::FilePath nssdb_path = GetSoftwareNSSDBPath(path);
if (!base::CreateDirectory(nssdb_path)) {
LOG(ERROR) << "Failed to create " << nssdb_path.value() << " directory.";
return ScopedPK11Slot();
@ -235,17 +199,7 @@ class ChromeOSTokenManager {
FROM_HERE, base::BlockingType::WILL_BLOCK);
if (!tpm_args->chaps_module) {
ScopedChapsLoadFixup chaps_loader;
DVLOG(3) << "Loading chaps...";
tpm_args->chaps_module = LoadNSSModule(
kChapsModuleName, kChapsPath,
// For more details on these parameters, see:
// https://developer.mozilla.org/en/PKCS11_Module_Specs
// slotFlags=[PublicCerts] -- Certificates and public keys can be
// read from this slot without requiring a call to C_Login.
// askpw=only -- Only authenticate to the token when necessary.
"NSS=\"slotParams=(0={slotFlags=[PublicCerts] askpw=only})\"");
tpm_args->chaps_module = LoadChaps();
}
if (tpm_args->chaps_module) {
tpm_args->tpm_slot =
@ -527,6 +481,11 @@ base::LazyInstance<ChromeOSTokenManager>::Leaky g_token_manager =
LAZY_INSTANCE_INITIALIZER;
} // namespace
base::FilePath GetSoftwareNSSDBPath(
const base::FilePath& profile_directory_path) {
return profile_directory_path.AppendASCII(".pki").AppendASCII("nssdb");
}
ScopedPK11Slot GetSystemNSSKeySlot(
base::OnceCallback<void(ScopedPK11Slot)> callback) {
return g_token_manager.Get().GetSystemNSSKeySlot(std::move(callback));
@ -605,13 +564,4 @@ void SetPrivateSoftwareSlotForChromeOSUserForTesting(ScopedPK11Slot slot) {
std::move(slot));
}
bool IsSlotProvidedByChaps(PK11SlotInfo* slot) {
if (!slot)
return false;
SECMODModule* pk11_module = PK11_GetModule(slot);
return pk11_module && base::StringPiece(pk11_module->commonName) ==
base::StringPiece(kChapsModuleName);
}
} // namespace crypto

@ -44,6 +44,11 @@ class CRYPTO_EXPORT AutoSECMODListReadLock {
};
#if BUILDFLAG(IS_CHROMEOS_ASH)
// Returns path to the NSS database file in the provided profile
// directory.
CRYPTO_EXPORT base::FilePath GetSoftwareNSSDBPath(
const base::FilePath& profile_directory_path);
// Returns a reference to the system-wide TPM slot if it is loaded. If it is not
// loaded and |callback| is non-null, the |callback| will be run once the slot
// is loaded.
@ -122,9 +127,6 @@ CRYPTO_EXPORT void CloseChromeOSUserForTesting(
CRYPTO_EXPORT void SetPrivateSoftwareSlotForChromeOSUserForTesting(
ScopedPK11Slot slot);
// Returns true if chaps is the module to which |slot| is attached.
CRYPTO_EXPORT bool IsSlotProvidedByChaps(PK11SlotInfo* slot);
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
// Loads the given module for this NSS session.

@ -32,6 +32,10 @@
#include "net/third_party/mozilla_security_manager/nsNSSCertificateDB.h"
#include "net/third_party/mozilla_security_manager/nsPKCS12Blob.h"
#if defined(OS_CHROMEOS) || BUILDFLAG(IS_LACROS)
#include "crypto/chaps_support.h"
#endif
// PSM = Mozilla's Personal Security Manager.
namespace psm = mozilla_security_manager;
@ -436,7 +440,7 @@ bool NSSCertDatabase::IsHardwareBacked(const CERTCertificate* cert) {
if (!slot || !PK11_IsHW(slot))
return false;
#if BUILDFLAG(IS_CHROMEOS_ASH)
#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_LACROS)
// Chaps announces PK11_IsHW(slot) for all slots. However, it is possible for
// a key in chaps to be not truly hardware-backed, either because it has been
// requested to be software-backed, or because the TPM does not support the