| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ash/cert_provisioning/cert_provisioning_scheduler.h" |
| |
| #include "base/containers/contains.h" |
| #include "base/functional/bind.h" |
| #include "base/json/json_reader.h" |
| #include "base/test/values_test_util.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/ash/cert_provisioning/cert_provisioning_common.h" |
| #include "chrome/browser/ash/cert_provisioning/cert_provisioning_test_helpers.h" |
| #include "chrome/browser/ash/cert_provisioning/cert_provisioning_worker.h" |
| #include "chrome/browser/ash/cert_provisioning/mock_cert_provisioning_client.h" |
| #include "chrome/browser/ash/cert_provisioning/mock_cert_provisioning_worker.h" |
| #include "chrome/browser/ash/platform_keys/mock_platform_keys_service.h" |
| #include "chrome/browser/ash/platform_keys/platform_keys_service.h" |
| #include "chrome/common/pref_names.h" |
| #include "chromeos/ash/components/dbus/attestation/fake_attestation_client.h" |
| #include "chromeos/ash/components/dbus/attestation/interface.pb.h" |
| #include "chromeos/ash/components/network/network_state_test_helper.h" |
| #include "chromeos/ash/components/platform_keys/platform_keys.h" |
| #include "components/prefs/testing_pref_service.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/cros_system_api/dbus/shill/dbus-constants.h" |
| |
| using base::Time; |
| using base::test::ParseJson; |
| 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::SaveArg; |
| using testing::StrictMock; |
| |
| namespace ash::cert_provisioning { |
| namespace { |
| |
| constexpr char kWifiServiceGuid[] = "wifi_guid"; |
| constexpr char kCertProfileId[] = "cert_profile_id_1"; |
| constexpr char kCertProfileName[] = "Certificate Profile 1"; |
| constexpr char kCertProfileVersion[] = "cert_profile_version_1"; |
| constexpr char kCertProvId[] = "123"; |
| constexpr char kCertProvId0[] = "000"; |
| constexpr char kCertProvId1[] = "111"; |
| constexpr char kCertProvId2[] = "222"; |
| constexpr base::TimeDelta kCertProfileRenewalPeriod = base::Seconds(0); |
| |
| void VerifyDeleteKeysByPrefixCalledOnce(CertScope cert_scope) { |
| const std::vector<::attestation::DeleteKeysRequest> delete_keys_history = |
| AttestationClient::Get()->GetTestInterface()->delete_keys_history(); |
| // Use `ASSERT_EQ()` so the checks that follows don't crash. |
| ASSERT_EQ(delete_keys_history.size(), 1U); |
| EXPECT_EQ(delete_keys_history[0].username().empty(), |
| cert_scope != CertScope::kUser); |
| EXPECT_EQ(delete_keys_history[0].key_label_match(), kKeyNamePrefix); |
| EXPECT_EQ(delete_keys_history[0].match_behavior(), |
| ::attestation::DeleteKeysRequest::MATCH_BEHAVIOR_PREFIX); |
| } |
| |
| void ExpectDeleteKeysByPrefixNeverCalled() { |
| const std::vector<::attestation::DeleteKeysRequest> delete_keys_history = |
| AttestationClient::Get()->GetTestInterface()->delete_keys_history(); |
| EXPECT_TRUE(delete_keys_history.empty()); |
| } |
| |
| //=============== TestCertProvisioningSchedulerObserver ======================== |
| |
| class TestCertProvisioningSchedulerObserver { |
| public: |
| TestCertProvisioningSchedulerObserver() = default; |
| ~TestCertProvisioningSchedulerObserver() = default; |
| |
| TestCertProvisioningSchedulerObserver( |
| const TestCertProvisioningSchedulerObserver& other) = delete; |
| TestCertProvisioningSchedulerObserver& operator=( |
| const TestCertProvisioningSchedulerObserver& other) = delete; |
| |
| base::RepeatingClosure GetCallback() { |
| return base::BindRepeating( |
| &TestCertProvisioningSchedulerObserver::OnVisibleStateChanged, |
| base::Unretained(this)); |
| } |
| |
| void OnVisibleStateChanged() { |
| ++notifications_received_; |
| run_loop_->Quit(); |
| } |
| |
| // Waits for one call to happen (since construction or since the previous |
| // WaitForOneCall has returned). |
| void WaitForOneCall() { |
| run_loop_->Run(); |
| // Create a new RunLoop so it can already be terminated when the next |
| // OnVisibleStateChanged() call comes in. |
| run_loop_ = std::make_unique<base::RunLoop>(); |
| } |
| |
| // Returns the number of received calls since the last use of this method. |
| size_t ReadAndResetCallCount() { |
| size_t result = notifications_received_; |
| notifications_received_ = 0; |
| return result; |
| } |
| |
| private: |
| size_t notifications_received_ = 0; |
| std::unique_ptr<base::RunLoop> run_loop_ = std::make_unique<base::RunLoop>(); |
| }; |
| |
| //=================== 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()); |
| |
| certificate_helper_ = |
| std::make_unique<CertificateHelperForTesting>(&platform_keys_service_); |
| |
| AddOnlineWifiNetwork(); |
| } |
| |
| void FastForwardBy(base::TimeDelta delta) { |
| task_environment_.FastForwardBy(delta); |
| } |
| |
| void SetUp() override { |
| AttestationClient::InitializeFake(); |
| CertProvisioningWorkerFactory::SetFactoryForTesting(&mock_factory_); |
| } |
| |
| void TearDown() override { |
| CertProvisioningWorkerFactory::SetFactoryForTesting(nullptr); |
| AttestationClient::Shutdown(); |
| } |
| |
| 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_; |
| std::unique_ptr<CertificateHelperForTesting> certificate_helper_; |
| TestingPrefServiceSimple pref_service_; |
| // Only expected creations are allowed. |
| StrictMock<MockCertProvisioningWorkerFactory> mock_factory_; |
| NetworkStateTestHelper network_state_test_helper_; |
| std::string wifi_network_service_path_; |
| }; |
| |
| TEST_F(CertProvisioningSchedulerTest, Success) { |
| const CertScope kCertScope = CertScope::kUser; |
| |
| auto mock_invalidation_factory_obj = |
| std::make_unique<MockCertProvisioningInvalidatorFactory>(); |
| MockCertProvisioningInvalidatorFactory* mock_invalidation_factory = |
| mock_invalidation_factory_obj.get(); |
| |
| CertProvisioningSchedulerImpl scheduler( |
| kCertScope, GetProfile(), &pref_service_, |
| std::make_unique<MockCertProvisioningClient>(), &platform_keys_service_, |
| network_state_test_helper_.network_state_handler(), |
| std::move(mock_invalidation_factory_obj)); |
| |
| // The policy is empty, so no workers should be created yet. |
| FastForwardBy(base::Seconds(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. |
| |
| // From CertProvisioningSchedulerImpl::CleanVaKeysIfIdle. |
| VerifyDeleteKeysByPrefixCalledOnce(kCertScope); |
| |
| // One worker will be created on prefs update. |
| CertProfile cert_profile(kCertProfileId, kCertProfileName, |
| kCertProfileVersion, KeyType::kRsa, |
| /*is_va_enabled=*/true, kCertProfileRenewalPeriod, |
| ProtocolVersion::kStatic); |
| MockCertProvisioningWorker* worker = |
| mock_factory_.ExpectCreateReturnMock(kCertScope, cert_profile); |
| worker->SetExpectations(/*do_step_times=*/AtLeast(1), |
| /*is_waiting=*/false, kCertProvId, cert_profile, |
| /*failure_message=*/""); |
| |
| // Add 1 certificate profile to the policy (the values are the same as |
| // in |cert_profile|). |
| 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"}])"); |
| pref_service_.Set(GetPrefNameForCertProfiles(kCertScope), config); |
| |
| EXPECT_EQ(scheduler.GetWorkers().size(), 1U); |
| |
| // Emulate callback from the worker. |
| scheduler.OnProfileFinished(cert_profile, kCertProvId, |
| CertProvisioningWorkerState::kSucceeded); |
| |
| // Finished worker should be deleted. |
| EXPECT_EQ(scheduler.GetWorkers().size(), 0U); |
| EXPECT_TRUE(scheduler.GetFailedCertProfileIds().empty()); |
| |
| certificate_helper_->AddCert(kCertScope, 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.UpdateAllWorkers(); |
| |
| FastForwardBy(base::Seconds(100)); |
| } |
| |
| TEST_F(CertProvisioningSchedulerTest, WorkerFailed) { |
| const CertScope kCertScope = CertScope::kDevice; |
| |
| CertProvisioningSchedulerImpl scheduler( |
| kCertScope, GetProfile(), &pref_service_, |
| std::make_unique<MockCertProvisioningClient>(), &platform_keys_service_, |
| network_state_test_helper_.network_state_handler(), |
| MakeFakeInvalidationFactory()); |
| |
| // The policy is empty, so no workers should be created yet. |
| FastForwardBy(base::Seconds(1)); |
| EXPECT_EQ(scheduler.GetWorkers().size(), 0U); |
| |
| // From CertProvisioningScheduler::CleanVaKeysIfIdle. |
| VerifyDeleteKeysByPrefixCalledOnce(kCertScope); |
| |
| // One worker will be created on prefs update. |
| CertProfile cert_profile(kCertProfileId, kCertProfileName, |
| kCertProfileVersion, KeyType::kRsa, |
| /*is_va_enabled=*/true, kCertProfileRenewalPeriod, |
| ProtocolVersion::kStatic); |
| MockCertProvisioningWorker* worker = |
| mock_factory_.ExpectCreateReturnMock(kCertScope, cert_profile); |
| worker->SetExpectations(/*do_step_times=*/AtLeast(1), |
| /*is_waiting=*/false, kCertProvId, cert_profile, |
| /*failure_message=*/"reason for failure"); |
| |
| // Add 1 certificate profile to the policy (the values are the same as |
| // in |cert_profile|). |
| 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"}])"); |
| pref_service_.Set(GetPrefNameForCertProfiles(kCertScope), config); |
| |
| // Now 1 worker should be created. |
| EXPECT_EQ(scheduler.GetWorkers().size(), 1U); |
| |
| // Emulate callback from the worker. |
| scheduler.OnProfileFinished(cert_profile, kCertProvId, |
| CertProvisioningWorkerState::kFailed); |
| |
| // The failure message in the FailedWorkerInfo object should match the |
| // failure message passed to the mock worker |
| EXPECT_EQ(scheduler.GetFailedCertProfileIds().size(), 1U); |
| EXPECT_EQ( |
| scheduler.GetFailedCertProfileIds().at(kCertProfileId).failure_message, |
| "reason for failure"); |
| |
| // 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(kCertScope, 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.UpdateAllWorkers(); |
| } |
| |
| TEST_F(CertProvisioningSchedulerTest, InitialAndDailyUpdates) { |
| const CertScope kCertScope = CertScope::kUser; |
| |
| CertProfile cert_profile(kCertProfileId, kCertProfileName, |
| kCertProfileVersion, KeyType::kRsa, |
| /*is_va_enabled=*/true, kCertProfileRenewalPeriod, |
| ProtocolVersion::kStatic); |
| |
| // Add 1 certificate profile to the policy (the values are the same as |
| // in |cert_profile|). |
| 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"}])"); |
| pref_service_.Set(GetPrefNameForCertProfiles(kCertScope), config); |
| |
| CertProvisioningSchedulerImpl scheduler( |
| kCertScope, GetProfile(), &pref_service_, |
| std::make_unique<MockCertProvisioningClient>(), &platform_keys_service_, |
| network_state_test_helper_.network_state_handler(), |
| MakeFakeInvalidationFactory()); |
| |
| // Now one worker should be created. |
| MockCertProvisioningWorker* worker = |
| mock_factory_.ExpectCreateReturnMock(kCertScope, cert_profile); |
| worker->SetExpectations(/*do_step_times=*/AtLeast(1), /*is_waiting=*/false, |
| kCertProvId0, cert_profile, /*failure_message=*/""); |
| FastForwardBy(base::Seconds(1)); |
| ASSERT_EQ(scheduler.GetWorkers().size(), 1U); |
| |
| // Emulate callback from the worker. |
| scheduler.OnProfileFinished(cert_profile, kCertProvId0, |
| CertProvisioningWorkerState::kFailed); |
| |
| ASSERT_EQ(scheduler.GetWorkers().size(), 0U); |
| EXPECT_TRUE( |
| base::Contains(scheduler.GetFailedCertProfileIds(), kCertProfileId)); |
| |
| // No workers should be created yet. |
| FastForwardBy(base::Hours(20)); |
| ASSERT_EQ(scheduler.GetWorkers().size(), 0U); |
| |
| // From CertProvisioningSchedulerImpl::CleanVaKeysIfIdle. |
| VerifyDeleteKeysByPrefixCalledOnce(kCertScope); |
| |
| // Now list of failed profiles should be cleared that will cause a new attempt |
| // to provision certificate. |
| MockCertProvisioningWorker* worker2 = |
| mock_factory_.ExpectCreateReturnMock(kCertScope, cert_profile); |
| worker2->SetExpectations(/*do_step_times=*/AtLeast(1), /*is_waiting=*/false, |
| kCertProvId2, cert_profile, /*failure_message=*/""); |
| FastForwardBy(base::Hours(5)); |
| ASSERT_EQ(scheduler.GetWorkers().size(), 1U); |
| |
| // Emulate callback from the worker. |
| scheduler.OnProfileFinished(cert_profile, kCertProvId2, |
| CertProvisioningWorkerState::kSucceeded); |
| |
| ASSERT_EQ(scheduler.GetWorkers().size(), 0U); |
| EXPECT_TRUE(scheduler.GetFailedCertProfileIds().empty()); |
| } |
| |
| TEST_F(CertProvisioningSchedulerTest, MultipleWorkers) { |
| const CertScope kCertScope = CertScope::kDevice; |
| |
| CertProvisioningSchedulerImpl scheduler( |
| kCertScope, GetProfile(), &pref_service_, |
| std::make_unique<MockCertProvisioningClient>(), &platform_keys_service_, |
| network_state_test_helper_.network_state_handler(), |
| MakeFakeInvalidationFactory()); |
| |
| // The policy is empty, so no workers should be created yet. |
| FastForwardBy(base::Seconds(1)); |
| ASSERT_EQ(scheduler.GetWorkers().size(), 0U); |
| |
| // From CertProvisioningScheduler::CleanVaKeysIfIdle. |
| VerifyDeleteKeysByPrefixCalledOnce(kCertScope); |
| |
| // New workers will be created on prefs update. |
| const char kCertProfileId0[] = "cert_profile_id_0"; |
| const char kCertProfileName0[] = "Certificate Profile 0"; |
| const char kCertProfileVersion0[] = "cert_profile_version_0"; |
| CertProfile cert_profile0(kCertProfileId0, kCertProfileName0, |
| kCertProfileVersion0, KeyType::kRsa, |
| /*is_va_enabled=*/true, kCertProfileRenewalPeriod, |
| ProtocolVersion::kStatic); |
| const char kCertProfileId1[] = "cert_profile_id_1"; |
| const char kCertProfileName1[] = "Certificate Profile 1"; |
| const char kCertProfileVersion1[] = "cert_profile_version_1"; |
| CertProfile cert_profile1(kCertProfileId1, kCertProfileName1, |
| kCertProfileVersion1, KeyType::kRsa, |
| /*is_va_enabled=*/true, kCertProfileRenewalPeriod, |
| ProtocolVersion::kStatic); |
| const char kCertProfileId2[] = "cert_profile_id_2"; |
| const char kCertProfileName2[] = "Certificate Profile 2"; |
| const char kCertProfileVersion2[] = "cert_profile_version_2"; |
| CertProfile cert_profile2(kCertProfileId2, kCertProfileName2, |
| kCertProfileVersion2, KeyType::kRsa, |
| /*is_va_enabled=*/true, kCertProfileRenewalPeriod, |
| ProtocolVersion::kStatic); |
| MockCertProvisioningWorker* worker0 = |
| mock_factory_.ExpectCreateReturnMock(kCertScope, cert_profile0); |
| worker0->SetExpectations(/*do_step_times=*/AtLeast(1), /*is_waiting=*/false, |
| kCertProvId0, cert_profile0, /*failure_message=*/""); |
| MockCertProvisioningWorker* worker1 = |
| mock_factory_.ExpectCreateReturnMock(kCertScope, cert_profile1); |
| worker1->SetExpectations(/*do_step_times=*/AtLeast(1), /*is_waiting=*/false, |
| kCertProvId1, cert_profile1, /*failure_message=*/""); |
| MockCertProvisioningWorker* worker2 = |
| mock_factory_.ExpectCreateReturnMock(kCertScope, cert_profile2); |
| worker2->SetExpectations(/*do_step_times=*/AtLeast(1), /*is_waiting=*/false, |
| kCertProvId2, cert_profile2, /*failure_message=*/""); |
| |
| // Add 3 certificate profiles to the policy (the values are the same as |
| // in |cert_profile|-s) |
| 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" |
| }, |
| { |
| "name": "Certificate Profile 1", |
| "cert_profile_id":"cert_profile_id_1", |
| "policy_version":"cert_profile_version_1", |
| "key_algorithm":"rsa" |
| }, |
| { |
| "name": "Certificate Profile 2", |
| "cert_profile_id":"cert_profile_id_2", |
| "policy_version":"cert_profile_version_2", |
| "key_algorithm":"rsa" |
| }])"); |
| pref_service_.Set(GetPrefNameForCertProfiles(kCertScope), 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, kCertProvId0, |
| CertProvisioningWorkerState::kSucceeded); |
| |
| // worker1 is waiting. Should be continued. |
| worker1->SetExpectations(/*do_step_times=*/AtLeast(1), /*is_waiting=*/true, |
| kCertProvId1, cert_profile1, /*failure_message=*/""); |
| |
| // worker2 failed. Should be deleted and the profile id should be saved. |
| scheduler.OnProfileFinished(cert_profile2, kCertProvId2, |
| CertProvisioningWorkerState::kFailed); |
| |
| EXPECT_EQ(scheduler.GetWorkers().size(), 1U); |
| EXPECT_TRUE( |
| base::Contains(scheduler.GetFailedCertProfileIds(), kCertProfileId2)); |
| |
| certificate_helper_->AddCert(kCertScope, kCertProfileId0); |
| |
| // Make scheduler check workers state. |
| scheduler.UpdateAllWorkers(); |
| |
| EXPECT_EQ(scheduler.GetWorkers().size(), 1U); |
| EXPECT_TRUE( |
| base::Contains(scheduler.GetFailedCertProfileIds(), kCertProfileId2)); |
| |
| // 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.UpdateAllWorkers(); |
| EXPECT_EQ(scheduler.GetWorkers().size(), 1U); |
| } |
| |
| TEST_F(CertProvisioningSchedulerTest, RemoveCertWithoutPolicy) { |
| const CertScope kCertScope = CertScope::kDevice; |
| |
| certificate_helper_->AddCert(kCertScope, kCertProfileId); |
| |
| CertProvisioningSchedulerImpl scheduler( |
| kCertScope, GetProfile(), &pref_service_, |
| std::make_unique<MockCertProvisioningClient>(), &platform_keys_service_, |
| network_state_test_helper_.network_state_handler(), |
| MakeFakeInvalidationFactory()); |
| |
| EXPECT_CALL( |
| platform_keys_service_, |
| RemoveCertificate(GetPlatformKeysTokenId(kCertScope), |
| /*certificate=*/certificate_helper_->GetCerts().back(), |
| /*callback=*/_)) |
| .Times(1); |
| |
| FastForwardBy(base::Seconds(1)); |
| } |
| |
| TEST_F(CertProvisioningSchedulerTest, DeserializeWorkers) { |
| const CertScope kCertScope = CertScope::kUser; |
| |
| CertProfile cert_profile(kCertProfileId, kCertProfileName, |
| kCertProfileVersion, KeyType::kRsa, |
| /*is_va_enabled=*/true, kCertProfileRenewalPeriod, |
| ProtocolVersion::kStatic); |
| |
| // Add 1 certificate profile to the policy (the values are the same as |
| // in |cert_profile|). |
| 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"}])"); |
| pref_service_.Set(GetPrefNameForCertProfiles(kCertScope), 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, |
| "public_key": "fake_public_key_1", |
| "state": 1 |
| })"); |
| base::Value::Dict all_saved_workers; |
| all_saved_workers.Set("cert_profile_1", saved_worker.Clone()); |
| |
| pref_service_.SetDict(GetPrefNameForSerialization(kCertScope), |
| std::move(all_saved_workers)); |
| |
| MockCertProvisioningWorker* worker = |
| mock_factory_.ExpectDeserializeReturnMock(kCertScope, 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, kCertProvId, cert_profile, |
| /*failure_message=*/""); |
| |
| CertProvisioningSchedulerImpl scheduler( |
| kCertScope, GetProfile(), &pref_service_, |
| std::make_unique<MockCertProvisioningClient>(), &platform_keys_service_, |
| network_state_test_helper_.network_state_handler(), |
| MakeFakeInvalidationFactory()); |
| |
| // Now one worker should be created. |
| FastForwardBy(base::Seconds(1)); |
| ASSERT_EQ(scheduler.GetWorkers().size(), 1U); |
| } |
| |
| // Test that the scheduler removes deserialized workers if the cert for them |
| // already exists. |
| TEST_F(CertProvisioningSchedulerTest, DeserializeWorkerForExistingCert) { |
| const CertScope kCertScope = CertScope::kUser; |
| |
| CertProfile cert_profile(kCertProfileId, kCertProfileName, |
| kCertProfileVersion, KeyType::kRsa, |
| /*is_va_enabled=*/true, kCertProfileRenewalPeriod, |
| ProtocolVersion::kStatic); |
| |
| // Add 1 certificate profile to the policy (the values are the same as |
| // in |cert_profile|). |
| 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"}])"); |
| pref_service_.Set(GetPrefNameForCertProfiles(kCertScope), 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, |
| "public_key": "fake_public_key_1", |
| "state": 1 |
| })"); |
| base::Value::Dict all_saved_workers; |
| all_saved_workers.Set("cert_profile_1", saved_worker.Clone()); |
| |
| pref_service_.SetDict(GetPrefNameForSerialization(kCertScope), |
| std::move(all_saved_workers)); |
| |
| MockCertProvisioningWorker* worker = |
| mock_factory_.ExpectDeserializeReturnMock(kCertScope, saved_worker); |
| worker->SetExpectations(/*do_step_times=*/Exactly(0), |
| /*is_waiting=*/true, kCertProvId, cert_profile, |
| /*failure_message=*/""); |
| EXPECT_CALL(*worker, Stop(CertProvisioningWorkerState::kSucceeded)); |
| |
| // Add an existing cert for the profile id. |
| certificate_helper_->AddCert(kCertScope, kCertProfileId); |
| |
| CertProvisioningSchedulerImpl scheduler( |
| kCertScope, GetProfile(), &pref_service_, |
| std::make_unique<MockCertProvisioningClient>(), &platform_keys_service_, |
| network_state_test_helper_.network_state_handler(), |
| MakeFakeInvalidationFactory()); |
| |
| // Give the scheduler some time to process the worker. The expectations will |
| // be verified at the end of the test. |
| FastForwardBy(base::Seconds(1)); |
| } |
| |
| TEST_F(CertProvisioningSchedulerTest, InconsistentDataErrorHandling) { |
| const CertScope kCertScope = CertScope::kDevice; |
| |
| const char kCertProfileVersion1[] = "cert_profile_version_1"; |
| const char kCertProfileVersion2[] = "cert_profile_version_2"; |
| |
| CertProvisioningSchedulerImpl scheduler( |
| kCertScope, GetProfile(), &pref_service_, |
| std::make_unique<MockCertProvisioningClient>(), &platform_keys_service_, |
| network_state_test_helper_.network_state_handler(), |
| MakeFakeInvalidationFactory()); |
| |
| // The policy is empty, so no workers should be created yet. |
| FastForwardBy(base::Seconds(1)); |
| EXPECT_EQ(scheduler.GetWorkers().size(), 0U); |
| |
| // From CertProvisioningScheduler::CleanVaKeysIfIdle. |
| VerifyDeleteKeysByPrefixCalledOnce(kCertScope); |
| |
| CertProfile cert_profile_v1(kCertProfileId, kCertProfileName, |
| kCertProfileVersion1, KeyType::kRsa, |
| /*is_va_enabled=*/true, kCertProfileRenewalPeriod, |
| ProtocolVersion::kStatic); |
| |
| MockCertProvisioningWorker* worker = |
| mock_factory_.ExpectCreateReturnMock(kCertScope, cert_profile_v1); |
| worker->SetExpectations(/*do_step_times=*/AtLeast(1), /*is_waiting=*/false, |
| kCertProvId0, cert_profile_v1, |
| /*failure_message=*/""); |
| |
| // Add 1 certificate profile to the policy (the values are the same as |
| // in |cert_profile_v1|). |
| 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"}])"); |
| pref_service_.Set(GetPrefNameForCertProfiles(kCertScope), config); |
| |
| // Now 1 worker should be created. |
| EXPECT_EQ(scheduler.GetWorkers().size(), 1U); |
| |
| // Emulate callback from the worker. |
| scheduler.OnProfileFinished( |
| cert_profile_v1, kCertProvId, |
| 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(kCertScope, cert_profile_v1); |
| worker->SetExpectations(/*do_step_times=*/AtLeast(1), /*is_waiting=*/false, |
| kCertProvId1, cert_profile_v1, |
| /*failure_message=*/""); |
| |
| // After some delay a new worker should be created to try again. |
| FastForwardBy(base::Seconds(31)); |
| EXPECT_EQ(scheduler.GetWorkers().size(), 1U); |
| |
| // Emulate callback from the worker. |
| scheduler.OnProfileFinished( |
| cert_profile_v1, kCertProvId1, |
| 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, kCertProfileName, |
| kCertProfileVersion2, KeyType::kRsa, |
| /*is_va_enabled=*/true, kCertProfileRenewalPeriod, |
| ProtocolVersion::kStatic); |
| worker = mock_factory_.ExpectCreateReturnMock(kCertScope, cert_profile_v2); |
| worker->SetExpectations(/*do_step_times=*/AtLeast(1), /*is_waiting=*/false, |
| kCertProvId2, cert_profile_v2, |
| /*failure_message=*/""); |
| |
| // 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"}])"); |
| pref_service_.Set(GetPrefNameForCertProfiles(kCertScope), config); |
| EXPECT_EQ(scheduler.GetWorkers().size(), 1U); |
| |
| // If another update happens, workers with matching policy versions should not |
| // be deleted. |
| scheduler.UpdateAllWorkers(); |
| EXPECT_EQ(scheduler.GetWorkers().size(), 1U); |
| |
| // 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"}])"); |
| |
| // On policy change scheduler should detect mismatch in policy versions and |
| // stop the worker. |
| EXPECT_CALL(*worker, |
| Stop(CertProvisioningWorkerState::kInconsistentDataError)); |
| |
| pref_service_.Set(GetPrefNameForCertProfiles(kCertScope), config); |
| |
| // Emulate that after some time the worker reports back to scheduler. |
| FastForwardBy(base::Seconds(10)); |
| scheduler.OnProfileFinished( |
| cert_profile_v1, kCertProvId2, |
| CertProvisioningWorkerState::kInconsistentDataError); |
| EXPECT_EQ(scheduler.GetWorkers().size(), 0U); |
| } |
| |
| TEST_F(CertProvisioningSchedulerTest, RetryAfterNoInternetConnection) { |
| const CertScope kCertScope = CertScope::kDevice; |
| SetWifiNetworkState(shill::kStateIdle); |
| |
| CertProfile cert_profile(kCertProfileId, kCertProfileName, |
| kCertProfileVersion, KeyType::kRsa, |
| /*is_va_enabled=*/true, kCertProfileRenewalPeriod, |
| ProtocolVersion::kStatic); |
| // Add 1 certificate profile to the policy (the values are the same as |
| // in |cert_profile|). |
| 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"}])"); |
| pref_service_.Set(GetPrefNameForCertProfiles(kCertScope), config); |
| |
| CertProvisioningSchedulerImpl scheduler( |
| kCertScope, GetProfile(), &pref_service_, |
| std::make_unique<MockCertProvisioningClient>(), &platform_keys_service_, |
| network_state_test_helper_.network_state_handler(), |
| MakeFakeInvalidationFactory()); |
| |
| FastForwardBy(base::Hours(72)); |
| ASSERT_EQ(scheduler.GetWorkers().size(), 0U); |
| |
| // From CertProvisioningScheduler::CleanVaKeysIfIdle. |
| VerifyDeleteKeysByPrefixCalledOnce(kCertScope); |
| |
| // Add a new worker to the factory. |
| MockCertProvisioningWorker* worker = |
| mock_factory_.ExpectCreateReturnMock(kCertScope, cert_profile); |
| worker->SetExpectations(/*do_step_times=*/AtLeast(1), /*is_waiting=*/false, |
| kCertProvId, cert_profile, /*failure_message=*/""); |
| |
| SetWifiNetworkState(shill::kStateOnline); |
| |
| ASSERT_EQ(scheduler.GetWorkers().size(), 1U); |
| } |
| |
| TEST_F(CertProvisioningSchedulerTest, DeleteWorkerWithoutPolicy) { |
| const CertScope kCertScope = CertScope::kDevice; |
| |
| CertProfile cert_profile(kCertProfileId, kCertProfileName, |
| kCertProfileVersion, KeyType::kRsa, |
| /*is_va_enabled=*/true, kCertProfileRenewalPeriod, |
| ProtocolVersion::kStatic); |
| // Add 1 certificate profile to the policy (the values are the same as |
| // in |cert_profile|). |
| 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"}])"); |
| |
| CertProvisioningSchedulerImpl scheduler( |
| kCertScope, GetProfile(), &pref_service_, |
| std::make_unique<MockCertProvisioningClient>(), &platform_keys_service_, |
| network_state_test_helper_.network_state_handler(), |
| MakeFakeInvalidationFactory()); |
| |
| // Add a new worker to the factory. |
| MockCertProvisioningWorker* worker = |
| mock_factory_.ExpectCreateReturnMock(kCertScope, cert_profile); |
| worker->SetExpectations(/*do_step_times=*/AtLeast(1), /*is_waiting=*/false, |
| kCertProvId, cert_profile, /*failure_message=*/""); |
| |
| // Prefs update will be ignored because initialization task has not finished |
| // yet. |
| pref_service_.Set(GetPrefNameForCertProfiles(kCertScope), config); |
| ASSERT_EQ(scheduler.GetWorkers().size(), 0U); |
| |
| FastForwardBy(base::Seconds(1)); |
| EXPECT_EQ(scheduler.GetWorkers().size(), 1U); |
| |
| EXPECT_CALL(*worker, Stop(CertProvisioningWorkerState::kCanceled)); |
| |
| config = ParseJson("[]"); |
| pref_service_.Set(GetPrefNameForCertProfiles(kCertScope), config); |
| |
| FastForwardBy(base::Seconds(1)); |
| // Emulate callback from the worker. |
| scheduler.OnProfileFinished(cert_profile, kCertProvId, |
| CertProvisioningWorkerState::kCanceled); |
| |
| ASSERT_EQ(scheduler.GetWorkers().size(), 0U); |
| |
| // From CertProvisioningScheduler::CleanVaKeysIfIdle. |
| VerifyDeleteKeysByPrefixCalledOnce(kCertScope); |
| } |
| |
| TEST_F(CertProvisioningSchedulerTest, DeleteVaKeysOnIdle) { |
| const CertScope kCertScope = CertScope::kDevice; |
| |
| { |
| CertProvisioningSchedulerImpl scheduler( |
| kCertScope, GetProfile(), &pref_service_, |
| std::make_unique<MockCertProvisioningClient>(), &platform_keys_service_, |
| network_state_test_helper_.network_state_handler(), |
| MakeFakeInvalidationFactory()); |
| |
| FastForwardBy(base::Seconds(1)); |
| |
| // From CertProvisioningScheduler::CleanVaKeysIfIdle. |
| VerifyDeleteKeysByPrefixCalledOnce(kCertScope); |
| } |
| |
| AttestationClient::Get()->GetTestInterface()->ClearDeleteKeysHistory(); |
| |
| { |
| CertProfile cert_profile(kCertProfileId, kCertProfileName, |
| kCertProfileVersion, KeyType::kRsa, |
| /*is_va_enabled=*/true, kCertProfileRenewalPeriod, |
| ProtocolVersion::kStatic); |
| |
| // Add 1 serialized worker for the profile (the values are the same as |
| // in |cert_profile|). |
| base::Value saved_worker = ParseJson( |
| R"({ |
| "cert_profile": { |
| "policy_version": "cert_profile_version_1", |
| "profile_id": "cert_profile_1" |
| }, |
| "cert_scope": 0, |
| "public_key": "fake_public_key_1", |
| "state": 1 |
| })"); |
| base::Value::Dict all_saved_workers; |
| all_saved_workers.Set("cert_profile_1", saved_worker.Clone()); |
| |
| pref_service_.SetDict(GetPrefNameForSerialization(kCertScope), |
| std::move(all_saved_workers)); |
| |
| MockCertProvisioningWorker* worker = |
| mock_factory_.ExpectDeserializeReturnMock(kCertScope, 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, kCertProvId, cert_profile, |
| /*failure_message=*/""); |
| |
| CertProvisioningSchedulerImpl scheduler( |
| kCertScope, GetProfile(), &pref_service_, |
| std::make_unique<MockCertProvisioningClient>(), &platform_keys_service_, |
| network_state_test_helper_.network_state_handler(), |
| MakeFakeInvalidationFactory()); |
| |
| FastForwardBy(base::Seconds(1)); |
| |
| ExpectDeleteKeysByPrefixNeverCalled(); |
| } |
| } |
| |
| TEST_F(CertProvisioningSchedulerTest, UpdateOneWorker) { |
| const CertScope kCertScope = CertScope::kUser; |
| |
| CertProvisioningSchedulerImpl scheduler( |
| kCertScope, GetProfile(), &pref_service_, |
| std::make_unique<MockCertProvisioningClient>(), &platform_keys_service_, |
| network_state_test_helper_.network_state_handler(), |
| MakeFakeInvalidationFactory()); |
| |
| CertProfile cert_profile(kCertProfileId, kCertProfileName, |
| kCertProfileVersion, KeyType::kRsa, |
| /*is_va_enabled=*/true, kCertProfileRenewalPeriod, |
| ProtocolVersion::kStatic); |
| |
| FastForwardBy(base::Seconds(1)); |
| |
| // From CertProvisioningScheduler::CleanVaKeysIfIdle. |
| VerifyDeleteKeysByPrefixCalledOnce(kCertScope); |
| |
| // There is no policies yet, |kCertProfileId| will not be found. |
| scheduler.UpdateOneWorker(kCertProfileId); |
| FastForwardBy(base::Seconds(1)); |
| ASSERT_TRUE(scheduler.GetWorkers().empty()); |
| |
| MockCertProvisioningWorker* worker = |
| mock_factory_.ExpectCreateReturnMock(kCertScope, cert_profile); |
| worker->SetExpectations(/*do_step_times=*/Exactly(1), |
| /*is_waiting=*/false, kCertProvId, cert_profile, |
| /*failure_message=*/""); |
| |
| // Add 1 certificate profile to the policy (the values are the same as |
| // in |cert_profile|). |
| 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"}])"); |
| pref_service_.Set(GetPrefNameForCertProfiles(kCertScope), config); |
| FastForwardBy(base::Seconds(1)); |
| |
| // If worker is waiting, it should be continued. |
| { |
| worker->SetExpectations(/*do_step_times=*/Exactly(1), |
| /*is_waiting=*/true, kCertProvId, cert_profile, |
| /*failure_message=*/""); |
| |
| scheduler.UpdateOneWorker(kCertProfileId); |
| FastForwardBy(base::Seconds(1)); |
| ASSERT_EQ(scheduler.GetWorkers().size(), 1U); |
| } |
| |
| // If worker is not waiting, it should not be continued. |
| { |
| worker->SetExpectations(/*do_step_times=*/Exactly(0), |
| /*is_waiting=*/false, kCertProvId, cert_profile, |
| /*failure_message=*/""); |
| |
| scheduler.UpdateOneWorker(kCertProfileId); |
| FastForwardBy(base::Seconds(1)); |
| ASSERT_EQ(scheduler.GetWorkers().size(), 1U); |
| } |
| |
| // If there is no intenet connection, the worker should not be continued |
| // until it is restored. |
| { |
| SetWifiNetworkState(shill::kStateIdle); |
| |
| worker->SetExpectations(/*do_step_times=*/Exactly(0), |
| /*is_waiting=*/true, kCertProvId, cert_profile, |
| /*failure_message=*/""); |
| |
| scheduler.UpdateOneWorker(kCertProfileId); |
| FastForwardBy(base::Seconds(1)); |
| ASSERT_EQ(scheduler.GetWorkers().size(), 1U); |
| |
| worker->SetExpectations(/*do_step_times=*/Exactly(1), |
| /*is_waiting=*/true, kCertProvId, cert_profile, |
| /*failure_message=*/""); |
| |
| SetWifiNetworkState(shill::kStateOnline); |
| } |
| |
| // Emulate callback from the worker. |
| scheduler.OnProfileFinished(cert_profile, kCertProvId, |
| CertProvisioningWorkerState::kSucceeded); |
| FastForwardBy(base::Seconds(1)); |
| ASSERT_TRUE(scheduler.GetWorkers().empty()); |
| |
| certificate_helper_->AddCert(kCertScope, kCertProfileId); |
| |
| { |
| // If a certificate already exists, a new worker should not be created. |
| scheduler.UpdateOneWorker(kCertProfileId); |
| FastForwardBy(base::Seconds(1)); |
| ASSERT_TRUE(scheduler.GetWorkers().empty()); |
| } |
| } |
| |
| TEST_F(CertProvisioningSchedulerTest, CertRenewal) { |
| const CertScope kCertScope = CertScope::kUser; |
| // 1 day == 86400 seconds. |
| const base::TimeDelta kRenewalPeriod = base::Days(1); |
| |
| CertProfile cert_profile( |
| kCertProfileId, kCertProfileName, kCertProfileVersion, KeyType::kRsa, |
| /*is_va_enabled=*/true, kRenewalPeriod, ProtocolVersion::kStatic); |
| |
| const Time t1 = Time::Now() - base::Days(1); |
| const Time t2 = Time::Now() + base::Days(7); |
| certificate_helper_->AddCert(kCertScope, kCertProfileId, |
| chromeos::platform_keys::Status::kSuccess, |
| /*not_valid_before=*/t1, /*not_valid_after=*/t2); |
| |
| // Add 1 certificate profile to the policy (the values are the same as |
| // in |cert_profile|). |
| 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": 86400}])"); |
| pref_service_.Set(GetPrefNameForCertProfiles(kCertScope), config); |
| |
| CertProvisioningSchedulerImpl scheduler( |
| kCertScope, GetProfile(), &pref_service_, |
| std::make_unique<MockCertProvisioningClient>(), &platform_keys_service_, |
| network_state_test_helper_.network_state_handler(), |
| MakeFakeInvalidationFactory()); |
| |
| // The certificate already exists, nothing should happen on scheduler |
| // creation. |
| FastForwardBy(base::Seconds(1)); |
| ASSERT_EQ(scheduler.GetWorkers().size(), 0U); |
| |
| // From CertProvisioningScheduler::CleanVaKeysIfIdle. |
| VerifyDeleteKeysByPrefixCalledOnce(kCertScope); |
| |
| // Also nothing should happen in the next ~6 days. |
| FastForwardBy(base::Days(5) + base::Hours(23)); |
| ASSERT_EQ(scheduler.GetWorkers().size(), 0U); |
| |
| MockCertProvisioningWorker* worker = |
| mock_factory_.ExpectCreateReturnMock(kCertScope, cert_profile); |
| worker->SetExpectations(/*do_step_times=*/AtLeast(1), |
| /*is_waiting=*/false, kCertProvId, cert_profile, |
| /*failure_message=*/""); |
| |
| // One day (according to the policy) before the certificate expires, scheduler |
| // should create a new worker to provision a replacement. |
| FastForwardBy(base::Hours(1)); |
| ASSERT_EQ(scheduler.GetWorkers().size(), 1U); |
| } |
| |
| TEST_F(CertProvisioningSchedulerTest, PlatformKeysServiceShutDown) { |
| CertScope kCertScope = CertScope::kDevice; |
| |
| platform_keys::PlatformKeysServiceObserver* observer = nullptr; |
| EXPECT_CALL(platform_keys_service_, AddObserver(_)) |
| .WillOnce(SaveArg<0>(&observer)); |
| CertProvisioningSchedulerImpl scheduler( |
| kCertScope, GetProfile(), &pref_service_, |
| std::make_unique<MockCertProvisioningClient>(), &platform_keys_service_, |
| network_state_test_helper_.network_state_handler(), |
| MakeFakeInvalidationFactory()); |
| |
| ASSERT_TRUE(observer); |
| |
| // 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" }])"); |
| pref_service_.Set(prefs::kRequiredClientCertificateForDevice, config); |
| |
| // Same as in the policy. |
| CertProfile cert_profile{kCertProfileId, kCertProfileName, |
| kCertProfileVersion, KeyType::kRsa, |
| /*is_va_enabled=*/true, kCertProfileRenewalPeriod, |
| ProtocolVersion::kStatic}; |
| |
| MockCertProvisioningWorker* worker = |
| mock_factory_.ExpectCreateReturnMock(kCertScope, cert_profile); |
| worker->SetExpectations(/*do_step_times=*/AtLeast(1), /*is_waiting=*/false, |
| kCertProvId, cert_profile, /*failure_message=*/""); |
| scheduler.UpdateAllWorkers(); |
| |
| // Now 1 worker should be created. |
| EXPECT_EQ(scheduler.GetWorkers().size(), 1U); |
| |
| // PlatformKeysService notifies that it is shutting down. |
| EXPECT_CALL(platform_keys_service_, RemoveObserver(observer)); |
| observer->OnPlatformKeysServiceShutDown(); |
| |
| // The worker should be deleted. |
| EXPECT_EQ(scheduler.GetWorkers().size(), 0U); |
| |
| // Check one more time that scheduler doesn't create new workers after |
| // PlatformKeysService has been shut down (the factory will fail on an attempt |
| // to do so). |
| scheduler.UpdateAllWorkers(); |
| } |
| |
| TEST_F(CertProvisioningSchedulerTest, StateChangeNotifications) { |
| const CertScope kCertScope = CertScope::kDevice; |
| |
| CertProvisioningSchedulerImpl scheduler( |
| kCertScope, GetProfile(), &pref_service_, |
| std::make_unique<MockCertProvisioningClient>(), &platform_keys_service_, |
| network_state_test_helper_.network_state_handler(), |
| MakeFakeInvalidationFactory()); |
| |
| TestCertProvisioningSchedulerObserver observer; |
| auto subscription = scheduler.AddObserver(observer.GetCallback()); |
| |
| // The policy is empty, so no workers should be created yet. |
| FastForwardBy(base::Seconds(1)); |
| ASSERT_EQ(scheduler.GetWorkers().size(), 0U); |
| |
| // From CertProvisioningScheduler::CleanVaKeysIfIdle. |
| VerifyDeleteKeysByPrefixCalledOnce(kCertScope); |
| |
| // Two new workers will be created on prefs update. |
| // Expect a state change notification for this. |
| const char kCertProfileId0[] = "cert_profile_id_0"; |
| const char kCertProfileName0[] = "Certificate Profile 0"; |
| const char kCertProfileVersion0[] = "cert_profile_version_0"; |
| CertProfile cert_profile0(kCertProfileId0, kCertProfileName0, |
| kCertProfileVersion0, KeyType::kRsa, |
| /*is_va_enabled=*/true, kCertProfileRenewalPeriod, |
| ProtocolVersion::kStatic); |
| const char kCertProfileId1[] = "cert_profile_id_1"; |
| const char kCertProfileName1[] = "Certificate Profile 1"; |
| const char kCertProfileVersion1[] = "cert_profile_version_1"; |
| CertProfile cert_profile1(kCertProfileId1, kCertProfileName1, |
| kCertProfileVersion1, KeyType::kRsa, |
| /*is_va_enabled=*/true, kCertProfileRenewalPeriod, |
| ProtocolVersion::kStatic); |
| |
| MockCertProvisioningWorker* worker0 = |
| mock_factory_.ExpectCreateReturnMock(kCertScope, cert_profile0); |
| worker0->SetExpectations(/*do_step_times=*/AtLeast(1), /*is_waiting=*/false, |
| kCertProvId0, cert_profile0, /*failure_message=*/""); |
| MockCertProvisioningWorker* worker1 = |
| mock_factory_.ExpectCreateReturnMock(kCertScope, cert_profile1); |
| worker1->SetExpectations(/*do_step_times=*/AtLeast(1), /*is_waiting=*/false, |
| kCertProvId1, cert_profile1, /*failure_message=*/""); |
| |
| // Add 2 certificate profiles to the policy (the values are the same as |
| // in |cert_profile|-s) |
| 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" |
| }, |
| { |
| "name": "Certificate Profile 1", |
| "cert_profile_id":"cert_profile_id_1", |
| "policy_version":"cert_profile_version_1", |
| "key_algorithm":"rsa" |
| }])"); |
| pref_service_.Set(GetPrefNameForCertProfiles(kCertScope), config); |
| observer.WaitForOneCall(); |
| |
| // Now one worker for each profile should be created. |
| ASSERT_EQ(scheduler.GetWorkers().size(), 2U); |
| |
| // Emulate a worker reporting a state change. |
| // A state change event should be fired by the scheduler for that. |
| scheduler.OnVisibleStateChanged(); |
| observer.WaitForOneCall(); |
| |
| // Emulate a worker reporting a state changeand successfully finishing. |
| // Should be just deleted, and state change event should be |
| // fired for that. |
| scheduler.OnVisibleStateChanged(); |
| scheduler.OnProfileFinished(cert_profile0, kCertProvId0, |
| CertProvisioningWorkerState::kSucceeded); |
| observer.WaitForOneCall(); |
| |
| // worker1 failed. Should be deleted and the profile id should be saved, and a |
| // state change event should be fired for that. |
| scheduler.OnProfileFinished(cert_profile1, kCertProvId1, |
| CertProvisioningWorkerState::kFailed); |
| observer.WaitForOneCall(); |
| |
| EXPECT_EQ(scheduler.GetWorkers().size(), 0U); |
| EXPECT_TRUE( |
| base::Contains(scheduler.GetFailedCertProfileIds(), kCertProfileId1)); |
| } |
| |
| TEST_F(CertProvisioningSchedulerTest, HoldBackNotifications) { |
| CertProvisioningSchedulerImpl scheduler( |
| CertScope::kDevice, GetProfile(), &pref_service_, |
| std::make_unique<MockCertProvisioningClient>(), &platform_keys_service_, |
| network_state_test_helper_.network_state_handler(), |
| MakeFakeInvalidationFactory()); |
| |
| TestCertProvisioningSchedulerObserver observer; |
| auto subscription = scheduler.AddObserver(observer.GetCallback()); |
| |
| // Ensure initial 0. |
| EXPECT_EQ(0u, observer.ReadAndResetCallCount()); |
| |
| // A single event produces a single notification. |
| { |
| scheduler.OnVisibleStateChanged(); |
| // Here and below this is needed to let an async notification task to be |
| // executed. |
| FastForwardBy(base::Seconds(0)); |
| EXPECT_EQ(1u, observer.ReadAndResetCallCount()); |
| FastForwardBy(base::Seconds(0)); |
| EXPECT_EQ(0u, observer.ReadAndResetCallCount()); |
| FastForwardBy(base::Days(1)); |
| EXPECT_EQ(0u, observer.ReadAndResetCallCount()); |
| } |
| |
| // Multiple synchronous events produce a single notification. |
| { |
| scheduler.OnVisibleStateChanged(); |
| scheduler.OnVisibleStateChanged(); |
| scheduler.OnVisibleStateChanged(); |
| // Here and below this is needed to let an async notification task to be |
| // executed. |
| FastForwardBy(base::Seconds(0)); |
| EXPECT_EQ(1u, observer.ReadAndResetCallCount()); |
| FastForwardBy(base::Seconds(0)); |
| EXPECT_EQ(0u, observer.ReadAndResetCallCount()); |
| FastForwardBy(base::Days(1)); |
| EXPECT_EQ(0u, observer.ReadAndResetCallCount()); |
| } |
| |
| // Multiple asynchronous events within a short period of time produce a single |
| // notification immediately and one more after the internal timer is released. |
| { |
| scheduler.OnVisibleStateChanged(); |
| FastForwardBy(base::Seconds(0)); |
| scheduler.OnVisibleStateChanged(); |
| FastForwardBy(base::Seconds(0)); |
| scheduler.OnVisibleStateChanged(); |
| // Here and below this is needed to let an async notification task to be |
| // executed. |
| FastForwardBy(base::Seconds(0)); |
| EXPECT_EQ(1u, observer.ReadAndResetCallCount()); |
| FastForwardBy(base::Seconds(0)); |
| EXPECT_EQ(0u, observer.ReadAndResetCallCount()); |
| FastForwardBy(base::Milliseconds(350)); |
| EXPECT_EQ(1u, observer.ReadAndResetCallCount()); |
| FastForwardBy(base::Days(1)); |
| EXPECT_EQ(0u, observer.ReadAndResetCallCount()); |
| } |
| |
| // N asynchronous events far enough apart produce N notifications. |
| { |
| scheduler.OnVisibleStateChanged(); |
| FastForwardBy(base::Milliseconds(350)); |
| scheduler.OnVisibleStateChanged(); |
| FastForwardBy(base::Milliseconds(350)); |
| scheduler.OnVisibleStateChanged(); |
| // Here and below this is needed to let an async notification task to be |
| // executed. |
| FastForwardBy(base::Seconds(0)); |
| EXPECT_EQ(3u, observer.ReadAndResetCallCount()); |
| FastForwardBy(base::Seconds(0)); |
| EXPECT_EQ(0u, observer.ReadAndResetCallCount()); |
| FastForwardBy(base::Days(1)); |
| EXPECT_EQ(0u, observer.ReadAndResetCallCount()); |
| } |
| } |
| |
| TEST_F(CertProvisioningSchedulerTest, ResetOneWorker) { |
| const CertScope kCertScope = CertScope::kUser; |
| |
| CertProvisioningSchedulerImpl scheduler( |
| kCertScope, GetProfile(), &pref_service_, |
| std::make_unique<MockCertProvisioningClient>(), &platform_keys_service_, |
| network_state_test_helper_.network_state_handler(), |
| MakeFakeInvalidationFactory()); |
| |
| CertProfile cert_profile(kCertProfileId, kCertProfileName, |
| kCertProfileVersion, KeyType::kRsa, |
| /*is_va_enabled=*/true, kCertProfileRenewalPeriod, |
| ProtocolVersion::kStatic); |
| |
| FastForwardBy(base::Seconds(1)); |
| |
| // From CertProvisioningScheduler::CleanVaKeysIfIdle. |
| VerifyDeleteKeysByPrefixCalledOnce(kCertScope); |
| |
| MockCertProvisioningWorker* worker = |
| mock_factory_.ExpectCreateReturnMock(kCertScope, cert_profile); |
| worker->SetExpectations(/*do_step_times=*/Exactly(1), |
| /*is_waiting=*/false, kCertProvId, cert_profile, |
| /*failure_message=*/""); |
| |
| // Add 1 certificate profile to the policy (the values are the same as |
| // in |cert_profile|). |
| 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"}])"); |
| pref_service_.Set(GetPrefNameForCertProfiles(kCertScope), config); |
| FastForwardBy(base::Seconds(1)); |
| |
| { |
| worker->ResetExpected(); |
| scheduler.ResetOneWorker(kCertProfileId); |
| ASSERT_EQ(scheduler.GetWorkers().size(), 1U); |
| MockCertProvisioningWorker* second_worker = |
| mock_factory_.ExpectCreateReturnMock(kCertScope, cert_profile); |
| second_worker->SetExpectations(Exactly(1), false, kCertProvId, cert_profile, |
| ""); |
| scheduler.OnProfileFinished(cert_profile, kCertProvId, |
| CertProvisioningWorkerState::kCanceled); |
| ASSERT_EQ(scheduler.GetWorkers().size(), 1U); |
| } |
| } |
| |
| TEST_F(CertProvisioningSchedulerTest, PolicyChangeClearsFailedWorkers) { |
| const CertScope kCertScope = CertScope::kUser; |
| |
| CertProfile cert_profile(kCertProfileId, kCertProfileName, |
| kCertProfileVersion, KeyType::kRsa, |
| /*is_va_enabled=*/true, kCertProfileRenewalPeriod, |
| ProtocolVersion::kStatic); |
| |
| // Add 1 certificate profile to the policy (the values are the same as |
| // in |cert_profile|). |
| 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"}])"); |
| pref_service_.Set(GetPrefNameForCertProfiles(kCertScope), config); |
| |
| CertProvisioningSchedulerImpl scheduler( |
| kCertScope, GetProfile(), &pref_service_, |
| std::make_unique<MockCertProvisioningClient>(), &platform_keys_service_, |
| network_state_test_helper_.network_state_handler(), |
| MakeFakeInvalidationFactory()); |
| |
| TestCertProvisioningSchedulerObserver observer; |
| auto subscription = scheduler.AddObserver(observer.GetCallback()); |
| |
| // Now one worker should be created. |
| MockCertProvisioningWorker* worker = |
| mock_factory_.ExpectCreateReturnMock(kCertScope, cert_profile); |
| worker->SetExpectations(/*do_step_times=*/AtLeast(1), /*is_waiting=*/false, |
| kCertProvId0, cert_profile, /*failure_message=*/""); |
| FastForwardBy(base::Seconds(1)); |
| ASSERT_EQ(scheduler.GetWorkers().size(), 1U); |
| |
| // Emulate callback from the worker. |
| scheduler.OnProfileFinished(cert_profile, kCertProvId0, |
| CertProvisioningWorkerState::kFailed); |
| |
| ASSERT_EQ(scheduler.GetWorkers().size(), 0U); |
| ASSERT_TRUE( |
| base::Contains(scheduler.GetFailedCertProfileIds(), kCertProfileId)); |
| ASSERT_EQ(observer.ReadAndResetCallCount(), 1U); |
| |
| // Change the policy to the empty one, the failed worker should be cleared. |
| base::Value empty_config = ParseJson("[]"); |
| pref_service_.Set(GetPrefNameForCertProfiles(kCertScope), empty_config); |
| FastForwardBy(base::Seconds(1)); |
| |
| ASSERT_EQ(scheduler.GetWorkers().size(), 0U); |
| ASSERT_EQ(scheduler.GetFailedCertProfileIds().size(), 0U); |
| ASSERT_EQ(observer.ReadAndResetCallCount(), 1U); |
| } |
| |
| TEST_F(CertProvisioningSchedulerTest, PolicyUpdateRestartsFailedWorkers) { |
| const CertScope kCertScope = CertScope::kUser; |
| const char kCertProfileVersion2[] = "cert_profile_version_2"; |
| |
| CertProfile cert_profile(kCertProfileId, kCertProfileName, |
| kCertProfileVersion, KeyType::kRsa, |
| /*is_va_enabled=*/true, kCertProfileRenewalPeriod, |
| ProtocolVersion::kStatic); |
| |
| // Add 1 certificate profile to the policy (the values are the same as |
| // in |cert_profile|). |
| 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"}])"); |
| pref_service_.Set(GetPrefNameForCertProfiles(kCertScope), config); |
| |
| CertProvisioningSchedulerImpl scheduler( |
| kCertScope, GetProfile(), &pref_service_, |
| std::make_unique<MockCertProvisioningClient>(), &platform_keys_service_, |
| network_state_test_helper_.network_state_handler(), |
| MakeFakeInvalidationFactory()); |
| |
| TestCertProvisioningSchedulerObserver observer; |
| auto subscription = scheduler.AddObserver(observer.GetCallback()); |
| |
| // Now one worker should be created. |
| MockCertProvisioningWorker* worker = |
| mock_factory_.ExpectCreateReturnMock(kCertScope, cert_profile); |
| worker->SetExpectations(/*do_step_times=*/AtLeast(1), /*is_waiting=*/false, |
| kCertProvId0, cert_profile, /*failure_message=*/""); |
| FastForwardBy(base::Seconds(1)); |
| ASSERT_EQ(scheduler.GetWorkers().size(), 1U); |
| |
| // Emulate callback from the worker. |
| scheduler.OnProfileFinished(cert_profile, kCertProvId0, |
| CertProvisioningWorkerState::kFailed); |
| |
| ASSERT_EQ(scheduler.GetWorkers().size(), 0U); |
| ASSERT_TRUE( |
| base::Contains(scheduler.GetFailedCertProfileIds(), kCertProfileId)); |
| ASSERT_EQ(observer.ReadAndResetCallCount(), 1U); |
| |
| CertProfile updated_cert_profile( |
| kCertProfileId, kCertProfileName, kCertProfileVersion2, KeyType::kEc, |
| /*is_va_enabled=*/true, kCertProfileRenewalPeriod, |
| ProtocolVersion::kStatic); |
| // One worker should be created when the policy changes. |
| MockCertProvisioningWorker* worker2 = |
| mock_factory_.ExpectCreateReturnMock(kCertScope, updated_cert_profile); |
| worker2->SetExpectations(/*do_step_times=*/AtLeast(1), /*is_waiting=*/false, |
| kCertProvId0, updated_cert_profile, |
| /*failure_message=*/""); |
| |
| // Change the key type in the policy, the failed worker should be re-created |
| // with the new key type. |
| base::Value updated_config = ParseJson( |
| R"([{"name": "Certificate Profile 1", |
| "cert_profile_id":"cert_profile_id_1", |
| "policy_version":"cert_profile_version_2", |
| "key_algorithm":"ec"}])"); |
| pref_service_.Set(GetPrefNameForCertProfiles(kCertScope), updated_config); |
| FastForwardBy(base::Seconds(1)); |
| |
| ASSERT_TRUE(base::Contains(scheduler.GetWorkers(), kCertProfileId)); |
| ASSERT_EQ(scheduler.GetFailedCertProfileIds().size(), 0U); |
| ASSERT_GE(observer.ReadAndResetCallCount(), 1U); |
| } |
| |
| } // namespace |
| } // namespace ash::cert_provisioning |