blob: 4767d39467c200c1177f3941562ffabb44605836 [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_ENCLAVE_MANAGER_H_
#define CHROME_BROWSER_WEBAUTHN_ENCLAVE_MANAGER_H_
#include <deque>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "base/files/file_path.h"
#include "base/functional/callback_forward.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/sequence_checker.h"
#include "base/thread_annotations.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
#include "chrome/browser/webauthn/enclave_manager_interface.h"
#include "chrome/browser/webauthn/local_authentication_token.h"
#include "components/os_crypt/async/common/encryptor.h"
#include "components/trusted_vault/trusted_vault_connection.h"
#include "content/public/browser/global_routing_id.h"
#include "crypto/user_verifying_key.h"
#include "device/fido/enclave/types.h"
#include "device/fido/network_context_factory.h"
#if BUILDFLAG(IS_MAC)
#include "chrome/common/chrome_version.h"
#endif // BUILDFLAG(IS_MAC)
#if BUILDFLAG(IS_CHROMEOS)
#include <variant>
#endif // BUILDFLAG(IS_CHROMEOS)
class GaiaId;
namespace base {
class ElapsedTimer;
}
namespace crypto {
class RefCountedUserVerifyingSigningKey;
} // namespace crypto
#if BUILDFLAG(IS_CHROMEOS)
namespace ash {
class WebAuthNDialogController;
class ActiveSessionAuthController;
} // namespace ash
#endif
namespace network {
class SharedURLLoaderFactory;
} // namespace network
namespace signin {
class IdentityManager;
class PrimaryAccountAccessTokenFetcher;
} // namespace signin
namespace unexportable_keys {
class RefCountedUnexportableSigningKey;
}
namespace webauthn_pb {
class EnclaveLocalState;
class EnclaveLocalState_User;
class EnclaveLocalState_WrappedPIN;
} // namespace webauthn_pb
namespace trusted_vault {
struct GpmPinMetadata;
class RecoveryKeyStoreConnection;
class TrustedVaultAccessTokenFetcherFrontend;
#if BUILDFLAG(IS_MAC)
class ICloudRecoveryKey;
#endif // BUILDFLAG(IS_MAC)
} // namespace trusted_vault
// EnclaveManager stores and manages the passkey enclave state. One instance
// exists per-profile, owned by `EnclaveManagerFactory`.
//
// The state exposed from this class is per-primary-account. This class watches
// the `IdentityManager` and, when the primary account changes, the result of
// functions like `is_registered` will suddenly change too. If an account is
// removed from the cookie jar (and it's not primary) then state for that
// account will be erased. Any pending operations will be canceled when the
// primary account changes and their callback will be run with `false`.
//
// When `is_ready` is true then this class can produce wrapped security domain
// secrets and signing callbacks to use to perform passkey operations with the
// enclave, which is the ultimate point of this class.
class EnclaveManager : public EnclaveManagerInterface {
public:
#if BUILDFLAG(IS_MAC)
static constexpr char kEnclaveKeysKeychainAccessGroup[] =
MAC_TEAM_IDENTIFIER_STRING "." MAC_BUNDLE_IDENTIFIER_STRING
".webauthn-uvk";
#endif // BUILDFLAG(IS_MAC)
struct StoreKeysArgs;
class Observer : public base::CheckedObserver {
public:
// OnKeyStores is called when MagicArch provides keys to the EnclaveManager
// by calling `StoreKeys`.
virtual void OnKeysStored() = 0;
};
struct UVKeyOptions {
UVKeyOptions();
UVKeyOptions(const UVKeyOptions&) = delete;
UVKeyOptions& operator=(const UVKeyOptions&) = delete;
UVKeyOptions(UVKeyOptions&&);
UVKeyOptions& operator=(UVKeyOptions&&);
~UVKeyOptions();
// The RP for the request, to be included in the UV dialog.
std::string rp_id;
// The RenderFrameHost from which the request originates.
content::GlobalRenderFrameHostId render_frame_host_id;
#if BUILDFLAG(IS_CHROMEOS)
std::variant<raw_ptr<ash::WebAuthNDialogController>,
raw_ptr<ash::ActiveSessionAuthController>>
dialog_controller;
#endif
// An optional auth context. Currently only used to pass LAcontext to Apple
// Keychain operations.
std::optional<webauthn::LocalAuthenticationToken> local_auth_token;
};
// These values are detailed failure reasons. They are emitted whenever PIN
// renewal fails and give detailed information about why the attempt failed.
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
//
// LINT.IfChange(PinRenewalFailureCause)
enum class PinRenewalFailureCause {
kDuringDownload = 1,
kGettingAccessToken = 2,
kEnclaveRequest1 = 3,
kEnclaveRequest2 = 4,
kEnclaveResponse1 = 5,
kEnclaveResponse2 = 6,
kRKSUpload = 7,
kJoiningToDomain = 8,
kSecurityDomainReportsNoPin = 9,
kSecurityDomainReset = 10,
kMaxValue = kSecurityDomainReset,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/webauthn/enums.xml:PinRenewalFailureCauseEnum)
class UvKeyCreationLock {
public:
virtual ~UvKeyCreationLock() = default;
UvKeyCreationLock(const UvKeyCreationLock&) = delete;
UvKeyCreationLock& operator=(const UvKeyCreationLock&) = delete;
protected:
UvKeyCreationLock() = default;
};
EnclaveManager(
const base::FilePath& base_dir,
signin::IdentityManager* identity_manager,
device::NetworkContextFactory network_context_factory,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);
~EnclaveManager() override;
EnclaveManager(const EnclaveManager&) = delete;
EnclaveManager(const EnclaveManager&&) = delete;
// Returns `this`.
EnclaveManager* GetEnclaveManager() override;
// Returns true if there are no current operations pending.
bool is_idle() const;
// Returns true if the persistent state has been loaded from the disk. (Or
// else the loading failed and an empty state is being used.)
bool is_loaded() const;
// Returns true if the current user has been registered with the enclave.
bool is_registered() const override;
// Returns true if `StoreKeys` has been called and thus `AddDeviceToAccount`
// or `AddDeviceAndPINToAccount` can be called.
bool has_pending_keys() const;
// Returns true if the current user has joined the security domain and has one
// or more wrapped security domain secrets available. (This implies
// `is_registered`.)
bool is_ready() const;
// Returns the number of times that `StoreKeys` has been called.
unsigned store_keys_count() const;
// Load the persisted state from disk. Harmless to call if `is_loaded`.
void Load(base::OnceClosure closure);
// Register with the enclave if not already registered.
void RegisterIfNeeded(Callback callback);
// Set up an account with a newly-created PIN.
void SetupWithPIN(std::string pin, Callback callback);
// Adds the current device to the security domain. Only valid to call after
// `StoreKeys` has been called and thus `has_pending_keys` returns true. If
// `pin_metadata` has a value then it is taken to be the current GPM PIN.
// If you want to add a new PIN to the account, see
// `AddDeviceAndPINToAccount`.
//
// Returns false if `serialized_wrapped_pin` fails to parse and true
// otherwise.
bool AddDeviceToAccount(
std::optional<trusted_vault::GpmPinMetadata> pin_metadata,
Callback callback);
// Adds the current device, and a GPM PIN, to the security domain. Only valid
// to call after `StoreKeys` has been called and thus `has_pending_keys`
// returns true.
// `previous_pin_public_key` must be set if the PIN is replacing an existing
// GPM PIN.
void AddDeviceAndPINToAccount(
std::string pin,
std::optional<std::string> previous_pin_public_key,
Callback callback);
// Set a PIN on an account that doesn't currently have one.
void SetPIN(std::string pin, std::string rapt, Callback callback);
// Change the GPM PIN on the account. If a RAPT (Reauthentication Proof Token)
// is given then it will be used, otherwise the UV key will be used, causing
// system UI to appear to verify the user.
void ChangePIN(std::string updated_pin, std::string rapt, Callback callback);
// Renew the current PIN. Requires `has_wrapped_pin` to be true.
void RenewPIN(Callback callback);
#if BUILDFLAG(IS_MAC)
// Adds an iCloud recovery key to the security domain. This can only be called
// immediately after enrollment while we still have the security domain secret
// around.
void AddICloudRecoveryKey(
std::unique_ptr<trusted_vault::ICloudRecoveryKey> icloud_recovery_key,
Callback callback);
#endif // BUILDFLAG(IS_MAC)
// Send a request to the enclave to delete the registration for the current
// user, erase local keys, and erase local state for the user. Safe to call in
// any state and is a no-op if no registration exists.
void Unenroll(Callback callback) override;
// Process the current security domain state. Requires `is_registered()`. This
// can update the locally-cached view of the current GPM PIN, or can make
// `is_ready()` false if the security domain has been reset.
//
// Returns whether `is_ready()` will return true in the future. (Because
// other operations may be running at the time, is_ready() may not update
// immediately.)
bool ConsiderSecurityDomainState(
const trusted_vault::DownloadAuthenticationFactorsRegistrationStateResult&
state,
Callback callback);
// Get a callback to sign with the registered "hw" key. Only valid to call if
// `is_ready`.
device::enclave::SigningCallback IdentityKeySigningCallback();
// Get a callback to sign with the registered "uv" key. Only valid to call if
// `is_ready`.
device::enclave::SigningCallback UserVerifyingKeySigningCallback(
UVKeyOptions options);
// Get a callback that creates a new "uv" key. This can only be called when
// `is_ready` and the user's state has `deferred_uv_key_creation` = true.
// The callback will create a new UV key and provides the public key to the
// invoker.
// The `UVKeyCreationLock` prevents any other attempts to create UV keys
// while it is alive. Its destruction releases the lock.
std::pair<std::unique_ptr<UvKeyCreationLock>,
device::enclave::UVKeyCreationCallback>
UserVerifyingKeyCreationCallback();
// Fetch a wrapped security domain secret for the given epoch. Only valid to
// call if `is_ready`.
std::optional<std::vector<uint8_t>> GetWrappedSecret(int32_t version);
// Get the version and value of the current wrapped secret. Only valid to call
// if `is_ready`.
std::pair<int32_t, std::vector<uint8_t>> GetCurrentWrappedSecret();
// Take the security domain secret. Only possible immediately after the device
// has been added to the account.
std::optional<std::pair<int32_t, std::vector<uint8_t>>> TakeSecret();
// Returns true if a wrapped PIN is available for the current user. Requires
// `is_ready`.
bool has_wrapped_pin() const;
// Returns true if the wrapped PIN is arbitrary. I.e. is a general
// alphanumeric string. If false then the wrapped PIN is a 6-digit numeric
// string. Requires `has_wrapped_pin` to be true.
bool wrapped_pin_is_arbitrary() const;
// Returns a copy of the wrapped PIN for passing to `MakeClaimedPINSlowly`.
// Requires `has_wrapped_pin`.
std::unique_ptr<webauthn_pb::EnclaveLocalState_WrappedPIN> GetWrappedPIN();
// Replaces the wrapped PIN data.
// Requires `has_wrapped_pin`.
void SetWrappedPINDataForTesting(std::vector<uint8_t> wrapped_pin_data);
// Enumerates the types of user verifying signing keys that the EnclaveManager
// might have for the currently signed-in user.
enum class UvKeyState {
// No UV key present; perform user verification using a PIN.
kNone,
// A UV key is present and `UserVerifyingKeySigningCallback` will return a
// signing callback where the UI is handled by the system.
kUsesSystemUI,
// A UV key has not yet been created but can be.
// `UserVerifyingKeyCreationCallback` will return a callback that creates
// the UV key.
kUsesSystemUIDeferredCreation,
// A UV key is present and `UserVerifyingKeySigningCallback` will return a
// valid callback. However, Chrome UI needs to be shown in order to collect
// biometrics.
kUsesChromeUI,
};
UvKeyState uv_key_state(bool platform_has_biometrics) const;
// Checks whether UserVerifyingKeyCreationCallback() is available to be
// called, returning true if not. There should only be one key creation
// callback in existence at any one time, or else one could overwrite a
// previous caller's keys. Attempting to get a key creation callback
// while already locked results in a process crash.
bool deferred_uv_key_creation_locked() const {
return deferred_uv_key_creation_in_progress_;
}
// Called when `deferred_uv_key_creation_in_progress_` is true, to be
// notified when the existing key creation has completed. The boolean
// argument indicates whether the key creation was successful.
void AddPendingUvRequest(base::OnceCallback<void(bool)> callback);
// Calls the given callback with `true` if the current platform supports
// making user-verifying keys.
static void AreUserVerifyingKeysSupported(Callback callback);
// Get an access token for contacting the enclave.
std::unique_ptr<signin::PrimaryAccountAccessTokenFetcher> GetAccessToken(
base::OnceCallback<void(std::optional<std::string>)> callback);
void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer);
// This function is called by the MagicArch integration when the user
// successfully completes recovery.
void StoreKeys(const GaiaId& gaia_id,
std::vector<std::vector<uint8_t>> keys,
int last_key_version);
// Slowly compute a PIN claim for the given PIN for submission to the enclave.
static std::unique_ptr<device::enclave::ClaimedPIN> MakeClaimedPINSlowly(
std::string pin,
std::unique_ptr<webauthn_pb::EnclaveLocalState_WrappedPIN> wrapped_pin);
// If background processes need to be stopped then return true and call
// `on_stop` when stopped. Otherwise return false.
bool RunWhenStoppedForTesting(base::OnceClosure on_stop);
webauthn_pb::EnclaveLocalState& local_state_for_testing();
// Release the cached HW and UV key references.
void ClearCachedKeysForTesting();
// Reset the EnclaveManager to simulate creating a new one in initialized
// state.
void ResetForTesting();
// Clears the registration as if we were starting from scratch.
void ClearRegistrationForTesting();
// Toggle invariant checks.
static void EnableInvariantChecksForTesting(bool enable);
// Check whether the GPM PIN Vault should be renewed, and do so if needed.
void ConsiderPinRenewalForTesting();
unsigned renewal_checks_for_testing() const;
unsigned renewal_attempts_for_testing() const;
// Create a wrapped PIN, suitable for putting into a simulated security domain
// member.
static std::string MakeWrappedPINForTesting(
base::span<const uint8_t> security_domain_secret,
std::string_view pin);
// Encrypts `cbor_bytes` representing a wrapped PIN with
// `security_domain_secret`.
static std::vector<uint8_t> EncryptWrappedPIN(
base::span<const uint8_t> security_domain_secret,
base::span<const uint8_t> cbor_bytes);
base::WeakPtr<EnclaveManager> GetWeakPtr();
private:
class StateMachine;
class IdentityObserver;
struct PendingAction;
friend class StateMachine;
FRIEND_TEST_ALL_PREFIXES(EnclaveUVTest, UnregisterOnMissingUserVerifyingKey);
// Starts a `StateMachine` to process the current request.
void Act();
// Is called when reading the state file from disk has completed.
// (Successfully or otherwise.)
void LoadComplete(std::optional<std::string> contents);
// Called when `identity_observer_` reports a change in the signed-in state of
// the Profile. Also called once the local state has finished loading. In
// that case `is_post_load` will be false and any "change" in primary
// identity doesn't cause a reset.
void HandleIdentityChange(bool is_post_load = false);
// Called when a `StateMachine` has stopped (or needs to stop).
void Stopped();
// Called when the primary user changes and all pending actions are stopped.
void CancelAllActions();
// Can be called at any point to serialise the current value of `local_state_`
// to disk. Only a single write happens at a time. If a write is already
// happening, the request will be queued. If a request is already queued, this
// call will replace that queued write.
void WriteState(webauthn_pb::EnclaveLocalState* new_state);
void DoWriteState(std::string serialized);
void WriteStateComplete(bool success);
// Accessors for the HW and UV keys, invoking the supplied callbacks with
// the result. These can complete synchronously if the respective key is
// cached, or will attempt to load them asynchronously otherwise.
// If the key fails to load, the callback will be invoked with nullptr and
// the device's enclave registration will be reset.
void GetIdentityKeyForSignature(
base::OnceCallback<void(
scoped_refptr<unexportable_keys::RefCountedUnexportableSigningKey>)>
callback);
void GetUserVerifyingKeyForSignature(
UVKeyOptions options,
base::OnceCallback<void(
scoped_refptr<crypto::RefCountedUserVerifyingSigningKey>)> callback);
// If signing keys are lost or disabled, this can put the enclave registration
// in an unrecoverable state. In this case the registration state needs to be
// reset, and can be initiated from scratch.
void ClearRegistration();
void UnregisterComplete(Callback callback, bool success);
// Store the secret that `TakeSecret` will make available.
void SetSecret(int32_t key_version, base::span<const uint8_t> secret);
// Check whether the GPM PIN Vault should be renewed.
void ConsiderPinRenewal();
void OnRenewalComplete(bool success);
// Take the lock for UV key creation. Only one can exist at a time.
std::unique_ptr<UvKeyCreationLock> TakeUvKeyCreationLock();
// This is a callback for the UvKeyCreationLock.
void OnUvKeyCreationLockReleased();
// These clean up local state on resolution of a callback that was returned
// from UserVerifyingKeyCreationCallback();
void OnDeferredUvKeyCreationFailure();
void OnDeferredUvKeyCreationSuccess();
// Returns true if |state| indicates that the security domain has been reset,
// i.e. that the local Chrome state no longer matches what's on the security
// domain.
bool IsSecurityDomainReset(
const trusted_vault::DownloadAuthenticationFactorsRegistrationStateResult&
state);
// Called when the OSCrypt encryptor is available.
void OnOsCryptReady(os_crypt_async::Encryptor encryptor);
const base::FilePath file_path_;
const raw_ptr<signin::IdentityManager> identity_manager_;
device::NetworkContextFactory network_context_factory_;
const scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
const std::unique_ptr<trusted_vault::TrustedVaultConnection>
trusted_vault_conn_;
const std::unique_ptr<trusted_vault::TrustedVaultAccessTokenFetcherFrontend>
trusted_vault_access_token_fetcher_frontend_;
const std::unique_ptr<trusted_vault::RecoveryKeyStoreConnection>
recovery_key_store_conn_;
std::unique_ptr<webauthn_pb::EnclaveLocalState> local_state_;
bool loading_ = false;
raw_ptr<const webauthn_pb::EnclaveLocalState_User> user_ = nullptr;
std::unique_ptr<CoreAccountInfo> primary_account_info_;
std::unique_ptr<IdentityObserver> identity_observer_;
std::optional<std::string> pending_write_;
bool currently_writing_ = false;
base::OnceClosure write_finished_callback_;
std::unique_ptr<StoreKeysArgs> pending_keys_;
std::unique_ptr<StateMachine> state_machine_;
std::vector<base::OnceClosure> load_callbacks_;
std::deque<std::unique_ptr<PendingAction>> pending_actions_;
base::OneShotTimer load_timer_;
base::RepeatingTimer renewal_timer_;
unsigned renewal_checks_ = 0;
unsigned renewal_attempts_ = 0;
bool is_renewing_ = false;
bool deferred_uv_key_creation_in_progress_ = false;
std::optional<bool> deferred_uv_key_creation_successful_;
std::vector<base::OnceCallback<void(bool)>> pending_uv_key_requests_;
// These fields store the security domain secret immediately after a
// device has been added to the security domain.
int32_t secret_version_ = -1;
std::vector<uint8_t> secret_;
// Allow keys to persist across sequences because loading them is slow.
scoped_refptr<crypto::RefCountedUserVerifyingSigningKey> user_verifying_key_;
scoped_refptr<unexportable_keys::RefCountedUnexportableSigningKey>
identity_key_;
unsigned store_keys_count_ = 0;
// Timer for recording a metric measuring the delay to load the Enclave
// state.
std::unique_ptr<base::ElapsedTimer> load_duration_timer_;
base::ObserverList<Observer> observer_list_;
std::optional<os_crypt_async::Encryptor> encryptor_;
SEQUENCE_CHECKER(sequence_checker_);
base::WeakPtrFactory<EnclaveManager> weak_ptr_factory_{this};
};
#endif // CHROME_BROWSER_WEBAUTHN_ENCLAVE_MANAGER_H_