blob: f7c423d15d58a534bc7bae55413f71bd6894dc1e [file] [log] [blame]
// 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_impl.h"
#include "base/callback_forward.h"
#include "base/check.h"
#include "base/files/file_path.h"
#include "base/memory/weak_ptr.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.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 "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 "mojo/public/cpp/bindings/remote.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
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_;
};
// =============================================================================
CertDbInitializerImpl::CertDbInitializerImpl(Profile* profile)
: profile_(profile) {
DCHECK(chromeos::LacrosChromeServiceImpl::Get()
->IsAvailable<crosapi::mojom::CertDatabase>());
}
CertDbInitializerImpl::~CertDbInitializerImpl() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// In case the initialization didn't finish, notify waiting observers.
OnCertDbInitializationFinished(false);
}
void CertDbInitializerImpl::Start(signin::IdentityManager* identity_manager) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
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(&CertDbInitializerImpl::OnRefreshTokensLoaded,
weak_factory_.GetWeakPtr()));
return;
}
WaitForCertDbReady();
}
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::OnRefreshTokensLoaded() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
identity_manager_observer_.reset();
WaitForCertDbReady();
}
void CertDbInitializerImpl::WaitForCertDbReady() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!profile_->IsMainProfile()) {
OnCertDbInitializationFinished(false);
return;
}
chromeos::LacrosChromeServiceImpl::Get()
->GetRemote<crosapi::mojom::CertDatabase>()
->GetCertDatabaseInfo(
base::BindOnce(&CertDbInitializerImpl::OnCertDbInfoReceived,
weak_factory_.GetWeakPtr()));
}
void CertDbInitializerImpl::OnCertDbInfoReceived(
crosapi::mojom::GetCertDatabaseInfoResultPtr cert_db_info) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
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)),
base::BindOnce(&CertDbInitializerImpl::OnCertDbInitializationFinished,
weak_factory_.GetWeakPtr()));
}
void CertDbInitializerImpl::OnCertDbInitializationFinished(bool is_success) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
callbacks_.Notify(is_success);
is_ready_ = is_success;
}