blob: 37cb39a335c84663713294b501dcdce47d5a7fa1 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef DEVICE_FIDO_FIDO_REQUEST_HANDLER_BASE_H_
#define DEVICE_FIDO_FIDO_REQUEST_HANDLER_BASE_H_
#include <array>
#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <vector>
#include "base/check.h"
#include "base/component_export.h"
#include "base/containers/flat_set.h"
#include "base/functional/callback_forward.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observation_traits.h"
#include "base/timer/elapsed_timer.h"
#include "build/build_config.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_discovery_base.h"
#include "device/fido/fido_transport_protocol.h"
#include "device/fido/fido_types.h"
#include "device/fido/pin.h"
namespace device {
class BleAdapterManager;
class FidoAuthenticator;
class FidoDiscoveryFactory;
class DiscoverableCredentialMetadata;
struct TransportAvailabilityCallbackReadiness;
// Base class that handles authenticator discovery/removal. Its lifetime is
// equivalent to that of a single WebAuthn request. For each authenticator, the
// per-device work is carried out by one FidoAuthenticator instance, which is
// constructed in a FidoDiscoveryBase and passed to the request handler via its
// Observer interface.
class COMPONENT_EXPORT(DEVICE_FIDO) FidoRequestHandlerBase
: public FidoDiscoveryBase::Observer {
public:
using RequestCallback = base::RepeatingCallback<void(const std::string&)>;
using AuthenticatorMap = std::map<std::string,
raw_ptr<FidoAuthenticator, CtnExperimental>,
std::less<>>;
// BLE adapter status.
enum class BleStatus {
// The adapter is turned on.
kOn,
// The adapter is turned off.
kOff,
// The user has denied Chrome the permission to use bluetooth.
kPermissionDenied,
// Chrome has not yet requested permission to use bluetooth.
kPendingPermissionRequest,
};
using BlePermissionCallback = base::OnceCallback<void(BleStatus)>;
enum class RecognizedCredential {
kUnknown,
kHasRecognizedCredential,
kNoRecognizedCredential
};
// Encapsulates data required to initiate WebAuthN UX dialog. Once all
// components of TransportAvailabilityInfo is set,
// AuthenticatorRequestClientDelegate should be notified.
struct COMPONENT_EXPORT(DEVICE_FIDO) TransportAvailabilityInfo {
TransportAvailabilityInfo();
TransportAvailabilityInfo(const TransportAvailabilityInfo& other);
TransportAvailabilityInfo& operator=(
const TransportAvailabilityInfo& other);
~TransportAvailabilityInfo();
FidoRequestType request_type = FidoRequestType::kMakeCredential;
// Indicates whether this is a GetAssertion request with an empty allow
// list.
bool has_empty_allow_list = false;
// True this process has iCloud Keychain support. Only meaningful on macOS.
bool has_icloud_keychain = false;
// The intersection of transports supported by the client and allowed by the
// relying party.
base::flat_set<FidoTransportProtocol> available_transports;
// Whether the platform authenticator has a matching credential for the
// request. This is only set for a GetAssertion request.
RecognizedCredential has_platform_authenticator_credential =
RecognizedCredential::kNoRecognizedCredential;
// This field mirrors the previous one but is specific to iCloud
// Keychain. They are separate because a macOS system can have both the
// Chromium platform authenticator and iCloud Keychain as platform
// authenticators.
RecognizedCredential has_icloud_keychain_credential =
RecognizedCredential::kNoRecognizedCredential;
// The set of recognized credential user entities that can fulfill a
// GetAssertion request. Not all authenticators report this, so the set
// might be empty even if |has_platform_authenticator_credential| is
// |kHasRecognizedCredential|.
std::vector<DiscoverableCredentialMetadata> recognized_credentials;
BleStatus ble_status = BleStatus::kOff;
bool can_power_on_ble_adapter = false;
// Indicates whether the native Windows WebAuthn API is available.
// Dispatching to it should be controlled by the embedder.
//
// The embedder:
// - may choose not to dispatch immediately if caBLE is available
// - should dispatch immediately if no other transport is available
bool has_win_native_api_authenticator = false;
// Indicates whether the Windows native UI will include a privacy notice
// when creating a resident credential.
bool win_native_ui_shows_resident_credential_notice = false;
// Whether the native Windows API reports that a user verifying platform
// authenticator is available.
bool win_is_uvpaa = false;
// Whether the platform can check biometrics and has biometrics configured.
bool platform_has_biometrics = false;
// Indicates the ResidentKeyRequirement of the current request. Only valid
// if |request_type| is |RequestType::kMakeCredential|. Requests with a
// value of |ResidentKeyRequirement::kPreferred| or
// |ResidentKeyRequirement::kRequired| can create a resident credential,
// which could be discovered by someone with physical access to the
// authenticator and thus have privacy implications.
ResidentKeyRequirement resident_key_requirement =
ResidentKeyRequirement::kDiscouraged;
// Indicates the UserVerificationRequirement of the current request.
UserVerificationRequirement user_verification_requirement =
UserVerificationRequirement::kDiscouraged;
// The attestation preference. Present if, and only if, |request_type| is
// |kMakeCredential|.
std::optional<AttestationConveyancePreference>
attestation_conveyance_preference;
// transport_list_did_include_internal is set to true during a getAssertion
// request if at least one element of the allowList included the "internal"
// transport, or didn't have any transports.
//
// An embedder may use this to show a more precise UI when no transports
// are available. If the lack of transports is because the allowList only
// contained NFC-based credentials, and there's no NFC support, then that
// might be meaningfully different from the case where the allowList
// contained credentials that could have been on the local device but
// weren't.
bool transport_list_did_include_internal = false;
// transport_list_did_include_hybrid is set to true during a getAssertion
// request if at least one element of the allowList included the "hybrid"
// transport, or didn't have any transports.
bool transport_list_did_include_hybrid = false;
// transport_list_did_include_security_key is set to true during a
// getAssertion request if at least one element of the allowList included
// the "usb", "nfc", or "ble" transport, or didn't have any transports.
bool transport_list_did_include_security_key = false;
// request_is_internal_only indicates that this request can only be serviced
// by internal authenticators (e.g. due to the attachment setting).
// See also `make_credential_attachment`.
bool request_is_internal_only = false;
// make_credential_attachment contains the attachment preference for
// makeCredential requests. See also `request_is_internal_only`, which isn't
// specific to makeCredential requests.
std::optional<AuthenticatorAttachment> make_credential_attachment;
// If true, the only available credential may be selected by default in
// immediate mediation requests.
// TODO(crbug.com/393055190): Remove this field while cleaning up
// WebAuthenticationImmediateGetAutoselect once the autoselect experiment is
// complete.
bool autoselect_in_immediate_mediation = false;
};
class COMPONENT_EXPORT(DEVICE_FIDO) Observer {
public:
struct COMPONENT_EXPORT(DEVICE_FIDO) CollectPINOptions {
// Why this PIN is being collected.
pin::PINEntryReason reason;
// The error for which we are prompting for a PIN.
pin::PINEntryError error = pin::PINEntryError::kNoError;
// The minimum PIN length the authenticator will accept for the PIN.
uint32_t min_pin_length = device::kMinPinLength;
// The number of attempts remaining before a hard lock. Should be ignored
// unless |mode| is kChallenge.
int attempts = 0;
};
virtual ~Observer();
// `StartObserving` and `StopObserving` are called when the request handler
// is constructed and destructed, respectively.
virtual void StartObserving(FidoRequestHandlerBase* request_handler) = 0;
virtual void StopObserving(FidoRequestHandlerBase* request_handler) = 0;
// This method will not be invoked until the observer is set.
virtual void OnTransportAvailabilityEnumerated(
TransportAvailabilityInfo data) = 0;
// If true, the request handler will defer dispatch of its request onto the
// given authenticator to the embedder. The embedder needs to call
// |StartAuthenticatorRequest| when it wants to initiate request dispatch.
//
// This method is invoked before |FidoAuthenticatorAdded|, and may be
// invoked multiple times for the same authenticator. Depending on the
// result, the request handler might decide not to make the authenticator
// available, in which case it never gets passed to
// |FidoAuthenticatorAdded|.
virtual bool EmbedderControlsAuthenticatorDispatch(
const FidoAuthenticator& authenticator) = 0;
virtual void BluetoothAdapterStatusChanged(BleStatus ble_status) = 0;
virtual void FidoAuthenticatorAdded(
const FidoAuthenticator& authenticator) = 0;
virtual void FidoAuthenticatorRemoved(std::string_view device_id) = 0;
// SupportsPIN returns true if this observer supports collecting a PIN from
// the user. If this function returns false, |CollectPIN| and
// |FinishCollectPIN| will not be called.
virtual bool SupportsPIN() const = 0;
// CollectPIN is called when a PIN is needed to complete a request. The
// |attempts| parameter is either |nullopt| to indicate that the user needs
// to set a PIN, or contains the number of PIN attempts remaining before a
// hard lock.
virtual void CollectPIN(
CollectPINOptions options,
base::OnceCallback<void(std::u16string)> provide_pin_cb) = 0;
virtual void FinishCollectToken() = 0;
// Called when a biometric enrollment may be completed as part of the
// request and the user should be notified to collect samples.
// |next_callback| must be executed asynchronously at any time to move on to
// the next step of the request.
virtual void StartBioEnrollment(base::OnceClosure next_callback) = 0;
// Called when a biometric enrollment sample has been collected.
// |bio_samples_remaining| is the number of samples needed to finish the
// enrollment.
virtual void OnSampleCollected(int bio_samples_remaining) = 0;
// Called when an authenticator reports internal user verification has
// failed (e.g. not recognising the user's fingerprints) and the user should
// try again. Receives the number of |attempts| before the device locks
// internal user verification.
virtual void OnRetryUserVerification(int attempts) = 0;
};
// ScopedAlwaysAllowBLECalls allows BLE API calls to always be made, even if
// they would be disabled on macOS because Chromium was not launched with
// self-responsibility.
class COMPONENT_EXPORT(DEVICE_FIDO) ScopedAlwaysAllowBLECalls {
public:
ScopedAlwaysAllowBLECalls();
~ScopedAlwaysAllowBLECalls();
};
FidoRequestHandlerBase();
// The |available_transports| should be the intersection of transports
// supported by the client and allowed by the relying party.
FidoRequestHandlerBase(
FidoDiscoveryFactory* fido_discovery_factory,
const base::flat_set<FidoTransportProtocol>& available_transports);
FidoRequestHandlerBase(
FidoDiscoveryFactory* fido_discovery_factory,
std::vector<std::unique_ptr<FidoDiscoveryBase>> additional_discoveries,
const base::flat_set<FidoTransportProtocol>& available_transports);
FidoRequestHandlerBase(const FidoRequestHandlerBase&) = delete;
FidoRequestHandlerBase& operator=(const FidoRequestHandlerBase&) = delete;
~FidoRequestHandlerBase() override;
// Triggers DispatchRequest() if |active_authenticators_| hold
// FidoAuthenticator with given |authenticator_id|.
void StartAuthenticatorRequest(const std::string& authenticator_id);
// Invokes |FidoAuthenticator::Cancel| on all authenticators, except if
// matching |exclude_id|, if one is provided. Cancelled authenticators are
// immediately removed from |active_authenticators_|.
//
// This function is invoked either when: (a) the entire WebAuthn API request
// is canceled or, (b) a successful response or "invalid state error" is
// received from the any one of the connected authenticators, in which case
// all other authenticators are cancelled.
// https://w3c.github.io/webauthn/#iface-pkcredential
void CancelActiveAuthenticators(std::string_view exclude_id = "");
virtual void OnBluetoothAdapterEnumerated(bool is_present,
BleStatus ble_status,
bool can_power_on,
bool is_peripheral_role_supported);
void OnBluetoothAdapterStatusChanged(BleStatus ble_status);
void PowerOnBluetoothAdapter();
// Queries the OS for the status of the Bluetooth adapter. This is useful on
// macOS when TransportAvailabilityInfo::ble_status reports
// kPendingPermissionRequest, in which case the OS will display a blocking
// permissions prompt. Once the user allows or denies the prompt, |callback|
// will be executed with the result.
void RequestBluetoothPermission(BlePermissionCallback callback);
base::WeakPtr<FidoRequestHandlerBase> GetWeakPtr();
void SetObserver(Observer* observer);
void RemoveObserver(Observer* observer);
// Returns whether FidoAuthenticator with id equal to |authenticator_id|
// exists. Fake FidoRequestHandler objects used in testing overrides this
// function to simulate scenarios where authenticator with |authenticator_id|
// is known to the system.
virtual bool HasAuthenticator(const std::string& authentiator_id) const;
TransportAvailabilityInfo& transport_availability_info() {
return transport_availability_info_;
}
const AuthenticatorMap& AuthenticatorsForTesting() {
return active_authenticators_;
}
std::unique_ptr<BleAdapterManager>&
get_bluetooth_adapter_manager_for_testing() {
return bluetooth_adapter_manager_;
}
void StopDiscoveries();
protected:
// Authenticators that return a response in less than this time are likely to
// have done so without interaction from the user.
static constexpr base::TimeDelta kMinExpectedAuthenticatorResponseTime =
base::Milliseconds(300);
// Subclasses implement this method to dispatch their request onto the given
// FidoAuthenticator. The FidoAuthenticator is owned by this
// FidoRequestHandler and stored in active_authenticators().
virtual void DispatchRequest(FidoAuthenticator*) = 0;
void InitDiscoveries(
FidoDiscoveryFactory* fido_discovery_factory,
std::vector<std::unique_ptr<FidoDiscoveryBase>> additional_discoveries,
base::flat_set<FidoTransportProtocol> available_transports,
bool consider_enclave);
void Start();
AuthenticatorMap& active_authenticators() { return active_authenticators_; }
std::vector<std::unique_ptr<FidoDiscoveryBase>>& discoveries() {
return discoveries_;
}
Observer* observer() const { return observer_; }
// FidoDiscoveryBase::Observer
void DiscoveryStarted(
FidoDiscoveryBase* discovery,
bool success,
std::vector<FidoAuthenticator*> authenticators) override;
void AuthenticatorAdded(FidoDiscoveryBase* discovery,
FidoAuthenticator* authenticator) override;
void AuthenticatorRemoved(FidoDiscoveryBase* discovery,
FidoAuthenticator* authenticator) override;
// GetPlatformCredentialStatus is called to learn whether a platform
// authenticator has credentials responsive to the current request. If this
// method is overridden in a subclass then either:
// · The method in this base class must be called immediately, or
// · |OnHavePlatformCredentialStatus| must eventually called.
//
// This method runs only after the platform discovery has started
// successfully. (The Windows API doesn't count as a platform authenticator
// for the purposes of this call.)
virtual void GetPlatformCredentialStatus(
FidoAuthenticator* platform_authenticator);
// OnHavePlatformCredentialStatus is called by subclasses (after
// `GetPlatformCredentialStatus` has been called) to report on whether the
// platform authenticator whether it has responsive discoverable credentials
// and whether it has responsive credentials at all.
// `timer` allows recording metrics with the wait time for this callback.
void OnHavePlatformCredentialStatus(
AuthenticatorType authenticator_type,
std::optional<base::ElapsedTimer> timer,
std::vector<DiscoverableCredentialMetadata> user_entities,
RecognizedCredential has_credentials);
private:
friend class FidoRequestHandlerTest;
void MaybeSignalTransportsEnumerated();
// Invokes FidoAuthenticator::InitializeAuthenticator(), followed by
// DispatchRequest(). InitializeAuthenticator() sends a GetInfo command
// to FidoDeviceAuthenticator instances in order to determine their protocol
// versions before a request can be dispatched.
void InitializeAuthenticatorAndDispatchRequest(
const std::string& authenticator_id);
void ConstructBleAdapterPowerManager();
void OnWinIsUvpaa(bool is_uvpaa);
AuthenticatorMap active_authenticators_;
std::vector<std::unique_ptr<FidoDiscoveryBase>> discoveries_;
raw_ptr<Observer> observer_ = nullptr;
TransportAvailabilityInfo transport_availability_info_;
std::unique_ptr<BleAdapterManager> bluetooth_adapter_manager_;
// transport_availability_callback_readiness_ keeps track of state which
// determines whether this object is ready to call
// |OnTransportAvailabilityEnumerated| on |observer_|.
std::unique_ptr<TransportAvailabilityCallbackReadiness>
transport_availability_callback_readiness_;
base::WeakPtrFactory<FidoRequestHandlerBase> weak_factory_{this};
};
} // namespace device
namespace base {
template <>
struct ScopedObservationTraits<device::FidoRequestHandlerBase,
device::FidoRequestHandlerBase::Observer> {
static void AddObserver(device::FidoRequestHandlerBase* source,
device::FidoRequestHandlerBase::Observer* observer) {
source->SetObserver(observer);
}
static void RemoveObserver(
device::FidoRequestHandlerBase* source,
device::FidoRequestHandlerBase::Observer* observer) {
source->RemoveObserver(observer);
}
};
} // namespace base
#endif // DEVICE_FIDO_FIDO_REQUEST_HANDLER_BASE_H_