blob: 2c21fa280d20654fbd6fbf045d10be8b8ca52173 [file] [log] [blame]
// Copyright 2018 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.
#ifndef CHROME_BROWSER_WEBAUTHN_AUTHENTICATOR_REQUEST_DIALOG_MODEL_H_
#define CHROME_BROWSER_WEBAUTHN_AUTHENTICATOR_REQUEST_DIALOG_MODEL_H_
#include <memory>
#include <string>
#include <vector>
#include "base/containers/span.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/optional.h"
#include "base/strings/string16.h"
#include "base/strings/string_piece.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/webauthn/authenticator_reference.h"
#include "chrome/browser/webauthn/authenticator_transport.h"
#include "chrome/browser/webauthn/observable_authenticator_list.h"
#include "device/fido/cable/cable_discovery_data.h"
#include "device/fido/fido_request_handler_base.h"
#include "device/fido/fido_transport_protocol.h"
namespace device {
class AuthenticatorGetAssertionResponse;
}
// Encapsulates the model behind the Web Authentication request dialog's UX
// flow. This is essentially a state machine going through the states defined in
// the `Step` enumeration.
//
// Ultimately, this will become an observer of the AuthenticatorRequest, and
// contain the logic to figure out which steps the user needs to take, in which
// order, to complete the authentication flow.
class AuthenticatorRequestDialogModel {
public:
using RequestCallback = device::FidoRequestHandlerBase::RequestCallback;
using BlePairingCallback = device::FidoRequestHandlerBase::BlePairingCallback;
using BleDevicePairedCallback = base::RepeatingCallback<void(std::string)>;
using TransportAvailabilityInfo =
device::FidoRequestHandlerBase::TransportAvailabilityInfo;
// Defines the potential steps of the Web Authentication API request UX flow.
enum class Step {
// The UX flow has not started yet, the dialog should still be hidden.
kNotStarted,
kTransportSelection,
// The request errored out before completing. Error will only be sent
// after user interaction.
kErrorNoAvailableTransports,
kErrorInternalUnrecognized,
// The request is already complete, but the error dialog should wait
// until user acknowledgement.
kTimedOut,
kKeyNotRegistered,
kKeyAlreadyRegistered,
kMissingCapability,
kStorageFull,
// The request is completed, and the dialog should be closed.
kClosed,
// Universal Serial Bus (USB).
kUsbInsertAndActivate,
// Bluetooth Low Energy (BLE).
kBlePowerOnAutomatic,
kBlePowerOnManual,
kBlePairingBegin,
kBleEnterPairingMode,
kBleDeviceSelection,
kBlePinEntry,
kBleActivate,
kBleVerifying,
// Touch ID.
kTouchIdIncognitoSpeedBump,
// Phone as a security key.
kCableActivate,
// Authenticator Client PIN.
kClientPinEntry,
kClientPinSetup,
kClientPinTapAgain,
kClientPinErrorSoftBlock,
kClientPinErrorHardBlock,
kClientPinErrorAuthenticatorRemoved,
// Confirm user consent to create a resident credential. Used prior to
// triggering Windows-native APIs when Windows itself won't show any
// notice about resident credentials.
kResidentCredentialConfirmation,
// Account selection,
kSelectAccount,
// Attestation permission request.
kAttestationPermissionRequest,
// Display QR code for phone pairing.
kQRCode,
};
// Implemented by the dialog to observe this model and show the UI panels
// appropriate for the current step.
class Observer {
public:
// Called when the user clicks "Try Again" to restart the user flow.
virtual void OnStartOver() {}
// Called just before the model is destructed.
virtual void OnModelDestroyed() = 0;
// Called when the UX flow has navigated to a different step, so the UI
// should update.
virtual void OnStepTransition() {}
// Called when the model corresponding to the current sheet of the UX flow
// was updated, so UI should update.
virtual void OnSheetModelChanged() {}
// Called when the power state of the Bluetooth adapter has changed.
virtual void OnBluetoothPoweredStateChanged() {}
// Called when the user cancelled WebAuthN request by clicking the
// "cancel" button or the back arrow in the UI dialog.
virtual void OnCancelRequest() {}
};
explicit AuthenticatorRequestDialogModel(const std::string& relying_party_id);
~AuthenticatorRequestDialogModel();
void SetCurrentStep(Step step);
Step current_step() const { return current_step_; }
// Hides the dialog. A subsequent call to SetCurrentStep() will unhide it.
void HideDialog();
// Returns whether the UI is in a state at which the |request_| member of
// AuthenticatorImpl has completed processing. Note that the request callback
// is only resolved after the UI is dismissed.
bool is_request_complete() const {
return current_step() == Step::kTimedOut ||
current_step() == Step::kKeyNotRegistered ||
current_step() == Step::kKeyAlreadyRegistered ||
current_step() == Step::kMissingCapability ||
current_step() == Step::kClosed;
}
bool should_dialog_be_closed() const {
return current_step() == Step::kClosed;
}
bool should_dialog_be_hidden() const {
return current_step() == Step::kNotStarted;
}
const TransportAvailabilityInfo* transport_availability() const {
return &transport_availability_;
}
bool ble_adapter_is_powered() const {
return transport_availability()->is_ble_powered;
}
const base::Optional<std::string>& selected_authenticator_id() const {
return ephemeral_state_.selected_authenticator_id_;
}
// Starts the UX flow, by either showing the transport selection screen or
// the guided flow for them most likely transport.
//
// Valid action when at step: kNotStarted.
void StartFlow(
TransportAvailabilityInfo transport_availability,
base::Optional<device::FidoTransportProtocol> last_used_transport,
const base::ListValue* previously_paired_bluetooth_device_list);
// Restarts the UX flow.
void StartOver();
// Starts the UX flow. Tries to figure out the most likely transport to be
// used, and starts the guided flow for that transport; or shows the manual
// transport selection screen if the transport could not be uniquely
// identified.
//
// Valid action when at step: kNotStarted.
void StartGuidedFlowForMostLikelyTransportOrShowTransportSelection();
// Requests that the step-by-step wizard flow commence, guiding the user
// through using the Secutity Key with the given |transport|.
//
// Valid action when at step: kNotStarted.
// kTransportSelection, and steps where the other transports menu is shown,
// namely, kUsbInsertAndActivate, kBleActivate, kCableActivate.
void StartGuidedFlowForTransport(
AuthenticatorTransport transport,
bool pair_with_new_device_for_bluetooth_low_energy = false);
// Hides the modal Chrome UI dialog and shows the native Windows WebAuthn
// UI instead.
void HideDialogAndDispatchToNativeWindowsApi();
// StartPhonePairing triggers the display of a QR code for pairing a new
// phone.
void StartPhonePairing();
// Ensures that the Bluetooth adapter is powered before proceeding to |step|.
// -- If the adapter is powered, advanced directly to |step|.
// -- If the adapter is not powered, but Chrome can turn it automatically,
// then advanced to the flow to turn on Bluetooth automatically.
// -- Otherwise advanced to the manual Bluetooth power on flow.
//
// Valid action when at step: kNotStarted, kTransportSelection, and steps
// where the other transports menu is shown, namely, kUsbInsertAndActivate,
// kBleActivate, kCableActivate.
void EnsureBleAdapterIsPoweredBeforeContinuingWithStep(Step step);
// Continues with the BLE/caBLE flow now that the Bluetooth adapter is
// powered.
//
// Valid action when at step: kBlePowerOnManual, kBlePowerOnAutomatic.
void ContinueWithFlowAfterBleAdapterPowered();
// Turns on the BLE adapter automatically.
//
// Valid action when at step: kBlePowerOnAutomatic.
void PowerOnBleAdapter();
// Lets the pairing procedure start after the user learned about the need.
//
// Valid action when at step: kBlePairingBegin.
void StartBleDiscovery();
// Initiates pairing of the device that the user has chosen.
//
// Valid action when at step: kBleDeviceSelection.
void InitiatePairingDevice(base::StringPiece authenticator_id);
// Finishes pairing of the previously chosen device with the |pin| code
// entered.
//
// Valid action when at step: kBlePinEntry.
void FinishPairingWithPin(const base::string16& pin);
// Dispatches WebAuthN request to successfully paired Bluetooth authenticator.
//
// Valid action when at step: kBleVerifying.
void OnPairingSuccess();
// Returns to Bluetooth device selection modal.
//
// Valid action when at step: kBleVerifying.
void OnPairingFailure();
// Tries if a USB device is present -- the user claims they plugged it in.
//
// Valid action when at step: kUsbInsert.
void TryUsbDevice();
// Tries to use Touch ID -- either because the request requires it or because
// the user told us to. May show an error for unrecognized credential, or an
// Incognito mode interstitial, or proceed straight to the Touch ID prompt.
//
// Valid action when at all steps.
void StartTouchIdFlow();
// Proceeds straight to the Touch ID prompt.
//
// Valid action when at all steps.
void HideDialogAndTryTouchId();
// Cancels the flow as a result of the user clicking `Cancel` on the UI.
//
// Valid action at all steps.
void Cancel();
// Called by the AuthenticatorRequestSheetModel subclasses when their state
// changes, which will trigger notifying observers of OnSheetModelChanged.
void OnSheetModelDidChange();
// The |observer| must either outlive the object, or unregister itself on its
// destruction.
void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer);
// To be called when the Web Authentication request is complete.
void OnRequestComplete();
// To be called when Web Authentication request times-out.
void OnRequestTimeout();
// To be called when the user activates a security key that does not recognize
// any of the allowed credentials (during a GetAssertion request).
void OnActivatedKeyNotRegistered();
// To be called when the user activates a security key that does recognize
// one of excluded credentials (during a MakeCredential request).
void OnActivatedKeyAlreadyRegistered();
// To be called when the selected authenticator cannot currently handle PIN
// requests because it needs a power-cycle due to too many failures.
void OnSoftPINBlock();
// To be called when the selected authenticator must be reset before
// performing any PIN operations because of too many failures.
void OnHardPINBlock();
// To be called when the selected authenticator was removed while
// waiting for a PIN to be entered.
void OnAuthenticatorRemovedDuringPINEntry();
// To be called when the selected authenticator doesn't have the requested
// resident key capability.
void OnAuthenticatorMissingResidentKeys();
// To be called when the selected authenticator doesn't have the requested
// user verification capability.
void OnAuthenticatorMissingUserVerification();
// To be called when the selected authenticator cannot create a resident
// credential because of insufficient storage.
void OnAuthenticatorStorageFull();
// To be called when the user denies consent, e.g. by clicking "Cancel" on the
// system Touch ID prompt.
void OnUserConsentDenied();
// To be called when the Bluetooth adapter powered state changes.
void OnBluetoothPoweredStateChanged(bool powered);
void SetRequestCallback(RequestCallback request_callback);
void SetBlePairingCallback(BlePairingCallback ble_pairing_callback);
void SetBluetoothAdapterPowerOnCallback(
base::RepeatingClosure bluetooth_adapter_power_on_callback);
void SetBleDevicePairedCallback(
BleDevicePairedCallback ble_device_paired_callback);
void SetPINCallback(base::OnceCallback<void(std::string)> pin_callback);
// OnHavePIN is called when the user enters a PIN in the UI.
void OnHavePIN(const std::string& pin);
// OnResidentCredentialConfirmed is called when a user accepts a dialog
// confirming that they're happy to create a resident credential.
void OnResidentCredentialConfirmed();
// OnAttestationPermissionResponse is called when the user either allows or
// disallows an attestation permission request.
void OnAttestationPermissionResponse(bool attestation_permission_granted);
void UpdateAuthenticatorReferenceId(base::StringPiece old_authenticator_id,
std::string new_authenticator_id);
void AddAuthenticator(const device::FidoAuthenticator& authenticator);
void RemoveAuthenticator(base::StringPiece authenticator_id);
void UpdateAuthenticatorReferencePairingMode(
base::StringPiece authenticator_id,
bool is_in_pairing_mode,
base::string16 display_name);
// SelectAccount is called to trigger an account selection dialog.
void SelectAccount(
std::vector<device::AuthenticatorGetAssertionResponse> responses,
base::OnceCallback<void(device::AuthenticatorGetAssertionResponse)>
callback);
// OnAccountSelected is called when one of the accounts from |SelectAccount|
// has been picked. |index| is the index of the selected account in
// |responses()|.
void OnAccountSelected(size_t index);
// OnSuccess is called when a WebAuthn operation completes successfully.
void OnSuccess(AuthenticatorTransport transport);
void SetSelectedAuthenticatorForTesting(AuthenticatorReference authenticator);
ObservableAuthenticatorList& saved_authenticators() {
return ephemeral_state_.saved_authenticators_;
}
const std::vector<AuthenticatorTransport>& available_transports() {
return available_transports_;
}
base::span<const uint8_t, 32> qr_generator_key() const {
return *qr_generator_key_;
}
void CollectPIN(base::Optional<int> attempts,
base::OnceCallback<void(std::string)> provide_pin_cb);
bool has_attempted_pin_entry() const {
return ephemeral_state_.has_attempted_pin_entry_;
}
base::Optional<int> pin_attempts() const { return pin_attempts_; }
void RequestAttestationPermission(base::OnceCallback<void(bool)> callback);
const std::vector<device::AuthenticatorGetAssertionResponse>& responses() {
return ephemeral_state_.responses_;
}
void set_has_attempted_pin_entry_for_testing() {
ephemeral_state_.has_attempted_pin_entry_ = true;
}
void set_incognito_mode(bool incognito_mode) {
incognito_mode_ = incognito_mode;
}
bool might_create_resident_credential() const {
return might_create_resident_credential_;
}
void set_might_create_resident_credential(bool v) {
might_create_resident_credential_ = v;
}
void set_cable_transport_info(
bool cable_extension_provided,
bool has_paired_phones,
base::Optional<device::QRGeneratorKey> qr_generator_key);
const std::string& relying_party_id() const { return relying_party_id_; }
bool request_may_start_over() const { return request_may_start_over_; }
private:
// Contains the state that will be reset when calling StartOver(). StartOver()
// might be called at an arbitrary point of execution.
struct EphemeralState {
EphemeralState();
~EphemeralState();
void Reset();
// Represents the id of the Bluetooth authenticator that the user is trying
// to connect to or conduct WebAuthN request to via the WebAuthN UI.
base::Optional<std::string> selected_authenticator_id_;
// Transport type and id of Mac TouchId and BLE authenticators are cached so
// that the WebAuthN request for the corresponding authenticators can be
// dispatched lazily after the user interacts with the UI element.
ObservableAuthenticatorList saved_authenticators_;
bool has_attempted_pin_entry_ = false;
// responses_ contains possible accounts to select between.
std::vector<device::AuthenticatorGetAssertionResponse> responses_;
};
void DispatchRequestAsync(AuthenticatorReference* authenticator);
void DispatchRequestAsyncInternal(const std::string& authenticator_id);
EphemeralState ephemeral_state_;
// relying_party_id is the RP ID from Webauthn, essentially a domain name.
const std::string relying_party_id_;
// The current step of the request UX flow that is currently shown.
Step current_step_ = Step::kNotStarted;
// Determines which step to continue with once the Blueooth adapter is
// powered. Only set while the |current_step_| is either kBlePowerOnManual,
// kBlePowerOnAutomatic.
base::Optional<Step> next_step_once_ble_powered_;
// Determines whether Bluetooth device selection UI and pin pairing UI should
// be shown. We proceed directly to Step::kBleVerifying if the user has paired
// with a bluetooth authenticator previously.
bool previously_paired_with_bluetooth_authenticator_ = false;
base::ObserverList<Observer>::Unchecked observers_;
// These fields are only filled out when the UX flow is started.
TransportAvailabilityInfo transport_availability_;
std::vector<AuthenticatorTransport> available_transports_;
base::Optional<device::FidoTransportProtocol> last_used_transport_;
RequestCallback request_callback_;
BlePairingCallback ble_pairing_callback_;
base::RepeatingClosure bluetooth_adapter_power_on_callback_;
BleDevicePairedCallback ble_device_paired_callback_;
base::OnceCallback<void(std::string)> pin_callback_;
base::Optional<int> pin_attempts_;
base::OnceCallback<void(bool)> attestation_callback_;
// might_create_resident_credential_ records whether activating an
// authenticator may cause a resident credential to be created. A resident
// credential may be discovered by someone with physical access to the
// authenticator and thus has privacy implications.
bool might_create_resident_credential_ = false;
base::OnceCallback<void(device::AuthenticatorGetAssertionResponse)>
selection_callback_;
bool incognito_mode_ = false;
// request_may_start_over_ indicates whether a button to retry the request
// should be included on the dialog sheet shown when encountering certain
// errors.
bool request_may_start_over_ = true;
// cable_extension_provided_ indicates whether the request included a caBLE
// extension.
bool cable_extension_provided_ = false;
// have_paired_phones_ indicates whether this profile knows of any paired
// phones.
bool have_paired_phones_ = false;
base::Optional<device::QRGeneratorKey> qr_generator_key_;
// did_cable_broadcast_ is true if a caBLE v1 extension was provided and
// BLE adverts were broadcast.
bool did_cable_broadcast_ = false;
base::WeakPtrFactory<AuthenticatorRequestDialogModel> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(AuthenticatorRequestDialogModel);
};
#endif // CHROME_BROWSER_WEBAUTHN_AUTHENTICATOR_REQUEST_DIALOG_MODEL_H_