blob: 5b3e89983b2825bc9456fddffe7f9375f2c135ac [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 "base/bind.h"
#include "base/json/json_reader.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/values_test_util.h"
#include "base/time/time.h"
#include "chrome/browser/chromeos/cert_provisioning/cert_provisioning_common.h"
#include "chrome/browser/chromeos/cert_provisioning/cert_provisioning_test_helpers.h"
#include "chrome/browser/chromeos/cert_provisioning/cert_provisioning_worker.h"
#include "chrome/browser/chromeos/cert_provisioning/mock_cert_provisioning_worker.h"
#include "chrome/browser/chromeos/platform_keys/mock_platform_keys_service.h"
#include "chrome/browser/chromeos/platform_keys/platform_keys_service.h"
#include "chrome/browser/chromeos/platform_keys/platform_keys_service_factory.h"
#include "chrome/common/pref_names.h"
#include "chromeos/network/network_state_test_helper.h"
#include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
#include "components/prefs/testing_pref_service.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
using base::TimeDelta;
using base::test::ParseJson;
using base::test::RunOnceCallback;
using testing::_;
using testing::AtLeast;
using testing::ByMove;
using testing::Exactly;
using testing::Invoke;
using testing::Mock;
using testing::Return;
using testing::ReturnRef;
using testing::StrictMock;
namespace chromeos {
namespace cert_provisioning {
namespace {
const char kWifiServiceGuid[] = "wifi_guid";
//================ CertificateTestHelper =======================================
// Generated by chrome/test/data/policy/test_certs/create_test_certs.sh
const char kFakeCertificate[] = R"(-----BEGIN CERTIFICATE-----
MIIDJzCCAg+gAwIBAgIBATANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxyb290
X2NhX2NlcnQwHhcNMjAwMjI1MTUyNTU2WhcNMzAwMjIyMTUyNTU2WjAUMRIwEAYD
VQQDDAkxMjcuMC4wLjEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDW
druvpaJovmyWzIcjtsSk/lp319+zNPSYGLzJzTeEmnFoDf/b89ft6xR1NIahmvVd
UHGOMlzgDKnNkqWw+pgpn6U8dk+leWnwlUefzDz7OY8qXfX29Vh0m/kATQc64lnp
rX19fEi2DOgH6heCQDSaHI/KAnAXccwl8kdGuTEnvdzbdHqQq8pPGpEqzC/NOjk7
kDNkUt0J74ZVMm4+jhVOgZ35mFLtC+xjfycBgbnt8yfPOzmOMwXTjYDPNaIy32AZ
t66oIToteoW5Ilg+j5Mto3unBDHrw8rml3+W/nwHuOPEIgBqLQFfWtXpuX8CbcS6
SFNK4hxCJOvlzUbgTpsrAgMBAAGjgYAwfjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQW
BBRDEl1/2pL5LtKnpIly+XCj3N6MwDAfBgNVHSMEGDAWgBQrwVEnUQZlX850A2N+
URfS8BxoyzAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDwYDVR0RBAgw
BocEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEAXZd+Ul7GUFZPLSiTZ618hUI2UdO0
7rtPwBw3TephWuyEeHht+WhzA3sRL3nprEiJqIg5w/Tlfz4dsObpSU3vKmDhLzAx
HJrN5vKdbEj9wyuhYSRJwvJka1ZOgPzhQcDQOp1SqonNxLx/sSMDR2UIDMBGzrkQ
sDkn58N5eWm+hZADOAKROHR47j85VcsmYGK7z2x479YzsyWyOm0dbACXv7/HvFkz
56KvgxRaPZQzQUg5yuXa21IjQz07wyWSYnHpm2duAbYFl6CTR9Rlj5vpRkKsQP1W
mMhGDBfgEskdbM+0agsZrJupoQMBUbD5gflcJlW3kwlboi3dTtiGixfYWw==
-----END CERTIFICATE-----)";
struct CertificateTestHelper {
public:
CertificateTestHelper() {
cert = CreateSingleCertificateFromBytes(kFakeCertificate,
sizeof(kFakeCertificate));
DCHECK(cert);
}
void GetCertificates(const std::string& token_id,
const platform_keys::GetCertificatesCallback& callback) {
auto result = std::make_unique<net::CertificateList>();
*result = cert_list;
std::move(callback).Run(std::move(result), "");
}
void AddCert() {
DCHECK(cert_list.empty())
<< "Current implementation supports only one certificate";
cert_list.push_back(cert);
}
scoped_refptr<net::X509Certificate> GetCert() const { return cert; }
std::string GetPublicKeyForCert() const {
return platform_keys::GetSubjectPublicKeyInfo(cert);
}
private:
scoped_refptr<net::X509Certificate> cert;
net::CertificateList cert_list;
};
//================ CertProvisioningSchedulerTest ===============================
class CertProvisioningSchedulerTest : public testing::Test {
public:
CertProvisioningSchedulerTest()
: network_state_test_helper_(/*use_default_devices_and_services=*/false) {
Init();
}
CertProvisioningSchedulerTest(const CertProvisioningSchedulerTest&) = delete;
CertProvisioningSchedulerTest& operator=(
const CertProvisioningSchedulerTest&) = delete;
~CertProvisioningSchedulerTest() override = default;
protected:
void Init() {
RegisterProfilePrefs(pref_service_.registry());
RegisterLocalStatePrefs(pref_service_.registry());
platform_keys_service_ =
static_cast<platform_keys::MockPlatformKeysService*>(
platform_keys::PlatformKeysServiceFactory::GetInstance()
->SetTestingFactoryAndUse(
GetProfile(),
base::BindRepeating(
&platform_keys::BuildMockPlatformKeysService)));
ASSERT_TRUE(platform_keys_service_);
AddOnlineWifiNetwork();
}
void FastForwardBy(base::TimeDelta delta) {
task_environment_.FastForwardBy(delta);
}
void SetUp() override {
CertProvisioningWorkerFactory::SetFactoryForTesting(&mock_factory_);
EXPECT_CALL(*platform_keys_service_, GetCertificates)
.WillRepeatedly(Invoke(&certificate_helper_,
&CertificateTestHelper::GetCertificates));
}
void TearDown() override {
CertProvisioningWorkerFactory::SetFactoryForTesting(nullptr);
}
void AddOnlineWifiNetwork() {
ASSERT_TRUE(wifi_network_service_path_.empty());
std::stringstream ss;
ss << "{"
<< " \"GUID\": \"" << kWifiServiceGuid << "\","
<< " \"Type\": \"" << shill::kTypeWifi << "\","
<< " \"State\": \"" << shill::kStateOnline << "\""
<< "}";
wifi_network_service_path_ =
network_state_test_helper_.ConfigureService(ss.str());
}
// |network_state| is a shill network state, e.g. "shill::kStateIdle".
void SetWifiNetworkState(std::string network_state) {
network_state_test_helper_.SetServiceProperty(wifi_network_service_path_,
shill::kStateProperty,
base::Value(network_state));
base::RunLoop().RunUntilIdle();
}
Profile* GetProfile() { return profile_helper_for_testing_.GetProfile(); }
std::unique_ptr<MockCertProvisioningInvalidatorFactory>
MakeFakeInvalidationFactory() {
auto result = std::make_unique<MockCertProvisioningInvalidatorFactory>();
// Return nullptr from every Create. The result will be passed to mock
// worker, so it does not need to be non-null.
result->ExpectCreateReturnNull();
return result;
}
content::BrowserTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
ProfileHelperForTesting profile_helper_for_testing_;
platform_keys::MockPlatformKeysService* platform_keys_service_ = nullptr;
StrictMock<SpyingFakeCryptohomeClient> fake_cryptohome_client_;
TestingPrefServiceSimple pref_service_;
policy::MockCloudPolicyClient cloud_policy_client_;
// Only expected creations are allowed.
StrictMock<MockCertProvisioningWorkerFactory> mock_factory_;
CertificateTestHelper certificate_helper_;
NetworkStateTestHelper network_state_test_helper_;
std::string wifi_network_service_path_;
};
TEST_F(CertProvisioningSchedulerTest, Success) {
CertScope cert_scope = CertScope::kUser;
auto mock_invalidation_factory_obj =
std::make_unique<MockCertProvisioningInvalidatorFactory>();
MockCertProvisioningInvalidatorFactory* mock_invalidation_factory =
mock_invalidation_factory_obj.get();
CertProvisioningScheduler scheduler(
cert_scope, GetProfile(), &pref_service_,
prefs::kRequiredClientCertificateForUser, &cloud_policy_client_,
network_state_test_helper_.network_state_handler(),
std::move(mock_invalidation_factory_obj));
// From CertProvisioningScheduler::CleanVaKeysIfIdle.
EXPECT_CALL(fake_cryptohome_client_,
OnTpmAttestationDeleteKeysByPrefix(
attestation::AttestationKeyType::KEY_USER, kKeyNamePrefix))
.Times(1);
// The policy is empty, so no workers should be created yet.
FastForwardBy(base::TimeDelta::FromSeconds(1));
EXPECT_EQ(scheduler.GetWorkers().size(), 0U);
EXPECT_CALL(*mock_invalidation_factory, Create)
.Times(1)
.WillOnce(
Return(ByMove(nullptr))); // nullptr is good enough for mock worker.
// One worker will be created on prefs update.
const char kCertProfileId[] = "cert_profile_id_1";
const char kCertProfileVersion[] = "cert_profile_version_1";
CertProfile cert_profile{kCertProfileId, kCertProfileVersion};
MockCertProvisioningWorker* worker =
mock_factory_.ExpectCreateReturnMock(cert_scope, cert_profile);
worker->SetExpectations(/*do_step_times=*/AtLeast(1),
/*is_waiting=*/false, cert_profile);
// Add 1 certificate profile to the policy ("cert_profile_id" is the same as
// above).
base::Value config = ParseJson(
R"([{"name": "Certificate Profile 1",
"cert_profile_id":"cert_profile_id_1",
"policy_version":"cert_profile_version_1",
"key_algorithm":"rsa",
"renewal_period_seconds": 365000}])");
pref_service_.Set(prefs::kRequiredClientCertificateForUser, config);
EXPECT_EQ(scheduler.GetWorkers().size(), 1U);
// Emulate callback from the worker.
scheduler.OnProfileFinished(cert_profile,
CertProvisioningWorkerState::kSucceed);
// Finished worker should be deleted.
EXPECT_EQ(scheduler.GetWorkers().size(), 0U);
EXPECT_TRUE(scheduler.GetFailedCertProfileIds().empty());
certificate_helper_.AddCert();
EXPECT_CALL(
*platform_keys_service_,
GetAttributeForKey(
GetPlatformKeysTokenId(cert_scope),
certificate_helper_.GetPublicKeyForCert(),
platform_keys::KeyAttributeType::CertificateProvisioningId, _))
.Times(1)
.WillOnce(base::test::RunOnceCallback<3>(kCertProfileId, ""));
// Check one more time that scheduler doesn't create new workers for
// finished certificate profiles (the factory will fail on an attempt to
// do so).
scheduler.UpdateCerts();
FastForwardBy(base::TimeDelta::FromSeconds(100));
}
TEST_F(CertProvisioningSchedulerTest, WorkerFailed) {
CertScope cert_scope = CertScope::kDevice;
CertProvisioningScheduler scheduler(
cert_scope, GetProfile(), &pref_service_,
prefs::kRequiredClientCertificateForDevice, &cloud_policy_client_,
network_state_test_helper_.network_state_handler(),
MakeFakeInvalidationFactory());
// From CertProvisioningScheduler::CleanVaKeysIfIdle.
EXPECT_CALL(fake_cryptohome_client_,
OnTpmAttestationDeleteKeysByPrefix(
attestation::AttestationKeyType::KEY_DEVICE, kKeyNamePrefix))
.Times(1);
// The policy is empty, so no workers should be created yet.
FastForwardBy(base::TimeDelta::FromSeconds(1));
EXPECT_EQ(scheduler.GetWorkers().size(), 0U);
// One worker will be created on prefs update.
const char kCertProfileId[] = "cert_profile_id_1";
const char kCertProfileVersion[] = "cert_profile_version_1";
CertProfile cert_profile{kCertProfileId, kCertProfileVersion};
MockCertProvisioningWorker* worker =
mock_factory_.ExpectCreateReturnMock(cert_scope, cert_profile);
worker->SetExpectations(/*do_step_times=*/AtLeast(1),
/*is_waiting=*/false, cert_profile);
// Add 1 certificate profile to the policy ("cert_profile_id" is the same as
// above).
base::Value config = ParseJson(
R"([{"name": "Certificate Profile 1",
"cert_profile_id":"cert_profile_id_1",
"policy_version":"cert_profile_version_1",
"key_algorithm":"rsa",
"renewal_period_seconds": 365000}])");
pref_service_.Set(prefs::kRequiredClientCertificateForDevice, config);
// Now 1 worker should be created.
EXPECT_EQ(scheduler.GetWorkers().size(), 1U);
// Emulate callback from the worker.
scheduler.OnProfileFinished(cert_profile,
CertProvisioningWorkerState::kFailed);
// Failed worker should be deleted, failed profile ID is saved, no new
// workers should be created.
EXPECT_EQ(scheduler.GetWorkers().size(), 0U);
EXPECT_TRUE(
base::Contains(scheduler.GetFailedCertProfileIds(), kCertProfileId));
certificate_helper_.AddCert();
EXPECT_CALL(
*platform_keys_service_,
GetAttributeForKey(
GetPlatformKeysTokenId(cert_scope),
certificate_helper_.GetPublicKeyForCert(),
platform_keys::KeyAttributeType::CertificateProvisioningId, _))
.Times(1)
.WillOnce(base::test::RunOnceCallback<3>(kCertProfileId, ""));
// Check one more time that scheduler doesn't create new workers for failed
// certificate profiles (the factory will fail on an attempt to do so).
scheduler.UpdateCerts();
}
TEST_F(CertProvisioningSchedulerTest, InitialAndDailyUpdates) {
CertScope cert_scope = CertScope::kUser;
// Add 1 certificate profile to the policy.
base::Value config = ParseJson(
R"([{"name": "Certificate Profile 1",
"cert_profile_id":"cert_profile_id_1",
"policy_version":"cert_profile_version_1",
"key_algorithm":"rsa",
"renewal_period_seconds": 365000}])");
pref_service_.Set(prefs::kRequiredClientCertificateForUser, config);
// Same as in the policy.
const char kCertProfileId[] = "cert_profile_id_1";
const char kCertProfileVersion[] = "cert_profile_version_1";
CertProfile cert_profile{kCertProfileId, kCertProfileVersion};
CertProvisioningScheduler scheduler(
cert_scope, GetProfile(), &pref_service_,
prefs::kRequiredClientCertificateForUser, &cloud_policy_client_,
network_state_test_helper_.network_state_handler(),
MakeFakeInvalidationFactory());
// From CertProvisioningScheduler::CleanVaKeysIfIdle.
EXPECT_CALL(fake_cryptohome_client_,
OnTpmAttestationDeleteKeysByPrefix(
attestation::AttestationKeyType::KEY_USER, kKeyNamePrefix))
.Times(1);
// Now one worker should be created.
MockCertProvisioningWorker* worker =
mock_factory_.ExpectCreateReturnMock(cert_scope, cert_profile);
worker->SetExpectations(/*do_step_times=*/AtLeast(1), /*is_waiting=*/false,
cert_profile);
FastForwardBy(base::TimeDelta::FromSeconds(1));
ASSERT_EQ(scheduler.GetWorkers().size(), 1U);
// Emulate callback from the worker.
CertProfile profile{kCertProfileId, kCertProfileVersion};
scheduler.OnProfileFinished(profile, CertProvisioningWorkerState::kFailed);
ASSERT_EQ(scheduler.GetWorkers().size(), 0U);
EXPECT_TRUE(
base::Contains(scheduler.GetFailedCertProfileIds(), kCertProfileId));
// No workers should be created yet.
FastForwardBy(base::TimeDelta::FromHours(20));
ASSERT_EQ(scheduler.GetWorkers().size(), 0U);
// Now list of failed profiles should be cleared that will cause a new attempt
// to provision certificate.
MockCertProvisioningWorker* worker2 =
mock_factory_.ExpectCreateReturnMock(cert_scope, cert_profile);
worker2->SetExpectations(/*do_step_times=*/AtLeast(1), /*is_waiting=*/false,
cert_profile);
FastForwardBy(base::TimeDelta::FromHours(5));
ASSERT_EQ(scheduler.GetWorkers().size(), 1U);
// Emulate callback from the worker.
scheduler.OnProfileFinished(profile, CertProvisioningWorkerState::kSucceed);
ASSERT_EQ(scheduler.GetWorkers().size(), 0U);
EXPECT_TRUE(scheduler.GetFailedCertProfileIds().empty());
}
TEST_F(CertProvisioningSchedulerTest, MultipleWorkers) {
CertScope cert_scope = CertScope::kDevice;
CertProvisioningScheduler scheduler(
cert_scope, GetProfile(), &pref_service_,
prefs::kRequiredClientCertificateForUser, &cloud_policy_client_,
network_state_test_helper_.network_state_handler(),
MakeFakeInvalidationFactory());
// From CertProvisioningScheduler::CleanVaKeysIfIdle.
EXPECT_CALL(fake_cryptohome_client_,
OnTpmAttestationDeleteKeysByPrefix(
attestation::AttestationKeyType::KEY_DEVICE, kKeyNamePrefix))
.Times(1);
// The policy is empty, so no workers should be created yet.
FastForwardBy(base::TimeDelta::FromSeconds(1));
ASSERT_EQ(scheduler.GetWorkers().size(), 0U);
// New workers will be created on prefs update.
const char kCertProfileId0[] = "cert_profile_id_0";
const char kCertProfileVersion0[] = "cert_profile_version_0";
CertProfile cert_profile0{kCertProfileId0, kCertProfileVersion0};
const char kCertProfileId1[] = "cert_profile_id_1";
const char kCertProfileVersion1[] = "cert_profile_version_1";
CertProfile cert_profile1{kCertProfileId1, kCertProfileVersion1};
const char kCertProfileId2[] = "cert_profile_id_2";
const char kCertProfileVersion2[] = "cert_profile_version_2";
CertProfile cert_profile2{kCertProfileId2, kCertProfileVersion2};
MockCertProvisioningWorker* worker0 =
mock_factory_.ExpectCreateReturnMock(cert_scope, cert_profile0);
worker0->SetExpectations(/*do_step_times=*/AtLeast(1), /*is_waiting=*/false,
cert_profile0);
MockCertProvisioningWorker* worker1 =
mock_factory_.ExpectCreateReturnMock(cert_scope, cert_profile1);
worker1->SetExpectations(/*do_step_times=*/AtLeast(1), /*is_waiting=*/false,
cert_profile1);
MockCertProvisioningWorker* worker2 =
mock_factory_.ExpectCreateReturnMock(cert_scope, cert_profile2);
worker2->SetExpectations(/*do_step_times=*/AtLeast(1), /*is_waiting=*/false,
cert_profile2);
// Add 3 certificate profiles to the policy ("cert_profile_id"s are the same
// as above).
base::Value config = ParseJson(
R"([{
"name": "Certificate Profile 0",
"cert_profile_id":"cert_profile_id_0",
"policy_version":"cert_profile_version_0",
"key_algorithm":"rsa",
"renewal_period_seconds": 365000
},
{
"name": "Certificate Profile 1",
"cert_profile_id":"cert_profile_id_1",
"policy_version":"cert_profile_version_1",
"key_algorithm":"rsa",
"renewal_period_seconds": 365000
},
{
"name": "Certificate Profile 2",
"cert_profile_id":"cert_profile_id_2",
"policy_version":"cert_profile_version_2",
"key_algorithm":"rsa",
"renewal_period_seconds": 365000
}])");
pref_service_.Set(prefs::kRequiredClientCertificateForUser, config);
// Now one worker for each profile should be created.
ASSERT_EQ(scheduler.GetWorkers().size(), 3U);
// worker0 successfully finished. Should be just deleted.
scheduler.OnProfileFinished(cert_profile0,
CertProvisioningWorkerState::kSucceed);
// worker1 is waiting. Should be continued.
worker1->SetExpectations(/*do_step_times=*/AtLeast(1), /*is_waiting=*/true,
cert_profile1);
// worker2 failed. Should be deleted and the profile id should be saved.
scheduler.OnProfileFinished(cert_profile2,
CertProvisioningWorkerState::kFailed);
EXPECT_EQ(scheduler.GetWorkers().size(), 1U);
EXPECT_TRUE(
base::Contains(scheduler.GetFailedCertProfileIds(), kCertProfileId2));
certificate_helper_.AddCert();
EXPECT_CALL(
*platform_keys_service_,
GetAttributeForKey(
GetPlatformKeysTokenId(cert_scope),
certificate_helper_.GetPublicKeyForCert(),
platform_keys::KeyAttributeType::CertificateProvisioningId, _))
.Times(1)
.WillOnce(base::test::RunOnceCallback<3>(kCertProfileId0, ""));
// Make scheduler check workers state.
scheduler.UpdateCerts();
EXPECT_EQ(scheduler.GetWorkers().size(), 1U);
EXPECT_TRUE(
base::Contains(scheduler.GetFailedCertProfileIds(), kCertProfileId2));
EXPECT_CALL(
*platform_keys_service_,
GetAttributeForKey(
GetPlatformKeysTokenId(cert_scope),
certificate_helper_.GetPublicKeyForCert(),
platform_keys::KeyAttributeType::CertificateProvisioningId, _))
.Times(1)
.WillOnce(base::test::RunOnceCallback<3>(kCertProfileId0, ""));
// Check one more time that scheduler doesn't create new workers for failed
// certificate profiles (the factory will fail on an attempt to do so).
scheduler.UpdateCerts();
EXPECT_EQ(scheduler.GetWorkers().size(), 1U);
}
TEST_F(CertProvisioningSchedulerTest, RemoveCertWithoutPolicy) {
CertScope cert_scope = CertScope::kDevice;
const char kCertProfileId[] = "cert_profile_id_1";
certificate_helper_.AddCert();
EXPECT_CALL(*platform_keys_service_,
GetAttributeForKey(
GetPlatformKeysTokenId(cert_scope),
certificate_helper_.GetPublicKeyForCert(),
platform_keys::KeyAttributeType::CertificateProvisioningId,
/*callback=*/_))
.Times(1)
.WillOnce(base::test::RunOnceCallback<3>(kCertProfileId, ""));
CertProvisioningScheduler scheduler(
cert_scope, GetProfile(), &pref_service_,
prefs::kRequiredClientCertificateForDevice, &cloud_policy_client_,
network_state_test_helper_.network_state_handler(),
MakeFakeInvalidationFactory());
EXPECT_CALL(*platform_keys_service_,
RemoveCertificate(GetPlatformKeysTokenId(cert_scope),
/*certificate=*/certificate_helper_.GetCert(),
/*callback=*/_))
.Times(1);
FastForwardBy(base::TimeDelta::FromSeconds(1));
}
TEST_F(CertProvisioningSchedulerTest, DeserializeWorkers) {
CertScope cert_scope = CertScope::kUser;
const char kCertProfileId[] = "cert_profile_id_1";
const char kCertProfileVersion[] = "cert_profile_version_1";
CertProfile cert_profile{kCertProfileId, kCertProfileVersion};
// Add 1 certificate profile to the policy.
base::Value cert_profiles = ParseJson(
R"([{"name": "Certificate Profile 1",
"cert_profile_id":"cert_profile_id_1",
"policy_version": "cert_profile_version_1",
"key_algorithm":"rsa",
"renewal_period_seconds": 365000}])");
pref_service_.Set(prefs::kRequiredClientCertificateForUser, cert_profiles);
// Add 1 serialized worker for the profile.
base::Value saved_worker = ParseJson(
R"({
"cert_profile": {
"policy_version": "cert_profile_version_1",
"profile_id": "cert_profile_1"
},
"cert_scope": 0,
"invalidation_topic": "",
"public_key": "fake_public_key_1",
"state": 1
})");
base::Value all_saved_workers(base::Value::Type::DICTIONARY);
all_saved_workers.SetKey("cert_profile_1", saved_worker.Clone());
pref_service_.Set(prefs::kCertificateProvisioningStateForUser,
all_saved_workers);
MockCertProvisioningWorker* worker =
mock_factory_.ExpectDeserializeReturnMock(cert_scope, saved_worker);
// is_waiting==true should be set by Serializer so Scheduler knows that the
// worker has to be continued manually.
worker->SetExpectations(/*do_step_times=*/AtLeast(1),
/*is_waiting=*/true, cert_profile);
CertProvisioningScheduler scheduler(
cert_scope, GetProfile(), &pref_service_,
prefs::kRequiredClientCertificateForUser, &cloud_policy_client_,
network_state_test_helper_.network_state_handler(),
MakeFakeInvalidationFactory());
// Now one worker should be created.
FastForwardBy(base::TimeDelta::FromSeconds(1));
ASSERT_EQ(scheduler.GetWorkers().size(), 1U);
}
TEST_F(CertProvisioningSchedulerTest, InconsistentDataErrorHandling) {
CertScope cert_scope = CertScope::kDevice;
const char kCertProfileId[] = "cert_profile_id_1";
const char kCertProfileVersion1[] = "cert_profile_version_1";
const char kCertProfileVersion2[] = "cert_profile_version_2";
const char kCertProfileVersion3[] = "cert_profile_version_3";
CertProvisioningScheduler scheduler(
cert_scope, GetProfile(), &pref_service_,
prefs::kRequiredClientCertificateForDevice, &cloud_policy_client_,
network_state_test_helper_.network_state_handler(),
MakeFakeInvalidationFactory());
// From CertProvisioningScheduler::CleanVaKeysIfIdle.
EXPECT_CALL(fake_cryptohome_client_,
OnTpmAttestationDeleteKeysByPrefix(
attestation::AttestationKeyType::KEY_DEVICE, kKeyNamePrefix))
.Times(1);
// The policy is empty, so no workers should be created yet.
FastForwardBy(base::TimeDelta::FromSeconds(1));
EXPECT_EQ(scheduler.GetWorkers().size(), 0U);
CertProfile cert_profile_v1{kCertProfileId, kCertProfileVersion1};
MockCertProvisioningWorker* worker =
mock_factory_.ExpectCreateReturnMock(cert_scope, cert_profile_v1);
worker->SetExpectations(/*do_step_times=*/AtLeast(1), /*is_waiting=*/false,
cert_profile_v1);
// Add 1 certificate profile to the policy.
base::Value config = ParseJson(
R"([{"name": "Certificate Profile 1",
"cert_profile_id":"cert_profile_id_1",
"policy_version":"cert_profile_version_1",
"key_algorithm":"rsa",
"renewal_period_seconds": 365000}])");
pref_service_.Set(prefs::kRequiredClientCertificateForDevice, config);
// Now 1 worker should be created.
EXPECT_EQ(scheduler.GetWorkers().size(), 1U);
// Emulate callback from the worker.
scheduler.OnProfileFinished(
cert_profile_v1, CertProvisioningWorkerState::kInconsistentDataError);
// Failed worker should be deleted, failed profile ID should not be saved, no
// new workers should be created.
EXPECT_EQ(scheduler.GetWorkers().size(), 0U);
EXPECT_TRUE(scheduler.GetFailedCertProfileIds().empty());
// Add a new worker to the factory.
worker = mock_factory_.ExpectCreateReturnMock(cert_scope, cert_profile_v1);
worker->SetExpectations(/*do_step_times=*/AtLeast(1), /*is_waiting=*/false,
cert_profile_v1);
// After some delay a new worker should be created to try again.
FastForwardBy(base::TimeDelta::FromSeconds(31));
EXPECT_EQ(scheduler.GetWorkers().size(), 1U);
// Emulate callback from the worker.
scheduler.OnProfileFinished(
cert_profile_v1, CertProvisioningWorkerState::kInconsistentDataError);
// Failed worker should be deleted, failed profile ID should not be saved, no
// new workers should be created.
EXPECT_EQ(scheduler.GetWorkers().size(), 0U);
EXPECT_TRUE(scheduler.GetFailedCertProfileIds().empty());
// Add a new worker to the factory.
CertProfile cert_profile_v2{kCertProfileId, kCertProfileVersion2};
worker = mock_factory_.ExpectCreateReturnMock(cert_scope, cert_profile_v2);
worker->SetExpectations(/*do_step_times=*/AtLeast(1), /*is_waiting=*/false,
cert_profile_v2);
// On policy update a new worker should be created to try again.
config = ParseJson(
R"([{"name": "Certificate Profile 1",
"cert_profile_id":"cert_profile_id_1",
"policy_version":"cert_profile_version_2",
"key_algorithm":"rsa",
"renewal_period_seconds": 365000}])");
pref_service_.Set(prefs::kRequiredClientCertificateForDevice, config);
EXPECT_EQ(scheduler.GetWorkers().size(), 1U);
// If another update happens, workers with matching policy versions should not
// be deleted.
scheduler.UpdateCerts();
EXPECT_EQ(scheduler.GetWorkers().size(), 1U);
// Add a new worker to the factory.
CertProfile cert_profile_v3{kCertProfileId, kCertProfileVersion3};
worker = mock_factory_.ExpectCreateReturnMock(cert_scope, cert_profile_v3);
worker->SetExpectations(/*do_step_times=*/AtLeast(1), /*is_waiting=*/false,
cert_profile_v3);
// On policy update if existing profile has changed its policy_version,
// scheduler should recreate the worker for it.
config = ParseJson(
R"([{"name": "Certificate Profile 1",
"cert_profile_id":"cert_profile_id_1",
"policy_version":"cert_profile_version_3",
"key_algorithm":"rsa",
"renewal_period_seconds": 365000}])");
pref_service_.Set(prefs::kRequiredClientCertificateForDevice, config);
EXPECT_EQ(scheduler.GetWorkers().size(), 1U);
}
TEST_F(CertProvisioningSchedulerTest, RetryAfterNoInternetConnection) {
CertScope cert_scope = CertScope::kDevice;
SetWifiNetworkState(shill::kStateIdle);
// Add 1 certificate profile to the policy.
base::Value config = ParseJson(
R"([{"name": "Certificate Profile 1",
"cert_profile_id":"cert_profile_id_1",
"policy_version":"cert_profile_version_1",
"key_algorithm":"rsa",
"renewal_period_seconds": 365000}])");
pref_service_.Set(prefs::kRequiredClientCertificateForDevice, config);
// Same as in the policy.
const char kCertProfileId[] = "cert_profile_id_1";
const char kCertProfileVersion[] = "cert_profile_version_1";
CertProfile cert_profile{kCertProfileId, kCertProfileVersion};
CertProvisioningScheduler scheduler(
cert_scope, GetProfile(), &pref_service_,
prefs::kRequiredClientCertificateForDevice, &cloud_policy_client_,
network_state_test_helper_.network_state_handler(),
MakeFakeInvalidationFactory());
// From CertProvisioningScheduler::CleanVaKeysIfIdle.
EXPECT_CALL(fake_cryptohome_client_,
OnTpmAttestationDeleteKeysByPrefix(
attestation::AttestationKeyType::KEY_DEVICE, kKeyNamePrefix))
.Times(1);
FastForwardBy(base::TimeDelta::FromHours(72));
ASSERT_EQ(scheduler.GetWorkers().size(), 0U);
// Add a new worker to the factory.
MockCertProvisioningWorker* worker =
mock_factory_.ExpectCreateReturnMock(cert_scope, cert_profile);
worker->SetExpectations(/*do_step_times=*/AtLeast(1), /*is_waiting=*/false,
cert_profile);
SetWifiNetworkState(shill::kStateOnline);
ASSERT_EQ(scheduler.GetWorkers().size(), 1U);
}
TEST_F(CertProvisioningSchedulerTest, DeleteWorkerWithoutPolicy) {
CertScope cert_scope = CertScope::kDevice;
// Add 1 certificate profile to the policy.
base::Value config = ParseJson(
R"([{"name": "Certificate Profile 1",
"cert_profile_id":"cert_profile_id_1",
"policy_version":"cert_profile_version_1",
"key_algorithm":"rsa",
"renewal_period_seconds": 365000}])");
// Same as in the policy.
const char kCertProfileId[] = "cert_profile_id_1";
const char kCertProfileVersion[] = "cert_profile_version_1";
CertProfile cert_profile{kCertProfileId, kCertProfileVersion};
CertProvisioningScheduler scheduler(
cert_scope, GetProfile(), &pref_service_,
prefs::kRequiredClientCertificateForDevice, &cloud_policy_client_,
network_state_test_helper_.network_state_handler(),
MakeFakeInvalidationFactory());
// From CertProvisioningScheduler::CleanVaKeysIfIdle.
EXPECT_CALL(fake_cryptohome_client_,
OnTpmAttestationDeleteKeysByPrefix(
attestation::AttestationKeyType::KEY_DEVICE, kKeyNamePrefix))
.Times(1);
// Add a new worker to the factory.
MockCertProvisioningWorker* worker =
mock_factory_.ExpectCreateReturnMock(cert_scope, cert_profile);
worker->SetExpectations(/*do_step_times=*/AtLeast(1), /*is_waiting=*/false,
cert_profile);
// Prefs update will be ignored because initialization task has not finished
// yet.
pref_service_.Set(prefs::kRequiredClientCertificateForDevice, config);
ASSERT_EQ(scheduler.GetWorkers().size(), 0U);
FastForwardBy(base::TimeDelta::FromSeconds(1));
EXPECT_EQ(scheduler.GetWorkers().size(), 1U);
EXPECT_CALL(*worker, Cancel);
config = ParseJson("[]");
pref_service_.Set(prefs::kRequiredClientCertificateForDevice, config);
ASSERT_EQ(scheduler.GetWorkers().size(), 0U);
}
TEST_F(CertProvisioningSchedulerTest, DeleteVaKeysOnIdle) {
CertScope cert_scope = CertScope::kDevice;
{
CertProvisioningScheduler scheduler(
cert_scope, GetProfile(), &pref_service_,
prefs::kRequiredClientCertificateForDevice, &cloud_policy_client_,
network_state_test_helper_.network_state_handler(),
MakeFakeInvalidationFactory());
// From CertProvisioningScheduler::CleanVaKeysIfIdle.
EXPECT_CALL(
fake_cryptohome_client_,
OnTpmAttestationDeleteKeysByPrefix(
attestation::AttestationKeyType::KEY_DEVICE, kKeyNamePrefix))
.Times(1);
FastForwardBy(base::TimeDelta::FromSeconds(1));
}
{
const char kCertProfileId[] = "cert_profile_id_1";
const char kCertProfileVersion[] = "cert_profile_version_1";
CertProfile cert_profile{kCertProfileId, kCertProfileVersion};
// Add 1 serialized worker for the profile.
base::Value saved_worker = ParseJson(
R"({
"cert_profile": {
"policy_version": "cert_profile_version_1",
"profile_id": "cert_profile_1"
},
"cert_scope": 0,
"invalidation_topic": "",
"public_key": "fake_public_key_1",
"state": 1
})");
base::Value all_saved_workers(base::Value::Type::DICTIONARY);
all_saved_workers.SetKey("cert_profile_1", saved_worker.Clone());
pref_service_.Set(prefs::kCertificateProvisioningStateForDevice,
all_saved_workers);
MockCertProvisioningWorker* worker =
mock_factory_.ExpectDeserializeReturnMock(cert_scope, saved_worker);
// This worker should be deleted approximately right after creation, hence
// no calls for DoStep.
worker->SetExpectations(/*do_step_times=*/Exactly(0),
/*is_waiting=*/true, cert_profile);
CertProvisioningScheduler scheduler(
cert_scope, GetProfile(), &pref_service_,
prefs::kRequiredClientCertificateForDevice, &cloud_policy_client_,
network_state_test_helper_.network_state_handler(),
MakeFakeInvalidationFactory());
EXPECT_CALL(fake_cryptohome_client_, OnTpmAttestationDeleteKeysByPrefix)
.Times(0);
FastForwardBy(base::TimeDelta::FromSeconds(1));
}
}
} // namespace
} // namespace cert_provisioning
} // namespace chromeos