| // 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 "chrome/browser/chromeos/login/challenge_response_auth_keys_loader.h" |
| |
| #include <cstdint> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/containers/flat_set.h" |
| #include "base/optional.h" |
| #include "base/scoped_observer.h" |
| #include "base/time/time.h" |
| #include "base/timer/timer.h" |
| #include "base/values.h" |
| #include "chrome/browser/chromeos/certificate_provider/certificate_provider.h" |
| #include "chrome/browser/chromeos/certificate_provider/certificate_provider_service.h" |
| #include "chrome/browser/chromeos/certificate_provider/certificate_provider_service_factory.h" |
| #include "chrome/browser/chromeos/profiles/profile_helper.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chromeos/login/auth/challenge_response/cert_utils.h" |
| #include "chromeos/login/auth/challenge_response/known_user_pref_utils.h" |
| #include "components/account_id/account_id.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/user_manager/known_user.h" |
| #include "extensions/browser/extension_host.h" |
| #include "extensions/browser/extension_host_observer.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/extension_registry_observer.h" |
| #include "extensions/browser/lazy_context_id.h" |
| #include "extensions/browser/lazy_context_task_queue.h" |
| #include "extensions/browser/pref_names.h" |
| #include "extensions/browser/process_manager.h" |
| #include "extensions/browser/process_manager_observer.h" |
| #include "extensions/common/manifest_handlers/background_info.h" |
| |
| namespace chromeos { |
| |
| namespace { |
| |
| constexpr base::TimeDelta kDefaultMaximumExtensionLoadWaitingTime = |
| base::TimeDelta::FromSeconds(5); |
| |
| base::flat_set<std::string> GetLoginScreenPolicyExtensionIds() { |
| DCHECK(ProfileHelper::IsSigninProfileInitialized()); |
| |
| const PrefService* const prefs = |
| ProfileHelper::GetSigninProfile()->GetPrefs(); |
| DCHECK_EQ(prefs->GetAllPrefStoresInitializationStatus(), |
| PrefService::INITIALIZATION_STATUS_SUCCESS); |
| |
| const PrefService::Preference* const pref = |
| prefs->FindPreference(extensions::pref_names::kLoginScreenExtensions); |
| if (!pref || !pref->IsManaged() || |
| pref->GetType() != base::Value::Type::DICTIONARY) |
| return {}; |
| |
| base::flat_set<std::string> extension_ids; |
| for (const auto& item : pref->GetValue()->DictItems()) |
| extension_ids.insert(item.first); |
| return extension_ids; |
| } |
| |
| content::BrowserContext* GetBrowserContext() { |
| return ProfileHelper::GetSigninProfile()->GetOriginalProfile(); |
| } |
| |
| extensions::ExtensionRegistry* GetExtensionRegistry() { |
| return extensions::ExtensionRegistry::Get(GetBrowserContext()); |
| } |
| |
| extensions::ProcessManager* GetProcessManager() { |
| return extensions::ProcessManager::Get(GetBrowserContext()); |
| } |
| |
| // Loads the persistently stored information about the challenge-response keys |
| // that can be used for authenticating the user. |
| void LoadStoredChallengeResponseSpkiKeysForUser( |
| const AccountId& account_id, |
| std::vector<std::string>* spki_items, |
| base::flat_set<std::string>* extension_ids) { |
| const base::Value known_user_value = |
| user_manager::known_user::GetChallengeResponseKeys(account_id); |
| std::vector<DeserializedChallengeResponseKey> |
| deserialized_challenge_response_keys; |
| DeserializeChallengeResponseKeyFromKnownUser( |
| known_user_value, &deserialized_challenge_response_keys); |
| for (const DeserializedChallengeResponseKey& challenge_response_key : |
| deserialized_challenge_response_keys) { |
| if (!challenge_response_key.public_key_spki_der.empty()) { |
| spki_items->push_back(challenge_response_key.public_key_spki_der); |
| } |
| if (!challenge_response_key.extension_id.empty()) { |
| extension_ids->insert(challenge_response_key.extension_id); |
| } |
| } |
| } |
| |
| // Returns the certificate provider service that should be used for querying the |
| // currently available cryptographic keys. |
| // The sign-in profile is used since it's where the needed extensions are |
| // installed (e.g., for the smart card based login they are force-installed via |
| // the DeviceLoginScreenExtensions admin policy). |
| CertificateProviderService* GetCertificateProviderService() { |
| return CertificateProviderServiceFactory::GetForBrowserContext( |
| ProfileHelper::GetSigninProfile()); |
| } |
| |
| // Maps from the TLS 1.3 SignatureScheme values into the challenge-response key |
| // algorithm list. |
| std::vector<ChallengeResponseKey::SignatureAlgorithm> MakeAlgorithmListFromSsl( |
| const std::vector<uint16_t>& ssl_algorithms) { |
| std::vector<ChallengeResponseKey::SignatureAlgorithm> |
| challenge_response_algorithms; |
| for (auto ssl_algorithm : ssl_algorithms) { |
| base::Optional<ChallengeResponseKey::SignatureAlgorithm> algorithm = |
| GetChallengeResponseKeyAlgorithmFromSsl(ssl_algorithm); |
| if (algorithm) |
| challenge_response_algorithms.push_back(*algorithm); |
| } |
| return challenge_response_algorithms; |
| } |
| |
| // Can observe the installation and loading of force-installed extensions. |
| // Allows to wait until the background pages of a set of extensions are loaded. |
| // |
| // When waiting for an extension that is not yet installed, the following chain |
| // of calls should happen, either directly or trough some observers: |
| // 1. StartWaiting |
| // 2. OnExtensionInstalled |
| // 3. OnBackgroundHostCreated |
| // 4. OnExtensionBackgroundHostActive |
| // 5. OnExtensionHostDidStopFirstLoad |
| // 6. StopWaitingOnExtension |
| // 7. TriggerExtensionsReadyCallback |
| // 8. OnStopWaiting |
| // Depending on the state of installation/loading when observation starts, some |
| // of these steps can be omitted. |
| class ExtensionLoadObserver final |
| : public extensions::ProcessManagerObserver, |
| public extensions::ExtensionHostObserver, |
| public extensions::ExtensionRegistryObserver { |
| public: |
| // Waits until all extensions in |extension_ids| are ready and invokes |
| // |callback| afterwards. |
| static void WaitUntilExtensionsReady( |
| const base::flat_set<std::string>& extension_ids, |
| base::TimeDelta maximum_waiting_time, |
| base::OnceClosure callback) { |
| std::unique_ptr<ExtensionLoadObserver> extension_load_observer = |
| std::make_unique<ExtensionLoadObserver>(maximum_waiting_time); |
| ExtensionLoadObserver* extension_load_observer_ptr = |
| extension_load_observer.get(); |
| // Starts waiting asynchronously. The object is kept alive since it is bound |
| // to the callback. |
| extension_load_observer_ptr->StartWaiting( |
| extension_ids, base::BindOnce(&ExtensionLoadObserver::OnStopWaiting, |
| std::move(extension_load_observer), |
| std::move(callback))); |
| } |
| |
| explicit ExtensionLoadObserver(base::TimeDelta maximum_waiting_time) |
| : maximum_waiting_time_(maximum_waiting_time) { |
| process_manager_observer_.Add(GetProcessManager()); |
| extension_registry_observer_.Add(GetExtensionRegistry()); |
| } |
| ExtensionLoadObserver(const ExtensionLoadObserver&) = delete; |
| ExtensionLoadObserver& operator=(const ExtensionLoadObserver&) = delete; |
| ~ExtensionLoadObserver() override = default; |
| |
| // Observers that observe extension installation and loading in order to delay |
| // requests until all relevant extensions are ready. |
| |
| // extensions::ExtensionRegistryObserver |
| |
| void OnExtensionInstalled(content::BrowserContext* browser_context, |
| const extensions::Extension* extension, |
| bool is_update) override { |
| if (!extensions_waited_for_.contains(extension->id())) |
| return; |
| |
| // If the extension does not have a background page, do not wait on it since |
| // we do not know how it indicates readiness. |
| if (!extensions::BackgroundInfo::HasBackgroundPage(extension)) { |
| StopWaitingOnExtension(extension->id()); |
| return; |
| } |
| |
| // Ensure that the extension's background host is active. |
| const extensions::LazyContextId context_id(browser_context, |
| extension->id()); |
| extensions::LazyContextTaskQueue* queue = context_id.GetTaskQueue(); |
| if (!queue->ShouldEnqueueTask(browser_context, extension)) { |
| // The background host already exists. |
| OnExtensionBackgroundHostActive(extension->id()); |
| } else { |
| // In case the extension got suspended or isn't ready yet, wake it up and |
| // wait until it is ready. |
| queue->AddPendingTask( |
| context_id, |
| base::BindOnce( |
| &ExtensionLoadObserver::OnExtensionBackgroundHostActive, |
| weak_ptr_factory_.GetWeakPtr(), extension->id())); |
| } |
| } |
| |
| void OnShutdown(extensions::ExtensionRegistry* registry) override { |
| extension_registry_observer_.Remove(registry); |
| TriggerExtensionsReadyCallback(); |
| } |
| |
| // extensions::ProcessManagerObserver |
| |
| void OnBackgroundHostCreated( |
| extensions::ExtensionHost* extension_host) override { |
| if (extensions_waited_for_.contains(extension_host->extension_id())) |
| OnExtensionBackgroundHostActive(extension_host->extension_id()); |
| } |
| |
| void OnProcessManagerShutdown(extensions::ProcessManager* manager) override { |
| process_manager_observer_.Remove(manager); |
| TriggerExtensionsReadyCallback(); |
| } |
| |
| // extensions::ExtensionHostObserver |
| |
| void OnExtensionHostDestroyed(extensions::ExtensionHost* host) override { |
| DCHECK(extension_host_observer_.IsObserving(host)); |
| extension_host_observer_.Remove(host); |
| StopWaitingOnExtension(host->extension_id()); |
| } |
| |
| void OnExtensionHostDidStopFirstLoad( |
| const extensions::ExtensionHost* host) override { |
| StopWaitingOnExtension(host->extension_id()); |
| } |
| |
| private: |
| static void OnStopWaiting( |
| std::unique_ptr<ExtensionLoadObserver> extension_load_observer, |
| base::OnceClosure callback) { |
| std::move(callback).Run(); |
| } |
| |
| // Waits until all extensions in |extension_ids| are ready and invokes |
| // |callback| afterwards. |
| void StartWaiting(const base::flat_set<std::string>& extension_ids, |
| base::OnceClosure callback) { |
| extensions_ready_callback_ = std::move(callback); |
| |
| // Do not wait longer than |maximum_waiting_time_|. |
| stop_waiting_timer_.Start( |
| FROM_HERE, maximum_waiting_time_, this, |
| &ExtensionLoadObserver::TriggerExtensionsReadyCallback); |
| |
| base::flat_set<std::string> login_screen_policy_extension_ids = |
| GetLoginScreenPolicyExtensionIds(); |
| // Get all ids that are both in |extension_ids| and |
| // |login_screen_policy_extension_ids|. |
| std::vector<std::string> extension_ids_to_wait_for; |
| for (const std::string& extension_id : extension_ids) { |
| if (login_screen_policy_extension_ids.contains(extension_id)) { |
| extension_ids_to_wait_for.push_back(extension_id); |
| } |
| } |
| |
| extensions_waited_for_.insert(extension_ids_to_wait_for.begin(), |
| extension_ids_to_wait_for.end()); |
| |
| if (extensions_waited_for_.empty()) { |
| TriggerExtensionsReadyCallback(); |
| return; |
| } |
| |
| for (const std::string& extension_id : extension_ids_to_wait_for) { |
| const extensions::Extension* extension = |
| GetExtensionRegistry()->GetInstalledExtension(extension_id); |
| if (extension) { |
| OnExtensionInstalled(GetBrowserContext(), extension, |
| /*is_update=*/false); |
| } |
| } |
| // OnExtensionInstalled() can cause |this| to be destroyed, so it needs to |
| // be the last call of the method. |
| } |
| |
| void OnExtensionBackgroundHostActive( |
| const std::string& extension_id, |
| std::unique_ptr<extensions::LazyContextTaskQueue::ContextInfo> params = |
| nullptr /*ignored*/) { |
| extensions::ExtensionHost* extension_host = |
| GetProcessManager()->GetBackgroundHostForExtension(extension_id); |
| if (!extension_host) { |
| // Generally this should not happen, but better safe than sorry. |
| NOTREACHED(); |
| return; |
| } |
| |
| if (extension_host->has_loaded_once()) { |
| StopWaitingOnExtension(extension_id); |
| return; |
| } |
| |
| // Observe first load of background page. |
| if (!extension_host_observer_.IsObserving(extension_host)) { |
| extension_host_observer_.Add(extension_host); |
| } |
| } |
| |
| void StopWaitingOnExtension(const std::string& extension_id) { |
| if (!extensions_waited_for_.contains(extension_id)) { |
| NOTREACHED(); |
| return; |
| } |
| |
| extensions_waited_for_.erase(extension_id); |
| if (extensions_waited_for_.empty()) { |
| TriggerExtensionsReadyCallback(); |
| } |
| } |
| |
| // Triggers the callback. |this| may be destroyed after this function. |
| void TriggerExtensionsReadyCallback() { |
| if (extensions_ready_callback_) { |
| std::move(extensions_ready_callback_).Run(); |
| } |
| } |
| |
| const base::TimeDelta maximum_waiting_time_; |
| |
| base::OnceClosure extensions_ready_callback_; |
| base::OneShotTimer stop_waiting_timer_; |
| // Ids of all extensions that are necessary but not yet ready. |
| base::flat_set<std::string> extensions_waited_for_; |
| |
| ScopedObserver<extensions::ProcessManager, extensions::ProcessManagerObserver> |
| process_manager_observer_{this}; |
| ScopedObserver<extensions::ExtensionHost, extensions::ExtensionHostObserver> |
| extension_host_observer_{this}; |
| ScopedObserver<extensions::ExtensionRegistry, |
| extensions::ExtensionRegistryObserver> |
| extension_registry_observer_{this}; |
| |
| base::WeakPtrFactory<ExtensionLoadObserver> weak_ptr_factory_{this}; |
| }; |
| |
| } // namespace |
| |
| // static |
| bool ChallengeResponseAuthKeysLoader::CanAuthenticateUser( |
| const AccountId& account_id) { |
| std::vector<std::string> suitable_public_key_spki_items; |
| base::flat_set<std::string> extension_ids_ignored; |
| LoadStoredChallengeResponseSpkiKeysForUser( |
| account_id, &suitable_public_key_spki_items, &extension_ids_ignored); |
| return !suitable_public_key_spki_items.empty(); |
| } |
| |
| ChallengeResponseAuthKeysLoader::ChallengeResponseAuthKeysLoader() |
| : maximum_extension_load_waiting_time_( |
| kDefaultMaximumExtensionLoadWaitingTime) {} |
| |
| ChallengeResponseAuthKeysLoader::~ChallengeResponseAuthKeysLoader() = default; |
| |
| void ChallengeResponseAuthKeysLoader::LoadAvailableKeys( |
| const AccountId& account_id, |
| LoadAvailableKeysCallback callback) { |
| // Load the list of public keys of the cryptographic keys that can be used |
| // for authenticating the user. |
| std::vector<std::string> suitable_public_key_spki_items; |
| base::flat_set<std::string> extension_ids; |
| LoadStoredChallengeResponseSpkiKeysForUser( |
| account_id, &suitable_public_key_spki_items, &extension_ids); |
| if (suitable_public_key_spki_items.empty()) { |
| // This user's profile doesn't support challenge-response authentication. |
| std::move(callback).Run(/*challenge_response_keys=*/{}); |
| return; |
| } |
| |
| // Wait until the extensions that are needed to sign the keys are ready. |
| ExtensionLoadObserver::WaitUntilExtensionsReady( |
| extension_ids, maximum_extension_load_waiting_time_, |
| base::BindOnce(&ChallengeResponseAuthKeysLoader:: |
| ContinueLoadAvailableKeysExtensionsLoaded, |
| weak_ptr_factory_.GetWeakPtr(), account_id, |
| std::move(suitable_public_key_spki_items), |
| std::move(callback))); |
| } |
| |
| void ChallengeResponseAuthKeysLoader::ContinueLoadAvailableKeysExtensionsLoaded( |
| const AccountId& account_id, |
| const std::vector<std::string>& suitable_public_key_spki_items, |
| LoadAvailableKeysCallback callback) { |
| // Asynchronously poll all certificate providers to get the list of |
| // currently available cryptographic keys. |
| std::unique_ptr<CertificateProvider> cert_provider = |
| GetCertificateProviderService()->CreateCertificateProvider(); |
| cert_provider->GetCertificates(base::BindOnce( |
| &ChallengeResponseAuthKeysLoader::ContinueLoadAvailableKeysWithCerts, |
| weak_ptr_factory_.GetWeakPtr(), account_id, |
| std::move(suitable_public_key_spki_items), std::move(callback))); |
| } |
| |
| void ChallengeResponseAuthKeysLoader::ContinueLoadAvailableKeysWithCerts( |
| const AccountId& account_id, |
| const std::vector<std::string>& suitable_public_key_spki_items, |
| LoadAvailableKeysCallback callback, |
| net::ClientCertIdentityList /* cert_identities */) { |
| CertificateProviderService* const cert_provider_service = |
| GetCertificateProviderService(); |
| std::vector<ChallengeResponseKey> filtered_keys; |
| // Filter those of the currently available cryptographic keys that can be used |
| // for authenticating the user. Also fill out for the selected keys the |
| // currently available cryptographic signature algorithms. |
| for (const auto& suitable_spki : suitable_public_key_spki_items) { |
| std::vector<uint16_t> supported_ssl_algorithms; |
| std::string extension_id; |
| cert_provider_service->LookUpSpki(suitable_spki, &supported_ssl_algorithms, |
| &extension_id); |
| if (supported_ssl_algorithms.empty()) { |
| // This key is not currently exposed by any certificate provider or, |
| // potentially, is exposed but without supporting any signature |
| // algorithm. |
| continue; |
| } |
| std::vector<ChallengeResponseKey::SignatureAlgorithm> supported_algorithms = |
| MakeAlgorithmListFromSsl(supported_ssl_algorithms); |
| if (supported_algorithms.empty()) { |
| // This currently available key doesn't support any of the algorithms |
| // that are supported by the challenge-response user authentication. |
| continue; |
| } |
| ChallengeResponseKey filtered_key; |
| filtered_key.set_public_key_spki_der(suitable_spki); |
| filtered_key.set_signature_algorithms(supported_algorithms); |
| filtered_key.set_extension_id(extension_id); |
| filtered_keys.push_back(filtered_key); |
| } |
| std::move(callback).Run(std::move(filtered_keys)); |
| } |
| |
| } // namespace chromeos |