| // 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 |