blob: ccd640a814fab9bd9d74b87dc95d98396c59866d [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/chromeos/cert_provisioning/cert_provisioning_scheduler.h"
#include <memory>
#include <unordered_map>
#include "base/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/optional.h"
#include "base/time/time.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part.h"
#include "chrome/browser/chromeos/cert_provisioning/cert_provisioning_common.h"
#include "chrome/browser/chromeos/cert_provisioning/cert_provisioning_metrics.h"
#include "chrome/browser/chromeos/cert_provisioning/cert_provisioning_worker.h"
#include "chrome/browser/chromeos/platform_keys/platform_keys_service.h"
#include "chrome/browser/chromeos/platform_keys/platform_keys_service_factory.h"
#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
#include "chrome/browser/chromeos/policy/user_cloud_policy_manager_chromeos.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/common/pref_names.h"
#include "chromeos/network/network_handler.h"
#include "chromeos/network/network_state_handler.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_thread.h"
namespace chromeos {
namespace cert_provisioning {
namespace {
template <typename Container, typename Value>
void EraseKey(Container& container, const Value& value) {
auto iter = container.find(value);
if (iter == container.end()) {
return;
}
container.erase(iter);
}
const base::TimeDelta kInconsistentDataErrorRetryDelay =
base::TimeDelta::FromSeconds(30);
policy::CloudPolicyClient* GetCloudPolicyClientForDevice() {
policy::BrowserPolicyConnectorChromeOS* connector =
g_browser_process->platform_part()->browser_policy_connector_chromeos();
if (!connector) {
return nullptr;
}
policy::DeviceCloudPolicyManagerChromeOS* policy_manager =
connector->GetDeviceCloudPolicyManager();
if (!policy_manager) {
return nullptr;
}
policy::CloudPolicyCore* core = policy_manager->core();
if (!core) {
return nullptr;
}
return core->client();
}
policy::CloudPolicyClient* GetCloudPolicyClientForUser(Profile* profile) {
policy::UserCloudPolicyManagerChromeOS* user_cloud_policy_manager =
profile->GetUserCloudPolicyManagerChromeOS();
if (!user_cloud_policy_manager) {
return nullptr;
}
policy::CloudPolicyCore* core = user_cloud_policy_manager->core();
if (!core) {
return nullptr;
}
return core->client();
}
NetworkStateHandler* GetNetworkStateHandler() {
// Can happen in tests.
if (!NetworkHandler::IsInitialized()) {
return nullptr;
}
return NetworkHandler::Get()->network_state_handler();
}
} // namespace
// static
std::unique_ptr<CertProvisioningScheduler>
CertProvisioningScheduler::CreateUserCertProvisioningScheduler(
Profile* profile) {
PrefService* pref_service = profile->GetPrefs();
policy::CloudPolicyClient* cloud_policy_client =
GetCloudPolicyClientForUser(profile);
NetworkStateHandler* network_state_handler = GetNetworkStateHandler();
if (!profile || !pref_service || !cloud_policy_client ||
!network_state_handler) {
LOG(ERROR) << "Failed to create user certificate provisioning scheduler";
return nullptr;
}
return std::make_unique<CertProvisioningScheduler>(
CertScope::kUser, profile, pref_service,
prefs::kRequiredClientCertificateForUser, cloud_policy_client,
network_state_handler,
std::make_unique<CertProvisioningUserInvalidatorFactory>(profile));
}
// static
std::unique_ptr<CertProvisioningScheduler>
CertProvisioningScheduler::CreateDeviceCertProvisioningScheduler(
policy::AffiliatedInvalidationServiceProvider*
invalidation_service_provider) {
Profile* profile = ProfileHelper::GetSigninProfile();
PrefService* pref_service = g_browser_process->local_state();
policy::CloudPolicyClient* cloud_policy_client =
GetCloudPolicyClientForDevice();
NetworkStateHandler* network_state_handler = GetNetworkStateHandler();
if (!profile || !pref_service || !cloud_policy_client ||
!network_state_handler) {
LOG(ERROR) << "Failed to create device certificate provisioning scheduler";
return nullptr;
}
return std::make_unique<CertProvisioningScheduler>(
CertScope::kDevice, profile, pref_service,
prefs::kRequiredClientCertificateForDevice, cloud_policy_client,
network_state_handler,
std::make_unique<CertProvisioningDeviceInvalidatorFactory>(
invalidation_service_provider));
}
CertProvisioningScheduler::CertProvisioningScheduler(
CertScope cert_scope,
Profile* profile,
PrefService* pref_service,
const char* pref_name,
policy::CloudPolicyClient* cloud_policy_client,
NetworkStateHandler* network_state_handler,
std::unique_ptr<CertProvisioningInvalidatorFactory> invalidator_factory)
: cert_scope_(cert_scope),
profile_(profile),
pref_service_(pref_service),
pref_name_(pref_name),
cloud_policy_client_(cloud_policy_client),
network_state_handler_(network_state_handler),
invalidator_factory_(std::move(invalidator_factory)) {
CHECK(pref_service_);
CHECK(pref_name_);
CHECK(cloud_policy_client_);
CHECK(profile);
CHECK(invalidator_factory_);
platform_keys_service_ =
platform_keys::PlatformKeysServiceFactory::GetForBrowserContext(profile);
CHECK(platform_keys_service_);
network_state_handler_->AddObserver(this, FROM_HERE);
ScheduleInitialUpdate();
ScheduleDailyUpdate();
}
CertProvisioningScheduler::~CertProvisioningScheduler() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
network_state_handler_->RemoveObserver(this, FROM_HERE);
}
void CertProvisioningScheduler::ScheduleInitialUpdate() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::Bind(&CertProvisioningScheduler::InitialUpdateCerts,
weak_factory_.GetWeakPtr()));
}
void CertProvisioningScheduler::ScheduleDailyUpdate() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::Bind(&CertProvisioningScheduler::DailyUpdateCerts,
weak_factory_.GetWeakPtr()),
base::TimeDelta::FromDays(1));
}
void CertProvisioningScheduler::ScheduleRetry(const CertProfile& profile) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::Bind(&CertProvisioningScheduler::ProcessProfile,
weak_factory_.GetWeakPtr(), profile),
kInconsistentDataErrorRetryDelay);
}
void CertProvisioningScheduler::InitialUpdateCerts() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DeleteCertsWithoutPolicy();
}
void CertProvisioningScheduler::DeleteCertsWithoutPolicy() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
std::vector<CertProfile> profiles = GetCertProfiles();
std::set<std::string> cert_profile_ids_to_keep;
for (const auto& profile : profiles) {
cert_profile_ids_to_keep.insert(profile.profile_id);
}
cert_deleter_ = std::make_unique<CertProvisioningCertDeleter>();
cert_deleter_->DeleteCerts(
cert_scope_, platform_keys_service_, cert_profile_ids_to_keep,
base::BindOnce(&CertProvisioningScheduler::OnDeleteCertsWithoutPolicyDone,
weak_factory_.GetWeakPtr()));
}
void CertProvisioningScheduler::OnDeleteCertsWithoutPolicyDone(
const std::string& error_message) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
cert_deleter_.reset();
if (!error_message.empty()) {
LOG(ERROR) << "Failed to delete certificates without policies: "
<< error_message;
}
DeserializeWorkers();
CleanVaKeysIfIdle();
}
void CertProvisioningScheduler::CleanVaKeysIfIdle() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!workers_.empty()) {
OnCleanVaKeysIfIdleDone(true);
return;
}
DeleteVaKeysByPrefix(
cert_scope_, profile_, kKeyNamePrefix,
base::BindOnce(&CertProvisioningScheduler::OnCleanVaKeysIfIdleDone,
weak_factory_.GetWeakPtr()));
}
void CertProvisioningScheduler::OnCleanVaKeysIfIdleDone(
base::Optional<bool> delete_result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!delete_result.has_value() || !delete_result.value()) {
LOG(ERROR) << "Failed to delete keys while idle";
}
RegisterForPrefsChanges();
UpdateCerts();
}
void CertProvisioningScheduler::RegisterForPrefsChanges() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
pref_change_registrar_.Init(pref_service_);
pref_change_registrar_.Add(
pref_name_, base::BindRepeating(&CertProvisioningScheduler::OnPrefsChange,
weak_factory_.GetWeakPtr()));
}
void CertProvisioningScheduler::DailyUpdateCerts() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
failed_cert_profiles_.clear();
UpdateCerts();
ScheduleDailyUpdate();
}
void CertProvisioningScheduler::DeserializeWorkers() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const base::Value* saved_workers =
pref_service_->Get(GetPrefNameForSerialization(cert_scope_));
if (!saved_workers) {
return;
}
for (const auto& kv : saved_workers->DictItems()) {
const base::Value& saved_worker = kv.second;
std::unique_ptr<CertProvisioningWorker> worker =
CertProvisioningWorkerFactory::Get()->Deserialize(
cert_scope_, profile_, pref_service_, saved_worker,
cloud_policy_client_, invalidator_factory_->Create(),
base::BindOnce(&CertProvisioningScheduler::OnProfileFinished,
weak_factory_.GetWeakPtr()));
if (!worker) {
// Deserialization error message was already logged.
continue;
}
workers_[worker->GetCertProfile().profile_id] = std::move(worker);
}
}
void CertProvisioningScheduler::OnPrefsChange() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
UpdateCerts();
}
void CertProvisioningScheduler::UpdateOneCert(
const std::string& cert_profile_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
RecordEvent(cert_scope_, CertProvisioningEvent::kWorkerRetryManual);
if (!CheckInternetConnection()) {
return;
}
base::Optional<CertProfile> cert_profile = GetOneCertProfile(cert_profile_id);
if (!cert_profile) {
return;
}
ProcessProfile(cert_profile.value());
}
void CertProvisioningScheduler::UpdateCerts() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!CheckInternetConnection()) {
return;
}
if (certs_with_ids_getter_ && certs_with_ids_getter_->IsRunning()) {
// Another UpdateCerts was started recently and still gethering info about
// existing certs.
return;
}
certs_with_ids_getter_ =
std::make_unique<CertProvisioningCertsWithIdsGetter>();
certs_with_ids_getter_->GetCertsWithIds(
cert_scope_, platform_keys_service_,
base::BindOnce(&CertProvisioningScheduler::OnGetCertsWithIdsDone,
weak_factory_.GetWeakPtr()));
}
void CertProvisioningScheduler::OnGetCertsWithIdsDone(
std::map<std::string, scoped_refptr<net::X509Certificate>>
existing_certs_with_ids,
const std::string& error_message) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
certs_with_ids_getter_.reset();
if (!error_message.empty()) {
LOG(ERROR) << "Failed to get existing cert ids: " << error_message;
return;
}
std::vector<CertProfile> profiles = GetCertProfiles();
CancelWorkersWithoutPolicy(profiles);
for (const auto& profile : profiles) {
if (base::Contains(existing_certs_with_ids, profile.profile_id) ||
base::Contains(failed_cert_profiles_, profile.profile_id)) {
continue;
}
ProcessProfile(profile);
}
}
void CertProvisioningScheduler::ProcessProfile(
const CertProfile& cert_profile) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
CertProvisioningWorker* worker = FindWorker(cert_profile.profile_id);
if (!worker) {
CreateCertProvisioningWorker(cert_profile);
return;
}
if ((worker->GetCertProfile().policy_version !=
cert_profile.policy_version)) {
// The worker has outdated policy version. Make it stop, clean up current
// state and report back through its callback. That will trigger retry for
// its certificate profile.
worker->Stop(CertProvisioningWorkerState::kInconsistentDataError);
return;
}
if (worker->IsWaiting()) {
worker->DoStep();
return;
}
// There already is an active worker for this profile. No action required.
return;
}
void CertProvisioningScheduler::CreateCertProvisioningWorker(
CertProfile cert_profile) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
std::unique_ptr<CertProvisioningWorker> worker =
CertProvisioningWorkerFactory::Get()->Create(
cert_scope_, profile_, pref_service_, cert_profile,
cloud_policy_client_, invalidator_factory_->Create(),
base::BindOnce(&CertProvisioningScheduler::OnProfileFinished,
weak_factory_.GetWeakPtr()));
CertProvisioningWorker* worker_unowned = worker.get();
workers_[cert_profile.profile_id] = std::move(worker);
worker_unowned->DoStep();
}
void CertProvisioningScheduler::OnProfileFinished(
const CertProfile& profile,
CertProvisioningWorkerState state) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto worker_iter = workers_.find(profile.profile_id);
if (worker_iter == workers_.end()) {
NOTREACHED();
LOG(WARNING) << "Finished worker is not found";
return;
}
switch (state) {
case CertProvisioningWorkerState::kSucceeded:
VLOG(0) << "Successfully provisioned certificate for profile: "
<< profile.profile_id;
break;
case CertProvisioningWorkerState::kInconsistentDataError:
LOG(WARNING) << "Inconsistent data error for certificate profile: "
<< profile.profile_id;
ScheduleRetry(profile);
break;
case CertProvisioningWorkerState::kCanceled:
break;
default:
LOG(ERROR) << "Failed to process certificate profile: "
<< profile.profile_id;
UpdateFailedCertProfiles(*(worker_iter->second));
break;
}
workers_.erase(worker_iter);
}
CertProvisioningWorker* CertProvisioningScheduler::FindWorker(
CertProfileId profile_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto iter = workers_.find(profile_id);
if (iter == workers_.end()) {
return nullptr;
}
return iter->second.get();
}
base::Optional<CertProfile> CertProvisioningScheduler::GetOneCertProfile(
const std::string& cert_profile_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const base::Value* profile_list = pref_service_->Get(pref_name_);
if (!profile_list) {
LOG(WARNING) << "Preference is not found";
return {};
}
for (const base::Value& cur_profile : profile_list->GetList()) {
const std::string* id = cur_profile.FindStringKey(kCertProfileIdKey);
if (!id || (*id != cert_profile_id)) {
continue;
}
return CertProfile::MakeFromValue(cur_profile);
}
return base::nullopt;
}
std::vector<CertProfile> CertProvisioningScheduler::GetCertProfiles() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const base::Value* profile_list = pref_service_->Get(pref_name_);
if (!profile_list) {
LOG(WARNING) << "Preference is not found";
return {};
}
std::vector<CertProfile> result_profiles;
for (const base::Value& cur_profile : profile_list->GetList()) {
base::Optional<CertProfile> p = CertProfile::MakeFromValue(cur_profile);
if (!p) {
LOG(WARNING) << "Failed to parse certificate profile";
continue;
}
result_profiles.emplace_back(std::move(p.value()));
}
return result_profiles;
}
const WorkerMap& CertProvisioningScheduler::GetWorkers() const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
return workers_;
}
const std::map<std::string, FailedWorkerInfo>&
CertProvisioningScheduler::GetFailedCertProfileIds() const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
return failed_cert_profiles_;
}
bool CertProvisioningScheduler::CheckInternetConnection() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const NetworkState* network = network_state_handler_->DefaultNetwork();
bool is_online = network && network->IsOnline();
is_waiting_for_online_ = !is_online;
return is_online;
}
void CertProvisioningScheduler::OnNetworkChange(
const chromeos::NetworkState* network) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (is_waiting_for_online_ && network && network->IsOnline()) {
UpdateCerts();
}
}
void CertProvisioningScheduler::DefaultNetworkChanged(
const chromeos::NetworkState* network) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
OnNetworkChange(network);
}
void CertProvisioningScheduler::NetworkConnectionStateChanged(
const chromeos::NetworkState* network) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
OnNetworkChange(network);
}
void CertProvisioningScheduler::UpdateFailedCertProfiles(
const CertProvisioningWorker& worker) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
FailedWorkerInfo info;
info.state = worker.GetPreviousState();
info.public_key = worker.GetPublicKey();
failed_cert_profiles_[worker.GetCertProfile().profile_id] = std::move(info);
}
void CertProvisioningScheduler::CancelWorkersWithoutPolicy(
const std::vector<CertProfile>& profiles) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (workers_.empty()) {
return;
}
std::unordered_set<CertProfileId> cert_profile_ids;
for (const CertProfile& profile : profiles) {
cert_profile_ids.insert(profile.profile_id);
}
for (auto& kv : workers_) {
auto& worker_ptr = kv.second;
if (cert_profile_ids.find(worker_ptr->GetCertProfile().profile_id) ==
cert_profile_ids.end()) {
// This will trigger clean up (if any) in the worker and make it call its
// callback.
worker_ptr->Stop(CertProvisioningWorkerState::kCanceled);
}
}
}
} // namespace cert_provisioning
} // namespace chromeos