| // Copyright 2019 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 "chromeos/services/device_sync/cryptauth_v2_enrollment_manager_impl.h" |
| |
| #include <memory> |
| #include <unordered_map> |
| #include <utility> |
| |
| #include "base/base64url.h" |
| #include "base/no_destructor.h" |
| #include "base/optional.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/simple_test_clock.h" |
| #include "base/timer/mock_timer.h" |
| #include "chromeos/services/device_sync/cryptauth_constants.h" |
| #include "chromeos/services/device_sync/cryptauth_enrollment_result.h" |
| #include "chromeos/services/device_sync/cryptauth_key_bundle.h" |
| #include "chromeos/services/device_sync/cryptauth_key_registry.h" |
| #include "chromeos/services/device_sync/cryptauth_key_registry_impl.h" |
| #include "chromeos/services/device_sync/cryptauth_scheduler.h" |
| #include "chromeos/services/device_sync/cryptauth_v2_enroller.h" |
| #include "chromeos/services/device_sync/cryptauth_v2_enroller_impl.h" |
| #include "chromeos/services/device_sync/fake_cryptauth_gcm_manager.h" |
| #include "chromeos/services/device_sync/fake_cryptauth_scheduler.h" |
| #include "chromeos/services/device_sync/fake_cryptauth_v2_enroller.h" |
| #include "chromeos/services/device_sync/mock_cryptauth_client.h" |
| #include "chromeos/services/device_sync/pref_names.h" |
| #include "chromeos/services/device_sync/proto/cryptauth_api.pb.h" |
| #include "chromeos/services/device_sync/proto/cryptauth_better_together_feature_metadata.pb.h" |
| #include "chromeos/services/device_sync/proto/cryptauth_client_app_metadata.pb.h" |
| #include "chromeos/services/device_sync/proto/cryptauth_common.pb.h" |
| #include "chromeos/services/device_sync/proto/cryptauth_directive.pb.h" |
| #include "chromeos/services/device_sync/proto/cryptauth_v2_test_util.h" |
| #include "chromeos/services/device_sync/public/cpp/fake_client_app_metadata_provider.h" |
| #include "chromeos/services/device_sync/public/cpp/gcm_constants.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/prefs/testing_pref_service.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace chromeos { |
| |
| namespace device_sync { |
| |
| namespace { |
| |
| const char kFakeV1PublicKey[] = "public_key_v1"; |
| const char kFakeV1PrivateKey[] = "private_key_v1"; |
| const char kFakeV2PublicKey[] = "public_key_v2"; |
| const char kFakeV2PrivateKey[] = "private_key_v2"; |
| const char kFakeSessionId[] = "session_id"; |
| |
| constexpr base::TimeDelta kFakeRefreshPeriod = |
| base::TimeDelta::FromMilliseconds(2592000000); |
| constexpr base::TimeDelta kFakeRetryPeriod = |
| base::TimeDelta::FromMilliseconds(43200000); |
| |
| const cryptauthv2::PolicyReference& GetClientDirectivePolicyReferenceForTest() { |
| static const base::NoDestructor<cryptauthv2::PolicyReference> |
| policy_reference([] { |
| return cryptauthv2::BuildPolicyReference("policy_reference_name", |
| 1 /* version */); |
| }()); |
| |
| return *policy_reference; |
| } |
| |
| class FakeCryptAuthV2EnrollerFactory : public CryptAuthV2EnrollerImpl::Factory { |
| public: |
| FakeCryptAuthV2EnrollerFactory( |
| const CryptAuthKeyRegistry* expected_key_registry, |
| const CryptAuthClientFactory* expected_client_factory) |
| : expected_key_registry_(expected_key_registry), |
| expected_client_factory_(expected_client_factory) {} |
| |
| ~FakeCryptAuthV2EnrollerFactory() override = default; |
| |
| const std::vector<FakeCryptAuthV2Enroller*>& created_instances() { |
| return created_instances_; |
| } |
| |
| private: |
| // CryptAuthV2EnrollerImpl::Factory: |
| std::unique_ptr<CryptAuthV2Enroller> BuildInstance( |
| CryptAuthKeyRegistry* key_registry, |
| CryptAuthClientFactory* client_factory, |
| std::unique_ptr<base::OneShotTimer> timer = |
| std::make_unique<base::MockOneShotTimer>()) override { |
| EXPECT_EQ(expected_key_registry_, key_registry); |
| EXPECT_EQ(expected_client_factory_, client_factory); |
| |
| auto instance = std::make_unique<FakeCryptAuthV2Enroller>(); |
| created_instances_.push_back(instance.get()); |
| |
| return instance; |
| } |
| |
| const CryptAuthKeyRegistry* expected_key_registry_; |
| const CryptAuthClientFactory* expected_client_factory_; |
| |
| std::vector<FakeCryptAuthV2Enroller*> created_instances_; |
| |
| DISALLOW_COPY_AND_ASSIGN(FakeCryptAuthV2EnrollerFactory); |
| }; |
| |
| } // namespace |
| |
| class DeviceSyncCryptAuthV2EnrollmentManagerImplTest |
| : public testing::Test, |
| CryptAuthEnrollmentManager::Observer { |
| protected: |
| DeviceSyncCryptAuthV2EnrollmentManagerImplTest() |
| : fake_gcm_manager_(std::string() /* registration_id */), |
| mock_client_factory_( |
| MockCryptAuthClientFactory::MockType::MAKE_NICE_MOCKS) {} |
| |
| // testing::Test: |
| void SetUp() override { |
| test_clock_.SetNow(base::Time::UnixEpoch()); |
| |
| CryptAuthV2EnrollmentManagerImpl::RegisterPrefs( |
| test_pref_service_.registry()); |
| CryptAuthKeyRegistryImpl::RegisterPrefs(test_pref_service_.registry()); |
| |
| key_registry_ = CryptAuthKeyRegistryImpl::Factory::Get()->BuildInstance( |
| &test_pref_service_); |
| |
| fake_enroller_factory_ = std::make_unique<FakeCryptAuthV2EnrollerFactory>( |
| key_registry_.get(), &mock_client_factory_); |
| CryptAuthV2EnrollerImpl::Factory::SetFactoryForTesting( |
| fake_enroller_factory_.get()); |
| |
| // Fix the scheduler's ClientDirective PolicyReference to test that it is |
| // passed to the enroller properly. |
| fake_enrollment_scheduler_.set_client_directive_policy_reference( |
| GetClientDirectivePolicyReferenceForTest()); |
| } |
| |
| // testing::Test: |
| void TearDown() override { |
| if (enrollment_manager_) |
| enrollment_manager_->RemoveObserver(this); |
| |
| CryptAuthV2EnrollerImpl::Factory::SetFactoryForTesting(nullptr); |
| } |
| |
| // CryptAuthEnrollmentManager::Observer: |
| void OnEnrollmentStarted() override { |
| ++num_enrollment_started_notifications_; |
| } |
| void OnEnrollmentFinished(bool success) override { |
| observer_enrollment_finished_success_list_.push_back(success); |
| } |
| |
| void AddV1UserKeyPairToV1Prefs(const std::string& public_key, |
| const std::string& private_key) { |
| std::string public_key_b64, private_key_b64; |
| base::Base64UrlEncode(public_key, |
| base::Base64UrlEncodePolicy::INCLUDE_PADDING, |
| &public_key_b64); |
| base::Base64UrlEncode(private_key, |
| base::Base64UrlEncodePolicy::INCLUDE_PADDING, |
| &private_key_b64); |
| |
| test_pref_service_.SetString(prefs::kCryptAuthEnrollmentUserPublicKey, |
| public_key_b64); |
| test_pref_service_.SetString(prefs::kCryptAuthEnrollmentUserPrivateKey, |
| private_key_b64); |
| } |
| |
| void CreateEnrollmentManager() { |
| auto mock_timer = std::make_unique<base::MockOneShotTimer>(); |
| mock_timer_ = mock_timer.get(); |
| |
| VerifyUserKeyPairStateHistogram(0u /* total_count */); |
| |
| enrollment_manager_ = |
| CryptAuthV2EnrollmentManagerImpl::Factory::Get()->BuildInstance( |
| &fake_client_app_metadata_provider_, key_registry_.get(), |
| &mock_client_factory_, &fake_gcm_manager_, |
| &fake_enrollment_scheduler_, &test_pref_service_, &test_clock_, |
| std::move(mock_timer)); |
| |
| VerifyUserKeyPairStateHistogram(1u /* total_count */); |
| |
| enrollment_manager_->AddObserver(this); |
| } |
| |
| void RequestEnrollmentThroughGcm( |
| const base::Optional<std::string>& session_id) { |
| fake_gcm_manager_.PushReenrollMessage(session_id, |
| base::nullopt /* feature_type */); |
| } |
| |
| void VerifyEnrollmentManagerObserversNotifiedOfStart( |
| size_t expected_num_enrollment_started_notifications) { |
| EXPECT_EQ(expected_num_enrollment_started_notifications, |
| num_enrollment_started_notifications_); |
| } |
| |
| void CompleteGcmRegistration(bool success) { |
| EXPECT_TRUE(fake_gcm_manager_.GetRegistrationId().empty()); |
| |
| if (success) { |
| fake_gcm_manager_.CompleteRegistration( |
| cryptauthv2::kTestGcmRegistrationId); |
| } else { |
| // An empty registration ID is interpreted as an error by |
| // FakeCryptAuthGCMManager. |
| fake_gcm_manager_.CompleteRegistration(std::string()); |
| } |
| } |
| |
| void HandleGetClientAppMetadataRequest(bool success) { |
| EXPECT_GT(fake_client_app_metadata_provider_.metadata_requests().size(), |
| 0u); |
| EXPECT_EQ(cryptauthv2::kTestGcmRegistrationId, |
| fake_client_app_metadata_provider_.metadata_requests() |
| .back() |
| .gcm_registration_id); |
| |
| if (success) { |
| std::move(fake_client_app_metadata_provider_.metadata_requests() |
| .back() |
| .callback) |
| .Run(cryptauthv2::GetClientAppMetadataForTest()); |
| return; |
| } |
| |
| std::move( |
| fake_client_app_metadata_provider_.metadata_requests().back().callback) |
| .Run(base::nullopt /* client_app_metadata */); |
| } |
| |
| void FinishEnrollmentAttempt( |
| size_t expected_enroller_instance_index, |
| const cryptauthv2::ClientMetadata& expected_client_metadata, |
| const CryptAuthEnrollmentResult& enrollment_result) { |
| EXPECT_TRUE(enrollment_manager_->IsEnrollmentInProgress()); |
| |
| // A valid GCM registration ID and valid ClientAppMetadata must exist before |
| // the enroller can be invoked. |
| EXPECT_EQ(cryptauthv2::kTestGcmRegistrationId, |
| fake_gcm_manager_.GetRegistrationId()); |
| EXPECT_GT(fake_client_app_metadata_provider_.metadata_requests().size(), |
| 0u); |
| |
| // Only the most recently created enroller is valid. |
| EXPECT_EQ(fake_enroller_factory_->created_instances().size() - 1, |
| expected_enroller_instance_index); |
| FakeCryptAuthV2Enroller* enroller = |
| fake_enroller_factory_ |
| ->created_instances()[expected_enroller_instance_index]; |
| |
| VerifyEnrollerData(enroller, expected_client_metadata); |
| |
| enroller->FinishAttempt(enrollment_result); |
| |
| EXPECT_FALSE(enrollment_manager_->IsEnrollmentInProgress()); |
| } |
| |
| void VerifyEnrollmentResults( |
| const std::vector<CryptAuthEnrollmentResult>& expected_results) { |
| VerifyResultsSentToEnrollmentManagerObservers(expected_results); |
| VerifyResultsSentToScheduler(expected_results); |
| VerifyEnrollmentResultHistograms(expected_results); |
| } |
| |
| void VerifyInvocationReasonHistogram( |
| const std::vector<cryptauthv2::ClientMetadata::InvocationReason>& |
| expected_invocation_reasons) { |
| std::unordered_map<cryptauthv2::ClientMetadata::InvocationReason, size_t> |
| reason_to_count_map; |
| for (const auto& reason : expected_invocation_reasons) |
| ++reason_to_count_map[reason]; |
| |
| size_t total_count = 0; |
| for (const auto& reason_count_pair : reason_to_count_map) { |
| histogram_tester_.ExpectBucketCount( |
| "CryptAuth.EnrollmentV2.InvocationReason", reason_count_pair.first, |
| reason_count_pair.second); |
| total_count += reason_count_pair.second; |
| } |
| |
| histogram_tester_.ExpectTotalCount( |
| "CryptAuth.EnrollmentV2.InvocationReason", total_count); |
| } |
| |
| void VerifyUserKeyPairStateHistogram(size_t total_count) { |
| histogram_tester_.ExpectTotalCount( |
| "CryptAuth.EnrollmentV2.UserKeyPairState", total_count); |
| } |
| |
| CryptAuthKeyRegistry* key_registry() { return key_registry_.get(); } |
| |
| FakeCryptAuthScheduler* fake_enrollment_scheduler() { |
| return &fake_enrollment_scheduler_; |
| } |
| |
| base::SimpleTestClock* test_clock() { return &test_clock_; } |
| |
| base::MockOneShotTimer* mock_timer() { return mock_timer_; } |
| |
| CryptAuthEnrollmentManager* enrollment_manager() { |
| return enrollment_manager_.get(); |
| } |
| |
| private: |
| void VerifyEnrollerData( |
| FakeCryptAuthV2Enroller* enroller, |
| const cryptauthv2::ClientMetadata& expected_client_metadata) { |
| EXPECT_TRUE(enroller->was_enroll_called()); |
| EXPECT_EQ(expected_client_metadata.SerializeAsString(), |
| enroller->client_metadata()->SerializeAsString()); |
| EXPECT_EQ(cryptauthv2::GetClientAppMetadataForTest().SerializeAsString(), |
| enroller->client_app_metadata()->SerializeAsString()); |
| EXPECT_EQ( |
| GetClientDirectivePolicyReferenceForTest().SerializeAsString(), |
| (*enroller->client_directive_policy_reference())->SerializeAsString()); |
| } |
| |
| void VerifyResultsSentToEnrollmentManagerObservers( |
| const std::vector<CryptAuthEnrollmentResult> |
| expected_enrollment_results) { |
| ASSERT_EQ(expected_enrollment_results.size(), |
| observer_enrollment_finished_success_list_.size()); |
| |
| for (size_t i = 0; i < expected_enrollment_results.size(); ++i) { |
| EXPECT_EQ(expected_enrollment_results[i].IsSuccess(), |
| observer_enrollment_finished_success_list_[i]); |
| } |
| } |
| |
| void VerifyResultsSentToScheduler(const std::vector<CryptAuthEnrollmentResult> |
| expected_enrollment_results) { |
| EXPECT_EQ(expected_enrollment_results, |
| fake_enrollment_scheduler()->handled_enrollment_results()); |
| } |
| |
| void VerifyEnrollmentResultHistograms( |
| const std::vector<CryptAuthEnrollmentResult> |
| expected_enrollment_results) { |
| std::unordered_map<CryptAuthEnrollmentResult::ResultCode, size_t> |
| result_code_to_count_map; |
| for (const auto& result : expected_enrollment_results) |
| ++result_code_to_count_map[result.result_code()]; |
| |
| size_t success_count = 0; |
| size_t failure_count = 0; |
| for (const auto& result_count_pair : result_code_to_count_map) { |
| histogram_tester_.ExpectBucketCount( |
| "CryptAuth.EnrollmentV2.Result.ResultCode", result_count_pair.first, |
| result_count_pair.second); |
| |
| if (CryptAuthEnrollmentResult(result_count_pair.first, base::nullopt) |
| .IsSuccess()) { |
| success_count += result_count_pair.second; |
| } else { |
| failure_count += result_count_pair.second; |
| } |
| } |
| |
| histogram_tester_.ExpectTotalCount( |
| "CryptAuth.EnrollmentV2.Result.ResultCode", |
| success_count + failure_count); |
| histogram_tester_.ExpectBucketCount("CryptAuth.EnrollmentV2.Result.Success", |
| true, success_count); |
| histogram_tester_.ExpectBucketCount("CryptAuth.EnrollmentV2.Result.Success", |
| false, failure_count); |
| } |
| |
| size_t num_enrollment_started_notifications_ = 0; |
| std::vector<bool> observer_enrollment_finished_success_list_; |
| |
| TestingPrefServiceSimple test_pref_service_; |
| FakeClientAppMetadataProvider fake_client_app_metadata_provider_; |
| FakeCryptAuthGCMManager fake_gcm_manager_; |
| FakeCryptAuthScheduler fake_enrollment_scheduler_; |
| base::SimpleTestClock test_clock_; |
| base::MockOneShotTimer* mock_timer_; |
| MockCryptAuthClientFactory mock_client_factory_; |
| base::HistogramTester histogram_tester_; |
| std::unique_ptr<CryptAuthKeyRegistry> key_registry_; |
| std::unique_ptr<FakeCryptAuthV2EnrollerFactory> fake_enroller_factory_; |
| |
| std::unique_ptr<CryptAuthEnrollmentManager> enrollment_manager_; |
| }; |
| |
| TEST_F(DeviceSyncCryptAuthV2EnrollmentManagerImplTest, |
| EnrollmentRequestedFromScheduler_NeverPreviouslyEnrolled) { |
| CreateEnrollmentManager(); |
| EXPECT_FALSE(fake_enrollment_scheduler()->HasEnrollmentSchedulingStarted()); |
| |
| enrollment_manager()->Start(); |
| EXPECT_TRUE(fake_enrollment_scheduler()->HasEnrollmentSchedulingStarted()); |
| |
| // The user has never enrolled with v1 or v2 and has not registered with GCM. |
| fake_enrollment_scheduler()->set_last_successful_enrollment_time( |
| base::Time()); |
| EXPECT_TRUE(key_registry()->key_bundles().empty()); |
| EXPECT_TRUE(enrollment_manager()->GetLastEnrollmentTime().is_null()); |
| EXPECT_FALSE(enrollment_manager()->IsEnrollmentValid()); |
| |
| // Scheduler triggers an initialization enrollment request. |
| fake_enrollment_scheduler()->set_num_consecutive_enrollment_failures(0); |
| fake_enrollment_scheduler()->RequestEnrollment( |
| cryptauthv2::ClientMetadata::INITIALIZATION /* invocation_reason */, |
| base::nullopt /* session_id */); |
| |
| EXPECT_TRUE(enrollment_manager()->IsEnrollmentInProgress()); |
| VerifyEnrollmentManagerObserversNotifiedOfStart( |
| 1 /* expected_num_enrollment_started_notifications */); |
| |
| CompleteGcmRegistration(true /* success */); |
| HandleGetClientAppMetadataRequest(true /* success */); |
| |
| CryptAuthEnrollmentResult expected_enrollment_result( |
| CryptAuthEnrollmentResult::ResultCode::kSuccessNewKeysEnrolled, |
| cryptauthv2::GetClientDirectiveForTest()); |
| |
| FinishEnrollmentAttempt( |
| 0u /* expected_enroller_instance_index */, |
| cryptauthv2::BuildClientMetadata( |
| 0 /* retry_count */, |
| cryptauthv2::ClientMetadata::INITIALIZATION /* invocation_reason */, |
| base::nullopt /* session_id */) /* expected_client_metadata */, |
| expected_enrollment_result); |
| |
| VerifyEnrollmentResults({expected_enrollment_result}); |
| } |
| |
| TEST_F(DeviceSyncCryptAuthV2EnrollmentManagerImplTest, GcmRegistrationFailed) { |
| CreateEnrollmentManager(); |
| enrollment_manager()->Start(); |
| |
| fake_enrollment_scheduler()->RequestEnrollment( |
| cryptauthv2::ClientMetadata::INITIALIZATION /* invocation_reason */, |
| base::nullopt /* session_id */); |
| |
| CompleteGcmRegistration(false /* success */); |
| |
| VerifyEnrollmentResults({CryptAuthEnrollmentResult( |
| CryptAuthEnrollmentResult::ResultCode::kErrorGcmRegistrationFailed, |
| base::nullopt /* client_directive */)}); |
| } |
| |
| TEST_F(DeviceSyncCryptAuthV2EnrollmentManagerImplTest, GcmRegistrationTimeout) { |
| CreateEnrollmentManager(); |
| enrollment_manager()->Start(); |
| |
| fake_enrollment_scheduler()->RequestEnrollment( |
| cryptauthv2::ClientMetadata::INITIALIZATION /* invocation_reason */, |
| base::nullopt /* session_id */); |
| |
| // Timeout waiting for GcmRegistration. |
| EXPECT_TRUE(mock_timer()->IsRunning()); |
| mock_timer()->Fire(); |
| |
| VerifyEnrollmentResults( |
| {CryptAuthEnrollmentResult(CryptAuthEnrollmentResult::ResultCode:: |
| kErrorTimeoutWaitingForGcmRegistration, |
| base::nullopt /* client_directive */)}); |
| } |
| |
| TEST_F(DeviceSyncCryptAuthV2EnrollmentManagerImplTest, |
| ClientAppMetadataFetchFailed) { |
| CreateEnrollmentManager(); |
| enrollment_manager()->Start(); |
| |
| fake_enrollment_scheduler()->RequestEnrollment( |
| cryptauthv2::ClientMetadata::INITIALIZATION /* invocation_reason */, |
| base::nullopt /* session_id */); |
| |
| CompleteGcmRegistration(true /* success */); |
| HandleGetClientAppMetadataRequest(false /* success */); |
| |
| VerifyEnrollmentResults({CryptAuthEnrollmentResult( |
| CryptAuthEnrollmentResult::ResultCode::kErrorClientAppMetadataFetchFailed, |
| base::nullopt /* client_directive */)}); |
| } |
| |
| TEST_F(DeviceSyncCryptAuthV2EnrollmentManagerImplTest, |
| ClientAppMetadataTimeout) { |
| CreateEnrollmentManager(); |
| enrollment_manager()->Start(); |
| |
| fake_enrollment_scheduler()->RequestEnrollment( |
| cryptauthv2::ClientMetadata::INITIALIZATION /* invocation_reason */, |
| base::nullopt /* session_id */); |
| |
| CompleteGcmRegistration(true /* success */); |
| |
| // Timeout waiting for ClientAppMetadata. |
| EXPECT_TRUE(mock_timer()->IsRunning()); |
| mock_timer()->Fire(); |
| |
| VerifyEnrollmentResults( |
| {CryptAuthEnrollmentResult(CryptAuthEnrollmentResult::ResultCode:: |
| kErrorTimeoutWaitingForClientAppMetadata, |
| base::nullopt /* client_directive */)}); |
| } |
| |
| TEST_F(DeviceSyncCryptAuthV2EnrollmentManagerImplTest, ForcedEnrollment) { |
| CreateEnrollmentManager(); |
| enrollment_manager()->Start(); |
| |
| enrollment_manager()->ForceEnrollmentNow( |
| cryptauth::InvocationReason::INVOCATION_REASON_FEATURE_TOGGLED, |
| kFakeSessionId); |
| EXPECT_TRUE(enrollment_manager()->IsEnrollmentInProgress()); |
| VerifyEnrollmentManagerObserversNotifiedOfStart( |
| 1 /* expected_num_enrollment_started_notifications */); |
| |
| // Simulate a failed enrollment attempt due to CryptAuth server overload. |
| CompleteGcmRegistration(true /* success */); |
| HandleGetClientAppMetadataRequest(true /* success */); |
| |
| CryptAuthEnrollmentResult expected_enrollment_result( |
| CryptAuthEnrollmentResult::ResultCode::kErrorCryptAuthServerOverloaded, |
| base::nullopt /* client_directive */); |
| |
| FinishEnrollmentAttempt( |
| 0u /* expected_enroller_instance_index */, |
| cryptauthv2::BuildClientMetadata( |
| 0 /* retry_count */, |
| cryptauthv2::ClientMetadata::FEATURE_TOGGLED /* invocation_reason */, |
| kFakeSessionId) /* expected_client_metadata */, |
| expected_enrollment_result); |
| |
| VerifyEnrollmentResults({expected_enrollment_result}); |
| } |
| |
| TEST_F(DeviceSyncCryptAuthV2EnrollmentManagerImplTest, |
| RetryAfterFailedPeriodicEnrollment_PreviouslyEnrolled) { |
| CreateEnrollmentManager(); |
| enrollment_manager()->Start(); |
| |
| // The user has already enrolled. |
| base::Time expected_last_enrollment_time = test_clock()->Now(); |
| fake_enrollment_scheduler()->set_last_successful_enrollment_time( |
| expected_last_enrollment_time); |
| EXPECT_EQ(expected_last_enrollment_time, |
| enrollment_manager()->GetLastEnrollmentTime()); |
| fake_enrollment_scheduler()->set_refresh_period(kFakeRefreshPeriod); |
| fake_enrollment_scheduler()->set_time_to_next_enrollment_request( |
| kFakeRefreshPeriod); |
| EXPECT_TRUE(enrollment_manager()->IsEnrollmentValid()); |
| EXPECT_EQ(kFakeRefreshPeriod, enrollment_manager()->GetTimeToNextAttempt()); |
| |
| // Now, user is due for a refresh. |
| test_clock()->SetNow(test_clock()->Now() + kFakeRefreshPeriod + |
| base::TimeDelta::FromSeconds(1)); |
| EXPECT_FALSE(enrollment_manager()->IsEnrollmentValid()); |
| base::TimeDelta expected_time_to_next_attempt = |
| base::TimeDelta::FromSeconds(0); |
| fake_enrollment_scheduler()->set_time_to_next_enrollment_request( |
| expected_time_to_next_attempt); |
| EXPECT_EQ(expected_time_to_next_attempt, |
| enrollment_manager()->GetTimeToNextAttempt()); |
| |
| cryptauthv2::ClientMetadata expected_client_metadata = |
| cryptauthv2::BuildClientMetadata(0 /* retry_count */, |
| cryptauthv2::ClientMetadata::PERIODIC, |
| base::nullopt /* session_id */); |
| |
| // First enrollment attempt fails. |
| // Note: User does not yet have a GCM registration ID or ClientAppMetadata. |
| fake_enrollment_scheduler()->RequestEnrollment( |
| cryptauthv2::ClientMetadata::PERIODIC, base::nullopt /* session_id */); |
| EXPECT_TRUE(enrollment_manager()->IsEnrollmentInProgress()); |
| VerifyEnrollmentManagerObserversNotifiedOfStart( |
| 1 /* expected_num_enrollment_started_notifications */); |
| |
| CompleteGcmRegistration(true /* success */); |
| HandleGetClientAppMetadataRequest(true /* success */); |
| |
| CryptAuthEnrollmentResult first_expected_enrollment_result( |
| CryptAuthEnrollmentResult::ResultCode::kErrorCryptAuthServerOverloaded, |
| base::nullopt /* client_directive */); |
| |
| FinishEnrollmentAttempt(0u /* expected_enroller_instance_index */, |
| expected_client_metadata, |
| first_expected_enrollment_result); |
| |
| EXPECT_FALSE(enrollment_manager()->IsRecoveringFromFailure()); |
| fake_enrollment_scheduler()->set_num_consecutive_enrollment_failures(1); |
| EXPECT_TRUE(enrollment_manager()->IsRecoveringFromFailure()); |
| |
| fake_enrollment_scheduler()->set_time_to_next_enrollment_request( |
| kFakeRetryPeriod); |
| EXPECT_EQ(kFakeRetryPeriod, enrollment_manager()->GetTimeToNextAttempt()); |
| |
| // Second (successful) enrollment attempt bypasses GCM registration and |
| // ClientAppMetadata fetch because they were performed during the failed |
| // attempt. |
| test_clock()->SetNow(test_clock()->Now() + kFakeRetryPeriod); |
| fake_enrollment_scheduler()->RequestEnrollment( |
| cryptauthv2::ClientMetadata::PERIODIC, base::nullopt /* session_id */); |
| VerifyEnrollmentManagerObserversNotifiedOfStart( |
| 2 /* expected_num_enrollment_started_notifications */); |
| |
| CryptAuthEnrollmentResult second_expected_enrollment_result = |
| CryptAuthEnrollmentResult( |
| CryptAuthEnrollmentResult::ResultCode::kSuccessNoNewKeysNeeded, |
| cryptauthv2::GetClientDirectiveForTest()); |
| |
| expected_client_metadata.set_retry_count(1); |
| FinishEnrollmentAttempt(1u /* expected_enroller_instance_index */, |
| expected_client_metadata, |
| second_expected_enrollment_result); |
| |
| VerifyEnrollmentResults( |
| {first_expected_enrollment_result, second_expected_enrollment_result}); |
| |
| fake_enrollment_scheduler()->set_last_successful_enrollment_time( |
| test_clock()->Now()); |
| fake_enrollment_scheduler()->set_time_to_next_enrollment_request( |
| kFakeRefreshPeriod); |
| fake_enrollment_scheduler()->set_num_consecutive_enrollment_failures(0); |
| EXPECT_EQ(test_clock()->Now(), enrollment_manager()->GetLastEnrollmentTime()); |
| EXPECT_TRUE(enrollment_manager()->IsEnrollmentValid()); |
| EXPECT_EQ(kFakeRefreshPeriod, enrollment_manager()->GetTimeToNextAttempt()); |
| EXPECT_FALSE(enrollment_manager()->IsRecoveringFromFailure()); |
| } |
| |
| TEST_F(DeviceSyncCryptAuthV2EnrollmentManagerImplTest, |
| EnrollmentTriggeredByGcmMessage_Success) { |
| CreateEnrollmentManager(); |
| enrollment_manager()->Start(); |
| |
| RequestEnrollmentThroughGcm(kFakeSessionId); |
| |
| EXPECT_TRUE(enrollment_manager()->IsEnrollmentInProgress()); |
| VerifyEnrollmentManagerObserversNotifiedOfStart( |
| 1 /* expected_num_enrollment_started_notifications */); |
| CompleteGcmRegistration(true /* success */); |
| HandleGetClientAppMetadataRequest(true /* success */); |
| CryptAuthEnrollmentResult expected_enrollment_result( |
| CryptAuthEnrollmentResult::ResultCode::kSuccessNoNewKeysNeeded, |
| cryptauthv2::GetClientDirectiveForTest()); |
| FinishEnrollmentAttempt( |
| 0u /* expected_enroller_instance_index */, |
| cryptauthv2::BuildClientMetadata( |
| 0 /* retry_count */, cryptauthv2::ClientMetadata::SERVER_INITIATED, |
| kFakeSessionId) /* expected_client_metadata */, |
| expected_enrollment_result); |
| VerifyEnrollmentResults({expected_enrollment_result}); |
| } |
| |
| TEST_F(DeviceSyncCryptAuthV2EnrollmentManagerImplTest, |
| EnrollmentTriggeredByGcmMessage_Failure) { |
| CreateEnrollmentManager(); |
| enrollment_manager()->Start(); |
| |
| RequestEnrollmentThroughGcm(kFakeSessionId); |
| |
| EXPECT_TRUE(enrollment_manager()->IsEnrollmentInProgress()); |
| VerifyEnrollmentManagerObserversNotifiedOfStart( |
| 1 /* expected_num_enrollment_started_notifications */); |
| CompleteGcmRegistration(true /* success */); |
| HandleGetClientAppMetadataRequest(true /* success */); |
| CryptAuthEnrollmentResult expected_enrollment_result( |
| CryptAuthEnrollmentResult::ResultCode::kErrorCryptAuthServerOverloaded, |
| base::nullopt /* client_directive */); |
| FinishEnrollmentAttempt( |
| 0u /* expected_enroller_instance_index */, |
| cryptauthv2::BuildClientMetadata( |
| 0 /* retry_count */, cryptauthv2::ClientMetadata::SERVER_INITIATED, |
| kFakeSessionId) /* expected_client_metadata */, |
| expected_enrollment_result); |
| VerifyEnrollmentResults({expected_enrollment_result}); |
| } |
| |
| TEST_F(DeviceSyncCryptAuthV2EnrollmentManagerImplTest, |
| V1UserKeyPairAddedToRegistryOnConstruction) { |
| AddV1UserKeyPairToV1Prefs(kFakeV1PublicKey, kFakeV1PrivateKey); |
| CryptAuthKey expected_user_key_pair_v1( |
| kFakeV1PublicKey, kFakeV1PrivateKey, CryptAuthKey::Status::kActive, |
| cryptauthv2::KeyType::P256, kCryptAuthFixedUserKeyPairHandle); |
| |
| EXPECT_FALSE( |
| key_registry()->GetActiveKey(CryptAuthKeyBundle::Name::kUserKeyPair)); |
| |
| CreateEnrollmentManager(); |
| |
| EXPECT_EQ( |
| expected_user_key_pair_v1, |
| *key_registry()->GetActiveKey(CryptAuthKeyBundle::Name::kUserKeyPair)); |
| EXPECT_EQ(expected_user_key_pair_v1.public_key(), |
| enrollment_manager()->GetUserPublicKey()); |
| EXPECT_EQ(expected_user_key_pair_v1.private_key(), |
| enrollment_manager()->GetUserPrivateKey()); |
| } |
| |
| TEST_F(DeviceSyncCryptAuthV2EnrollmentManagerImplTest, |
| V1UserKeyPairOverwritesV2UserKeyPairOnConstruction) { |
| AddV1UserKeyPairToV1Prefs(kFakeV1PublicKey, kFakeV1PrivateKey); |
| CryptAuthKey expected_user_key_pair_v1( |
| kFakeV1PublicKey, kFakeV1PrivateKey, CryptAuthKey::Status::kActive, |
| cryptauthv2::KeyType::P256, kCryptAuthFixedUserKeyPairHandle); |
| |
| // Add v2 user key pair to registry. |
| CryptAuthKey user_key_pair_v2( |
| kFakeV2PublicKey, kFakeV2PrivateKey, CryptAuthKey::Status::kActive, |
| cryptauthv2::KeyType::P256, kCryptAuthFixedUserKeyPairHandle); |
| key_registry()->AddKey(CryptAuthKeyBundle::Name::kUserKeyPair, |
| user_key_pair_v2); |
| EXPECT_EQ(user_key_pair_v2, *key_registry()->GetActiveKey( |
| CryptAuthKeyBundle::Name::kUserKeyPair)); |
| |
| // A legacy v1 user key pair should overwrite any existing v2 user key pair |
| // when the enrollment manager is constructed. |
| CreateEnrollmentManager(); |
| |
| EXPECT_EQ( |
| expected_user_key_pair_v1, |
| *key_registry()->GetActiveKey(CryptAuthKeyBundle::Name::kUserKeyPair)); |
| EXPECT_EQ(expected_user_key_pair_v1.public_key(), |
| enrollment_manager()->GetUserPublicKey()); |
| EXPECT_EQ(expected_user_key_pair_v1.private_key(), |
| enrollment_manager()->GetUserPrivateKey()); |
| } |
| |
| TEST_F(DeviceSyncCryptAuthV2EnrollmentManagerImplTest, GetUserKeyPair) { |
| CreateEnrollmentManager(); |
| EXPECT_TRUE(enrollment_manager()->GetUserPublicKey().empty()); |
| EXPECT_TRUE(enrollment_manager()->GetUserPrivateKey().empty()); |
| |
| key_registry()->AddKey( |
| CryptAuthKeyBundle::Name::kUserKeyPair, |
| CryptAuthKey(kFakeV2PublicKey, kFakeV2PrivateKey, |
| CryptAuthKey::Status::kActive, cryptauthv2::KeyType::P256, |
| kCryptAuthFixedUserKeyPairHandle)); |
| EXPECT_EQ(kFakeV2PublicKey, enrollment_manager()->GetUserPublicKey()); |
| EXPECT_EQ(kFakeV2PrivateKey, enrollment_manager()->GetUserPrivateKey()); |
| } |
| |
| TEST_F(DeviceSyncCryptAuthV2EnrollmentManagerImplTest, |
| MultipleEnrollmentAttempts) { |
| CreateEnrollmentManager(); |
| enrollment_manager()->Start(); |
| |
| std::vector<cryptauthv2::ClientMetadata::InvocationReason> |
| expected_invocation_reasons; |
| std::vector<CryptAuthEnrollmentResult> expected_enrollment_results; |
| |
| // Successfully enroll for the first time. |
| expected_invocation_reasons.push_back( |
| cryptauthv2::ClientMetadata::INITIALIZATION); |
| expected_enrollment_results.emplace_back( |
| CryptAuthEnrollmentResult::ResultCode::kSuccessNewKeysEnrolled, |
| cryptauthv2::GetClientDirectiveForTest()); |
| fake_enrollment_scheduler()->RequestEnrollment( |
| expected_invocation_reasons.back(), kFakeSessionId); |
| VerifyEnrollmentManagerObserversNotifiedOfStart( |
| 1 /* expected_num_enrollment_started_notifications */); |
| CompleteGcmRegistration(true /* success */); |
| HandleGetClientAppMetadataRequest(true /* success */); |
| FinishEnrollmentAttempt( |
| 0u /* expected_enroller_instance_index */, |
| cryptauthv2::BuildClientMetadata( |
| 0 /* retry_count */, expected_invocation_reasons.back(), |
| kFakeSessionId) /* expected_client_metadata */, |
| expected_enrollment_results.back()); |
| |
| // Fail periodic refresh twice due to overloaded CryptAuth server. |
| expected_invocation_reasons.push_back(cryptauthv2::ClientMetadata::PERIODIC); |
| expected_enrollment_results.emplace_back( |
| CryptAuthEnrollmentResult::ResultCode::kErrorCryptAuthServerOverloaded, |
| cryptauthv2::GetClientDirectiveForTest()); |
| fake_enrollment_scheduler()->RequestEnrollment( |
| expected_invocation_reasons.back(), kFakeSessionId); |
| VerifyEnrollmentManagerObserversNotifiedOfStart( |
| 2 /* expected_num_enrollment_started_notifications */); |
| FinishEnrollmentAttempt( |
| 1u /* expected_enroller_instance_index */, |
| cryptauthv2::BuildClientMetadata( |
| 0 /* retry_count */, expected_invocation_reasons.back(), |
| kFakeSessionId) /* expected_client_metadata */, |
| expected_enrollment_results.back()); |
| fake_enrollment_scheduler()->set_num_consecutive_enrollment_failures(1); |
| fake_enrollment_scheduler()->set_time_to_next_enrollment_request( |
| kFakeRetryPeriod); |
| |
| expected_invocation_reasons.push_back(cryptauthv2::ClientMetadata::PERIODIC); |
| expected_enrollment_results.emplace_back( |
| CryptAuthEnrollmentResult::ResultCode::kErrorCryptAuthServerOverloaded, |
| base::nullopt /* client_directive */); |
| fake_enrollment_scheduler()->RequestEnrollment( |
| expected_invocation_reasons.back(), kFakeSessionId); |
| VerifyEnrollmentManagerObserversNotifiedOfStart( |
| 3 /* expected_num_enrollment_started_notifications */); |
| FinishEnrollmentAttempt( |
| 2u /* expected_enroller_instance_index */, |
| cryptauthv2::BuildClientMetadata( |
| 1 /* retry_count */, expected_invocation_reasons.back(), |
| kFakeSessionId) /* expected_client_metadata */, |
| expected_enrollment_results.back()); |
| fake_enrollment_scheduler()->set_num_consecutive_enrollment_failures(2); |
| fake_enrollment_scheduler()->set_time_to_next_enrollment_request( |
| kFakeRetryPeriod); |
| |
| // While waiting for retry, force a manual enrollment that succeeds. |
| expected_invocation_reasons.push_back(cryptauthv2::ClientMetadata::MANUAL); |
| expected_enrollment_results.emplace_back( |
| CryptAuthEnrollmentResult::ResultCode::kSuccessNoNewKeysNeeded, |
| base::nullopt /* client_directive */); |
| enrollment_manager()->ForceEnrollmentNow(cryptauth::INVOCATION_REASON_MANUAL, |
| base::nullopt /* session_id */); |
| VerifyEnrollmentManagerObserversNotifiedOfStart( |
| 4 /* expected_num_enrollment_started_notifications */); |
| FinishEnrollmentAttempt( |
| 3u /* expected_enroller_instance_index */, |
| cryptauthv2::BuildClientMetadata( |
| 2 /* retry_count */, expected_invocation_reasons.back(), |
| base::nullopt /* session_id */) /* expected_client_metadata */, |
| expected_enrollment_results.back()); |
| |
| VerifyInvocationReasonHistogram(expected_invocation_reasons); |
| VerifyEnrollmentResults(expected_enrollment_results); |
| } |
| |
| } // namespace device_sync |
| |
| } // namespace chromeos |