| // 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/login/challenge_response_auth_keys_loader.h" |
| |
| #include <vector> |
| |
| #include "base/run_loop.h" |
| #include "base/scoped_observer.h" |
| #include "base/test/bind_test_util.h" |
| #include "chrome/browser/chromeos/certificate_provider/test_certificate_provider_extension.h" |
| #include "chrome/browser/chromeos/certificate_provider/test_certificate_provider_extension_login_screen_mixin.h" |
| #include "chrome/browser/chromeos/login/test/device_state_mixin.h" |
| #include "chrome/browser/chromeos/login/test/oobe_base_test.h" |
| #include "chrome/browser/chromeos/profiles/profile_helper.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chromeos/login/auth/challenge_response/known_user_pref_utils.h" |
| #include "chromeos/login/auth/challenge_response_key.h" |
| #include "components/account_id/account_id.h" |
| #include "components/prefs/pref_change_registrar.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/user_manager/known_user.h" |
| #include "content/public/test/browser_test.h" |
| #include "extensions/browser/extension_host.h" |
| #include "extensions/browser/pref_names.h" |
| #include "extensions/browser/process_manager.h" |
| #include "extensions/browser/process_manager_observer.h" |
| |
| namespace chromeos { |
| |
| namespace { |
| |
| constexpr char kUserEmail[] = "testuser@example.com"; |
| |
| content::BrowserContext* GetBrowserContext() { |
| return ProfileHelper::GetSigninProfile()->GetOriginalProfile(); |
| } |
| |
| extensions::ProcessManager* GetProcessManager() { |
| return extensions::ProcessManager::Get(GetBrowserContext()); |
| } |
| |
| } // namespace |
| |
| class ChallengeResponseAuthKeysLoaderBrowserTest : public OobeBaseTest { |
| public: |
| ChallengeResponseAuthKeysLoaderBrowserTest() { |
| // Required for TestCertificateProviderExtensionLoginScreenMixin |
| needs_background_networking_ = true; |
| } |
| ChallengeResponseAuthKeysLoaderBrowserTest( |
| const ChallengeResponseAuthKeysLoaderBrowserTest&) = delete; |
| ChallengeResponseAuthKeysLoaderBrowserTest& operator=( |
| const ChallengeResponseAuthKeysLoaderBrowserTest&) = delete; |
| ~ChallengeResponseAuthKeysLoaderBrowserTest() override = default; |
| |
| void SetUpOnMainThread() override { |
| OobeBaseTest::SetUpOnMainThread(); |
| challenge_response_auth_keys_loader_ = |
| std::make_unique<ChallengeResponseAuthKeysLoader>(); |
| challenge_response_auth_keys_loader_->SetMaxWaitTimeForTesting( |
| base::TimeDelta::Max()); |
| |
| // Register the ChallengeResponseKey for the user. |
| user_manager::known_user::SaveKnownUser(account_id_); |
| } |
| |
| void TearDownOnMainThread() override { |
| challenge_response_auth_keys_loader_.reset(); |
| OobeBaseTest::TearDownOnMainThread(); |
| } |
| |
| void RegisterChallengeResponseKey(bool with_extension_id) { |
| std::vector<ChallengeResponseKey> challenge_response_keys; |
| ChallengeResponseKey challenge_response_key; |
| challenge_response_key.set_public_key_spki_der(GetSpki()); |
| if (with_extension_id) { |
| challenge_response_key.set_extension_id( |
| cert_provider_extension_mixin_.GetExtensionId()); |
| } |
| |
| challenge_response_keys.push_back(challenge_response_key); |
| base::Value challenge_response_keys_value = |
| SerializeChallengeResponseKeysForKnownUser(challenge_response_keys); |
| user_manager::known_user::SetChallengeResponseKeys( |
| account_id_, std::move(challenge_response_keys_value)); |
| } |
| |
| void OnAvailableKeysLoaded( |
| base::RepeatingClosure run_loop_quit_closure, |
| std::vector<ChallengeResponseKey>* out_challenge_response_keys, |
| std::vector<ChallengeResponseKey> challenge_response_keys) { |
| out_challenge_response_keys->swap(challenge_response_keys); |
| run_loop_quit_closure.Run(); |
| } |
| |
| ChallengeResponseAuthKeysLoader::LoadAvailableKeysCallback CreateCallback( |
| base::RepeatingClosure run_loop_quit_closure, |
| std::vector<ChallengeResponseKey>* challenge_response_keys) { |
| return base::BindOnce( |
| &ChallengeResponseAuthKeysLoaderBrowserTest::OnAvailableKeysLoaded, |
| weak_ptr_factory_.GetWeakPtr(), run_loop_quit_closure, |
| challenge_response_keys); |
| } |
| |
| void InstallExtension(bool wait_on_extension_loaded) { |
| cert_provider_extension_mixin_.AddExtensionForForceInstallation(); |
| if (wait_on_extension_loaded) { |
| cert_provider_extension_mixin_.WaitUntilExtensionLoaded(); |
| } else { |
| // Even though we do not want to wait until the extension is fully ready, |
| // wait until the extension has been registered as a force-installed |
| // login-screen extension in profile preferences. |
| WaitUntilPrefUpdated(); |
| } |
| } |
| |
| void PrefChangedCallback() { |
| const PrefService* prefs = ProfileHelper::GetSigninProfile()->GetPrefs(); |
| const PrefService::Preference* pref = |
| prefs->FindPreference(extensions::pref_names::kLoginScreenExtensions); |
| if (pref->IsManaged() && wait_for_pref_change_run_loop_) { |
| wait_for_pref_change_run_loop_->Quit(); |
| } |
| } |
| |
| void CheckExtensionInstallPolicyApplied() { |
| // Check that the extension is registered as a force-installed login-screen |
| // extension. |
| const PrefService* const prefs = |
| ProfileHelper::GetSigninProfile()->GetPrefs(); |
| const PrefService::Preference* const pref = |
| prefs->FindPreference(extensions::pref_names::kLoginScreenExtensions); |
| EXPECT_TRUE(pref); |
| EXPECT_TRUE(pref->IsManaged()); |
| EXPECT_EQ(pref->GetType(), base::Value::Type::DICTIONARY); |
| EXPECT_EQ(pref->GetValue()->DictSize(), static_cast<size_t>(1)); |
| |
| for (const auto& item : pref->GetValue()->DictItems()) { |
| EXPECT_EQ(item.first, GetExtensionId()); |
| } |
| } |
| |
| std::vector<ChallengeResponseKey> LoadChallengeResponseKeys() { |
| base::RunLoop run_loop; |
| std::vector<ChallengeResponseKey> challenge_response_keys; |
| challenge_response_auth_keys_loader_->LoadAvailableKeys( |
| account_id_, |
| CreateCallback(run_loop.QuitClosure(), &challenge_response_keys)); |
| run_loop.Run(); |
| return challenge_response_keys; |
| } |
| |
| std::string GetSpki() const { |
| return cert_provider_extension_mixin_.test_certificate_provider_extension() |
| ->GetCertificateSpki(); |
| } |
| |
| std::string GetExtensionId() const { |
| return cert_provider_extension_mixin_.GetExtensionId(); |
| } |
| |
| AccountId account_id() const { return account_id_; } |
| |
| ChallengeResponseAuthKeysLoader* challenge_response_auth_keys_loader() { |
| return challenge_response_auth_keys_loader_.get(); |
| } |
| |
| void DeleteChallengeResponseAuthKeysLoader() { |
| challenge_response_auth_keys_loader_.reset(); |
| } |
| |
| private: |
| void WaitUntilPrefUpdated() { |
| PrefChangeRegistrar pref_change_registrar; |
| pref_change_registrar.Init(ProfileHelper::GetSigninProfile()->GetPrefs()); |
| pref_change_registrar.Add( |
| extensions::pref_names::kLoginScreenExtensions, |
| base::BindRepeating( |
| &ChallengeResponseAuthKeysLoaderBrowserTest::PrefChangedCallback, |
| weak_ptr_factory_.GetWeakPtr())); |
| const PrefService* prefs = ProfileHelper::GetSigninProfile()->GetPrefs(); |
| const PrefService::Preference* pref = |
| prefs->FindPreference(extensions::pref_names::kLoginScreenExtensions); |
| if (!pref->IsManaged()) { |
| base::RunLoop wait_for_pref_change_run_loop; |
| wait_for_pref_change_run_loop_ = &wait_for_pref_change_run_loop; |
| wait_for_pref_change_run_loop.Run(); |
| } |
| } |
| |
| const AccountId account_id_{AccountId::FromUserEmail(kUserEmail)}; |
| |
| DeviceStateMixin device_state_mixin_{ |
| &mixin_host_, DeviceStateMixin::State::OOBE_COMPLETED_CLOUD_ENROLLED}; |
| TestCertificateProviderExtensionLoginScreenMixin |
| cert_provider_extension_mixin_{&mixin_host_, &device_state_mixin_, |
| /*load_extension_immediately=*/false}; |
| base::RunLoop* wait_for_pref_change_run_loop_ = nullptr; |
| |
| std::unique_ptr<ChallengeResponseAuthKeysLoader> |
| challenge_response_auth_keys_loader_; |
| |
| base::WeakPtrFactory<ChallengeResponseAuthKeysLoaderBrowserTest> |
| weak_ptr_factory_{this}; |
| }; |
| |
| // Tests the error case when no key is registered for the current user. |
| IN_PROC_BROWSER_TEST_F(ChallengeResponseAuthKeysLoaderBrowserTest, |
| NoKeyRegistered) { |
| InstallExtension(/*wait_on_extension_loaded=*/true); |
| CheckExtensionInstallPolicyApplied(); |
| |
| // Challenge Response Auth Keys cannot be loaded. |
| EXPECT_FALSE( |
| ChallengeResponseAuthKeysLoader::CanAuthenticateUser(account_id())); |
| EXPECT_EQ(LoadChallengeResponseKeys().size(), static_cast<size_t>(0)); |
| } |
| |
| // Tests the error case when no extension providing keys is installed. |
| IN_PROC_BROWSER_TEST_F(ChallengeResponseAuthKeysLoaderBrowserTest, |
| NoExtensions) { |
| RegisterChallengeResponseKey(/*with_extension_id=*/true); |
| |
| // Challenge Response Auth Keys can be loaded. |
| EXPECT_TRUE( |
| ChallengeResponseAuthKeysLoader::CanAuthenticateUser(account_id())); |
| |
| // LoadAvailableKeys returns no keys, since there's no extension available. |
| EXPECT_EQ(LoadChallengeResponseKeys().size(), static_cast<size_t>(0)); |
| } |
| |
| // Tests that auth keys can be loaded with an extension providing them already |
| // in place. |
| IN_PROC_BROWSER_TEST_F(ChallengeResponseAuthKeysLoaderBrowserTest, |
| LoadingKeysAfterExtensionIsInstalled) { |
| RegisterChallengeResponseKey(/*with_extension_id=*/true); |
| InstallExtension(/*wait_on_extension_loaded=*/true); |
| CheckExtensionInstallPolicyApplied(); |
| |
| // Challenge Response Auth Keys can be loaded. |
| EXPECT_TRUE( |
| ChallengeResponseAuthKeysLoader::CanAuthenticateUser(account_id())); |
| |
| // LoadAvailableKeys returns the expected keys. |
| std::vector<ChallengeResponseKey> challenge_response_keys = |
| LoadChallengeResponseKeys(); |
| ASSERT_EQ(challenge_response_keys.size(), static_cast<size_t>(1)); |
| EXPECT_EQ(challenge_response_keys.at(0).extension_id(), GetExtensionId()); |
| EXPECT_EQ(challenge_response_keys.at(0).public_key_spki_der(), GetSpki()); |
| } |
| |
| // Tests that auth keys can be loaded while the extension providing them is is |
| // already registered as force-installed, but installation is not yet complete. |
| // ChallengeResponseAuthKeysLoader needs to wait on the installation to complete |
| // instead of incorrectly responding that there are no available certificates. |
| IN_PROC_BROWSER_TEST_F(ChallengeResponseAuthKeysLoaderBrowserTest, |
| LoadingKeysWhileExtensionIsBeingInstalled) { |
| RegisterChallengeResponseKey(/*with_extension_id=*/true); |
| InstallExtension(/*wait_on_extension_loaded=*/false); |
| CheckExtensionInstallPolicyApplied(); |
| |
| // Challenge Response Auth Keys can be loaded. |
| EXPECT_TRUE( |
| ChallengeResponseAuthKeysLoader::CanAuthenticateUser(account_id())); |
| |
| // LoadAvailableKeys returns the expected keys. |
| std::vector<ChallengeResponseKey> challenge_response_keys = |
| LoadChallengeResponseKeys(); |
| ASSERT_EQ(challenge_response_keys.size(), static_cast<size_t>(1)); |
| EXPECT_EQ(challenge_response_keys.at(0).extension_id(), GetExtensionId()); |
| EXPECT_EQ(challenge_response_keys.at(0).public_key_spki_der(), GetSpki()); |
| } |
| |
| // Tests running into the timeout when waiting for extensions. |
| IN_PROC_BROWSER_TEST_F(ChallengeResponseAuthKeysLoaderBrowserTest, |
| TimeoutWhileWaitingOnExtensionInstallation) { |
| RegisterChallengeResponseKey(/*with_extension_id=*/true); |
| InstallExtension(/*wait_on_extension_loaded=*/false); |
| challenge_response_auth_keys_loader()->SetMaxWaitTimeForTesting( |
| base::TimeDelta::Min()); |
| |
| // Challenge Response Auth Keys can be loaded. |
| EXPECT_TRUE( |
| ChallengeResponseAuthKeysLoader::CanAuthenticateUser(account_id())); |
| |
| // LoadAvailableKeys returns before any keys are available. |
| std::vector<ChallengeResponseKey> challenge_response_keys = |
| LoadChallengeResponseKeys(); |
| EXPECT_EQ(challenge_response_keys.size(), static_cast<size_t>(0)); |
| } |
| |
| // Tests flow when there is no stored extension_id, for backward compatibility. |
| IN_PROC_BROWSER_TEST_F(ChallengeResponseAuthKeysLoaderBrowserTest, |
| LoadingKeysWithoutExtensionId) { |
| RegisterChallengeResponseKey(/*with_extension_id=*/false); |
| InstallExtension(/*wait_on_extension_loaded=*/true); |
| CheckExtensionInstallPolicyApplied(); |
| |
| // Challenge Response Auth Keys can be loaded. |
| EXPECT_TRUE( |
| ChallengeResponseAuthKeysLoader::CanAuthenticateUser(account_id())); |
| |
| // LoadAvailableKeys returns the expected keys. |
| std::vector<ChallengeResponseKey> challenge_response_keys = |
| LoadChallengeResponseKeys(); |
| ASSERT_EQ(challenge_response_keys.size(), static_cast<size_t>(1)); |
| EXPECT_EQ(challenge_response_keys.at(0).extension_id(), GetExtensionId()); |
| EXPECT_EQ(challenge_response_keys.at(0).public_key_spki_der(), GetSpki()); |
| } |
| |
| // Tests the case when the loader is destroyed before the operation completes. |
| IN_PROC_BROWSER_TEST_F(ChallengeResponseAuthKeysLoaderBrowserTest, |
| DestroyedBeforeCompletion) { |
| RegisterChallengeResponseKey(/*with_extension_id=*/true); |
| InstallExtension(/*wait_on_extension_loaded=*/false); |
| CheckExtensionInstallPolicyApplied(); |
| |
| // Challenge Response Auth Keys can be loaded. |
| EXPECT_TRUE( |
| ChallengeResponseAuthKeysLoader::CanAuthenticateUser(account_id())); |
| |
| // Start the LoadAvailableKeys operation. The operation is expected to never |
| // complete. |
| challenge_response_auth_keys_loader()->LoadAvailableKeys( |
| account_id(), |
| base::BindOnce( |
| [](std::vector<ChallengeResponseKey> challenge_response_keys) { |
| ADD_FAILURE(); |
| })); |
| // Destroy the loader immediately. |
| DeleteChallengeResponseAuthKeysLoader(); |
| } |
| |
| class ChallengeResponseExtensionLoadObserverTest |
| : public ChallengeResponseAuthKeysLoaderBrowserTest, |
| public extensions::ProcessManagerObserver { |
| public: |
| ChallengeResponseExtensionLoadObserverTest() = default; |
| ChallengeResponseExtensionLoadObserverTest( |
| const ChallengeResponseExtensionLoadObserverTest&) = delete; |
| ChallengeResponseExtensionLoadObserverTest& operator=( |
| const ChallengeResponseExtensionLoadObserverTest&) = delete; |
| ~ChallengeResponseExtensionLoadObserverTest() override = default; |
| |
| void SetUpOnMainThread() override { |
| ChallengeResponseAuthKeysLoaderBrowserTest::SetUpOnMainThread(); |
| process_manager_observer_.Add(GetProcessManager()); |
| } |
| |
| void TearDownOnMainThread() override { |
| process_manager_observer_.RemoveAll(); |
| ChallengeResponseAuthKeysLoaderBrowserTest::TearDownOnMainThread(); |
| } |
| |
| void SetExtensionHostCreatedLoop(base::RunLoop* run_loop) { |
| extension_host_created_loop_ = run_loop; |
| } |
| |
| void StartLoadingChallengeResponseKeys(base::RunLoop* run_loop) { |
| // Result should be empty and will be discarded. |
| challenge_response_auth_keys_loader()->LoadAvailableKeys( |
| account_id(), |
| base::BindLambdaForTesting([=](std::vector<ChallengeResponseKey> keys) { |
| EXPECT_TRUE(keys.empty()); |
| run_loop->Quit(); |
| })); |
| } |
| |
| void WaitForExtensionHostAndDestroy() { |
| extension_host_created_loop_->Run(); |
| // Simulate Extension Host dying unexpectedly. |
| // Destroying this triggers a notification for the extension subsystem, |
| // which will deregister the host. |
| delete extension_host_; |
| extension_host_ = nullptr; |
| } |
| |
| // extensions::ProcessManagerObserver |
| |
| void OnBackgroundHostCreated( |
| extensions::ExtensionHost* extension_host) override { |
| if (extension_host->extension_id() == GetExtensionId()) { |
| extension_host_ = extension_host; |
| extension_host_created_loop_->Quit(); |
| } |
| } |
| |
| void OnProcessManagerShutdown(extensions::ProcessManager* manager) override { |
| process_manager_observer_.Remove(manager); |
| } |
| |
| private: |
| base::RunLoop* extension_host_created_loop_ = nullptr; |
| extensions::ExtensionHost* extension_host_ = nullptr; |
| ScopedObserver<extensions::ProcessManager, extensions::ProcessManagerObserver> |
| process_manager_observer_{this}; |
| }; |
| |
| // Tests that observers get cleaned up properly if the observed ExtensionHost |
| // is destroyed earlier than the observing ExtensionLoadObserver. |
| IN_PROC_BROWSER_TEST_F(ChallengeResponseExtensionLoadObserverTest, |
| ExtensionHostDestroyedEarly) { |
| base::RunLoop extension_host_created_loop; |
| SetExtensionHostCreatedLoop(&extension_host_created_loop); |
| |
| RegisterChallengeResponseKey(/*with_extension_id=*/true); |
| InstallExtension(/*wait_on_extension_loaded=*/false); |
| CheckExtensionInstallPolicyApplied(); |
| |
| // Challenge Response Auth Keys can be loaded. |
| EXPECT_TRUE( |
| ChallengeResponseAuthKeysLoader::CanAuthenticateUser(account_id())); |
| |
| base::RunLoop load_challenge_response_keys_complete; |
| StartLoadingChallengeResponseKeys(&load_challenge_response_keys_complete); |
| WaitForExtensionHostAndDestroy(); |
| load_challenge_response_keys_complete.Run(); |
| } |
| |
| } // namespace chromeos |