blob: 90185110526ee1e51838ca3f92bfdabfcce970e6 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_WEBAUTHN_GPM_ENCLAVE_CONTROLLER_H_
#define CHROME_BROWSER_WEBAUTHN_GPM_ENCLAVE_CONTROLLER_H_
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observation.h"
#include "base/time/tick_clock.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
#include "chrome/browser/webauthn/authenticator_request_dialog_model.h"
#include "chrome/browser/webauthn/enclave_manager.h"
#include "chrome/browser/webauthn/gpm_enclave_transaction.h"
#include "components/trusted_vault/trusted_vault_connection.h"
#include "content/public/browser/document_user_data.h"
#include "content/public/browser/global_routing_id.h"
#include "google_apis/gaia/gaia_id.h"
namespace base {
class TickClock;
class SequencedTaskRunner;
} // namespace base
namespace content {
class RenderFrameHost;
class WebContents;
} // namespace content
namespace device {
class FidoDiscoveryFactory;
enum class FidoRequestType : uint8_t;
enum class UserVerificationRequirement;
namespace enclave {
struct CredentialRequest;
} // namespace enclave
} // namespace device
namespace sync_pb {
class WebauthnCredentialSpecifics;
} // namespace sync_pb
namespace trusted_vault {
class ICloudRecoveryKey;
} // namespace trusted_vault
enum class EnclaveEnabledStatus;
class Profile;
// Provides a TrustedVaultConnection for a given RenderFrameHost.
// This allows tests to override the connection used by GPMEnclaveController.
class GpmTrustedVaultConnectionProvider
: public content::DocumentUserData<GpmTrustedVaultConnectionProvider> {
public:
~GpmTrustedVaultConnectionProvider() override;
// Sets a TrustedVaultConnection override for the document associated with
// `rfh`. The next call to GetConnectionForFrame for this document will
// return this override.
static void SetOverrideForFrame(
content::RenderFrameHost* rfh,
std::unique_ptr<trusted_vault::TrustedVaultConnection>
connection_override);
// Returns a TrustedVaultConnection for the document associated with `rfh`.
// If an override has been set via SetOverrideForFrame, that override is
// returned (and ownership is transferred). Otherwise, a new default
// TrustedVaultConnection is created. That connection is not associated with
// any particular document.
static std::unique_ptr<trusted_vault::TrustedVaultConnection> GetConnection(
content::RenderFrameHost* rfh,
signin::IdentityManager* identity_manager,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);
private:
explicit GpmTrustedVaultConnectionProvider(content::RenderFrameHost* rfh);
friend class content::DocumentUserData<GpmTrustedVaultConnectionProvider>;
DOCUMENT_USER_DATA_KEY_DECL();
std::unique_ptr<trusted_vault::TrustedVaultConnection> connection_override_;
};
class GpmTickAndTaskRunnerProvider
: public content::DocumentUserData<GpmTickAndTaskRunnerProvider> {
public:
~GpmTickAndTaskRunnerProvider() override;
// Sets a TickClock and SequencedTaskRunner override for the document
// associated with |rfh|. The next call to GetConnectionForFrame for this
// document will return this override.
static void SetOverrideForFrame(
content::RenderFrameHost* rfh,
base::TickClock const* tick_clock,
scoped_refptr<base::SequencedTaskRunner> task_runner);
// Returns the TickClock for the document associated with `rfh` if an override
// has been set via SetOverrideForFrame. Otherwise, the default TickClock is
// returned.
static base::TickClock const* GetTickClock(content::RenderFrameHost* rfh);
// Returns the SequencedTaskRunner for the document associated with `rfh` if
// an override has been set via SetOverrideForFrame. Otherwise, nullptr` is
// returned.
static scoped_refptr<base::SequencedTaskRunner> GetTaskRunner(
content::RenderFrameHost* rfh);
private:
explicit GpmTickAndTaskRunnerProvider(content::RenderFrameHost* rfh);
friend class content::DocumentUserData<GpmTickAndTaskRunnerProvider>;
DOCUMENT_USER_DATA_KEY_DECL();
raw_ptr<base::TickClock const> tick_clock_;
scoped_refptr<base::SequencedTaskRunner> task_runner_;
};
class GPMEnclaveController : public AuthenticatorRequestDialogModel::Observer,
public EnclaveManager::Observer,
public GPMEnclaveTransaction::Delegate {
public:
static constexpr base::TimeDelta kLoadingTimeout = base::Milliseconds(500);
enum class AccountState {
// There isn't a primary account, or enclave support is disabled.
kNone,
// The enclave state is still being loaded from disk.
kLoading,
// The state of the account is unknown pending network requests.
kChecking,
// The account can be recovered via user action.
kRecoverable,
// The account cannot be recovered, but could be reset.
kIrrecoverable,
// The security domain is empty.
kEmpty,
// The enclave is ready to use.
kReady,
};
enum class AccountReadyState {
kNotReady,
kLoading,
kReady,
};
explicit GPMEnclaveController(
content::RenderFrameHost* render_frame_host,
AuthenticatorRequestDialogModel* model,
const std::string& rp_id,
device::FidoRequestType request_type,
device::UserVerificationRequirement user_verification_requirement);
GPMEnclaveController(const GPMEnclaveController&) = delete;
GPMEnclaveController& operator=(const GPMEnclaveController&) = delete;
GPMEnclaveController(GPMEnclaveController&&) = delete;
GPMEnclaveController& operator=(GPMEnclaveController&&) = delete;
~GPMEnclaveController() override;
// Determines the enclave user verification early depending on the enclave
// state and UV requirements. Can return `std::nullopt` if the enclave is not
// ready. This is used for immediate mode requests.
std::optional<EnclaveUserVerificationMethod>
GetEnclaveUserVerificationMethod();
// Returns true if the enclave is active for this request. Crashes the address
// space if this hasn't yet been resolved.
bool is_active() const;
// Returns true if the enclave state is loaded to the point where the UI
// can be shown. If false, then the `OnReadyForUI` event will be triggered
// on the model when ready.
bool ready_for_ui() const;
// Configures an WebAuthn enclave authenticator discovery and provides it with
// synced passkeys.
void ConfigureDiscoveries(device::FidoDiscoveryFactory* discovery_factory);
// Fetch the set of GPM passkeys for this request.
const std::vector<sync_pb::WebauthnCredentialSpecifics>& creds() const;
AccountState account_state_for_testing() const;
// Returns the ready state of the account.
AccountReadyState account_ready_state() const;
// Runs `callback` once the account state is no longer `kLoading` or
// `kChecking`. If it's already in such a state, runs it immediately.
void RunWhenAccountReady(base::OnceClosure callback);
base::RepeatingCallback<
void(std::unique_ptr<device::enclave::CredentialRequest>)>&
enclave_request_callback_for_testing() {
return enclave_request_callback_;
}
private:
// GPMEnclaveTransaction::Delegate:
void HandleEnclaveTransactionError() override;
void BuildUVKeyOptions(EnclaveManager::UVKeyOptions& options) override;
void HandlePINValidationResult(
device::enclave::PINValidationResult result) override;
void OnPasskeyCreated(
const sync_pb::WebauthnCredentialSpecifics& passkey) override;
EnclaveUserVerificationMethod GetUvMethod() override;
Profile* GetProfile() const;
void OnUVCapabilityKnown(bool can_create_uv_keys);
// Called when the EnclaveManager has finished loading its state from the
// disk.
void OnEnclaveLoaded();
// Starts downloading the state of the account from the security domain
// service.
void DownloadAccountState();
// Called when fetching the account state took too long.
void OnAccountStateTimeOut();
// Called when the account state has finished downloading.
void OnAccountStateDownloaded(
GaiaId gaia_id,
std::unique_ptr<trusted_vault::TrustedVaultConnection> unused,
trusted_vault::DownloadAuthenticationFactorsRegistrationStateResult
result);
// Called when enough state has been loaded that the initial UI can be shown.
// If `kEnabled` then the enclave will be a valid mechanism.
void SetActive(EnclaveEnabledStatus enclave_enabled_status);
// EnclaveManager::Observer:
void OnKeysStored() override;
// Called when the local device has been added to the security domain.
void OnDeviceAdded(bool success);
// Initiates recovery from an iCloud keychain recovery key or MagicArch
// depending on availability.
void RecoverSecurityDomain();
#if BUILDFLAG(IS_MAC)
// Enrolls an iCloud keychain recovery factor if available and needed.
void MaybeAddICloudRecoveryKey();
// Called when Chrome has retrieved the iCloud recovery keys present in the
// current device.
void OnICloudKeysRetrievedForEnrollment(
std::vector<std::unique_ptr<trusted_vault::ICloudRecoveryKey>>
local_icloud_keys);
// Enrolls a specific iCloud keychain recovery key. |key| may be null, in
// which case we skip to the next step.
void EnrollICloudRecoveryKey(
std::unique_ptr<trusted_vault::ICloudRecoveryKey> key);
// Called when Chrome has retrieved the iCloud recovery keys present in the
// current device.
void OnICloudKeysRetrievedForRecovery(
std::vector<std::unique_ptr<trusted_vault::ICloudRecoveryKey>>
local_icloud_keys);
#endif // BUILDFLAG(IS_MAC)
// Called when the enclave enrollment is complete.
void OnEnclaveAccountSetUpComplete();
// Called when the EnclaveManager has finished loading. Sets `account_state_`
// and progresses the flow if waiting.
void SetAccountState(AccountState account_state);
// Called when the user selects Google Password Manager from the list of
// mechanisms. (Or when it's the priority mechanism.)
void OnGPMSelected() override;
// Called when a GPM passkey is selected from a list of credentials.
void OnGPMPasskeySelected(std::vector<uint8_t> credential_id) override;
// Sets the UI to the correct PIN prompt for the type of PIN configured.
void PromptForPin();
// Called when the user completes forgot pin flow.
void OnGpmPinChanged(bool success);
// Called when the user selects a GPM option, but the enclave is still loading
// or the account data hasn't finished downloading yet.
void OnGpmSelectedWhileLoading();
// Called when the enclave is still loading and |loading_timeout_| is
// triggered.
void OnLoadingTimeout();
// AuthenticatorRequestDialogModel::Observer:
void OnTrustThisComputer() override;
void OnGPMPinOptionChanged(bool is_arbitrary) override;
void OnGPMCreatePasskey() override;
void OnGPMConfirmOffTheRecordCreate() override;
void OnGPMPinEntered(const std::u16string& pin) override;
void OnTouchIDComplete(bool success) override;
void OnForgotGPMPinPressed() override;
void OnReauthComplete(std::string rapt) override;
void OnGpmPasskeysReset(bool success) override;
// Starts a create() or get() action with the enclave.
void StartTransaction();
// Accessors for the profile pref that counts the number of consecutive failed
// PIN attempts to know when a lockout will happen.
int GetFailedPINAttemptCount();
void SetFailedPINAttemptCount(int count);
// BrowserIsApp returns true if the current `Browser` is `TYPE_APP`. (I.e. a
// PWA.)
bool BrowserIsApp() const;
content::WebContents* web_contents() const;
const content::GlobalRenderFrameHostId render_frame_host_id_;
const std::string rp_id_;
const device::FidoRequestType request_type_;
const device::UserVerificationRequirement user_verification_requirement_;
// The `EnclaveManager` is a `KeyedService` for the current profile and so
// outlives this object.
const raw_ptr<EnclaveManager> enclave_manager_;
// This is owned by the ChromeAuthenticatorRequestDelegate, which also owns
// this object.
const raw_ptr<AuthenticatorRequestDialogModel> model_;
base::ScopedObservation<AuthenticatorRequestDialogModel,
AuthenticatorRequestDialogModel::Observer>
model_observer_{this};
base::ScopedObservation<EnclaveManager, EnclaveManager::Observer>
enclave_manager_observer_{this};
AccountState account_state_ = AccountState::kNone;
bool pin_is_arbitrary_ = false;
std::optional<std::string> pin_;
std::vector<sync_pb::WebauthnCredentialSpecifics> creds_;
// The user verification that will be performed for this request.
std::optional<EnclaveUserVerificationMethod> uv_method_;
std::optional<bool> is_active_;
// Whether the system can make UV keys.
std::optional<bool> can_make_uv_keys_;
// have_added_device_ is set to true if the local device was added to the
// security domain during this transaction. In this case, the security domain
// secret is available and can be used to satisfy user verification.
bool have_added_device_ = false;
// The ID of the selected credential when doing a get().
std::optional<std::vector<uint8_t>> selected_cred_id_;
// Contains the bytes of a WrappedPIN structure, downloaded from the security
// domain service. This is only set if the PIN is usable for recovery.
std::optional<trusted_vault::GpmPinMetadata> pin_metadata_;
// The list of iCloud recovery key members known to the security domain
// service.
std::vector<trusted_vault::VaultMember> security_domain_icloud_recovery_keys_;
// |recovered_with_icloud_keychain_| is true if this controller performed a
// successful recovery from iCloud keychain. This is reset on OnKeysStored().
bool recovered_with_icloud_keychain_ = false;
// The pending request to fetch the state of the trusted vault.
std::unique_ptr<trusted_vault::TrustedVaultConnection::Request>
download_account_state_request_;
std::unique_ptr<GPMEnclaveTransaction> pending_enclave_transaction_;
// The callback used to trigger a request to the enclave.
base::RepeatingCallback<void(
std::unique_ptr<device::enclave::CredentialRequest>)>
enclave_request_callback_;
// Whether the initial UI is being blocked while enclave state is loaded.
bool ready_for_ui_ = false;
// Whether showing the UI was delayed because the result from the security
// domain service is needed.
base::OnceClosure waiting_for_account_state_;
// If changing a GPM PIN, this holds a ReAuthentication Proof Token (RAPT), if
// the user is authenticating the request via doing a GAIA reauth.
std::optional<std::string> rapt_ = std::nullopt;
// A timeout to prevent waiting for the enclave to load forever. If triggered
// while still loading, the user is sent to the mechanism selection screen.
// Loading the enclave and downloading account data are not interrupted.
base::OneShotTimer loading_timeout_;
// Set to true when the user initiates reset GPM pin flow during UV.
bool changing_gpm_pin_ = false;
// Records when the user has confirmed credential creation in an Incognito
// context.
bool off_the_record_confirmed_ = false;
// Whether the user confirmed GPM PIN creation in the flow.
bool gpm_pin_creation_confirmed_ = false;
// The gaia id of the user at the time the account state was downloaded.
GaiaId user_gaia_id_;
base::WeakPtrFactory<GPMEnclaveController> weak_ptr_factory_{this};
};
#endif // CHROME_BROWSER_WEBAUTHN_GPM_ENCLAVE_CONTROLLER_H_