1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef CHROME_BROWSER_WEBAUTHN_AUTHENTICATOR_REQUEST_DIALOG_MODEL_H_
6 #define CHROME_BROWSER_WEBAUTHN_AUTHENTICATOR_REQUEST_DIALOG_MODEL_H_
7 
8 #include <memory>
9 #include <string>
10 #include <vector>
11 
12 #include "base/containers/span.h"
13 #include "base/memory/weak_ptr.h"
14 #include "base/observer_list.h"
15 #include "base/optional.h"
16 #include "base/strings/string16.h"
17 #include "base/strings/string_piece.h"
18 #include "base/values.h"
19 #include "build/build_config.h"
20 #include "chrome/browser/webauthn/authenticator_reference.h"
21 #include "chrome/browser/webauthn/authenticator_transport.h"
22 #include "chrome/browser/webauthn/observable_authenticator_list.h"
23 #include "device/fido/fido_request_handler_base.h"
24 #include "device/fido/fido_transport_protocol.h"
25 
26 namespace device {
27 class AuthenticatorGetAssertionResponse;
28 }
29 
30 // Encapsulates the model behind the Web Authentication request dialog's UX
31 // flow. This is essentially a state machine going through the states defined in
32 // the `Step` enumeration.
33 //
34 // Ultimately, this will become an observer of the AuthenticatorRequest, and
35 // contain the logic to figure out which steps the user needs to take, in which
36 // order, to complete the authentication flow.
37 class AuthenticatorRequestDialogModel {
38  public:
39   using RequestCallback = device::FidoRequestHandlerBase::RequestCallback;
40   using BlePairingCallback = device::FidoRequestHandlerBase::BlePairingCallback;
41   using BleDevicePairedCallback = base::RepeatingCallback<void(std::string)>;
42   using TransportAvailabilityInfo =
43       device::FidoRequestHandlerBase::TransportAvailabilityInfo;
44 
45   // Defines the potential steps of the Web Authentication API request UX flow.
46   enum class Step {
47     // The UX flow has not started yet, the dialog should still be hidden.
48     kNotStarted,
49 
50     kTransportSelection,
51 
52     // The request errored out before completing. Error will only be sent
53     // after user interaction.
54     kErrorNoAvailableTransports,
55     kErrorInternalUnrecognized,
56 
57     // The request is already complete, but the error dialog should wait
58     // until user acknowledgement.
59     kTimedOut,
60     kKeyNotRegistered,
61     kKeyAlreadyRegistered,
62     kMissingCapability,
63     kStorageFull,
64 
65     // The request is completed, and the dialog should be closed.
66     kClosed,
67 
68     // Universal Serial Bus (USB).
69     kUsbInsertAndActivate,
70 
71     // Bluetooth Low Energy (BLE).
72     kBlePowerOnAutomatic,
73     kBlePowerOnManual,
74 
75     // Touch ID.
76     kTouchIdIncognitoSpeedBump,
77 
78     // Phone as a security key.
79     kCableActivate,
80     kCableV2Activate,
81     kCableV2QRCode,
82 
83     // Authenticator Client PIN.
84     kClientPinEntry,
85     kClientPinSetup,
86     kClientPinTapAgain,
87     kClientPinErrorSoftBlock,
88     kClientPinErrorHardBlock,
89     kClientPinErrorAuthenticatorRemoved,
90 
91     // Authenticator Internal User Verification
92     kInlineBioEnrollment,
93     kRetryInternalUserVerification,
94 
95     // Confirm user consent to create a resident credential. Used prior to
96     // triggering Windows-native APIs when Windows itself won't show any
97     // notice about resident credentials.
98     kResidentCredentialConfirmation,
99 
100     // Account selection,
101     kSelectAccount,
102 
103     // Attestation permission request.
104     kAttestationPermissionRequest,
105   };
106 
107   // Implemented by the dialog to observe this model and show the UI panels
108   // appropriate for the current step.
109   class Observer {
110    public:
111     // Called when the user clicks "Try Again" to restart the user flow.
OnStartOver()112     virtual void OnStartOver() {}
113 
114     // Called just before the model is destructed.
115     virtual void OnModelDestroyed() = 0;
116 
117     // Called when the UX flow has navigated to a different step, so the UI
118     // should update.
OnStepTransition()119     virtual void OnStepTransition() {}
120 
121     // Called when the model corresponding to the current sheet of the UX flow
122     // was updated, so UI should update.
OnSheetModelChanged()123     virtual void OnSheetModelChanged() {}
124 
125     // Called when the power state of the Bluetooth adapter has changed.
OnBluetoothPoweredStateChanged()126     virtual void OnBluetoothPoweredStateChanged() {}
127 
128     // Called when the user cancelled WebAuthN request by clicking the
129     // "cancel" button or the back arrow in the UI dialog.
OnCancelRequest()130     virtual void OnCancelRequest() {}
131   };
132 
133   explicit AuthenticatorRequestDialogModel(const std::string& relying_party_id);
134   ~AuthenticatorRequestDialogModel();
135 
136   void SetCurrentStep(Step step);
current_step()137   Step current_step() const { return current_step_; }
138 
139   // Hides the dialog. A subsequent call to SetCurrentStep() will unhide it.
140   void HideDialog();
141 
142   // Returns whether the UI is in a state at which the |request_| member of
143   // AuthenticatorImpl has completed processing. Note that the request callback
144   // is only resolved after the UI is dismissed.
is_request_complete()145   bool is_request_complete() const {
146     return current_step() == Step::kTimedOut ||
147            current_step() == Step::kKeyNotRegistered ||
148            current_step() == Step::kKeyAlreadyRegistered ||
149            current_step() == Step::kMissingCapability ||
150            current_step() == Step::kClosed;
151   }
152 
should_dialog_be_closed()153   bool should_dialog_be_closed() const {
154     return current_step() == Step::kClosed;
155   }
should_dialog_be_hidden()156   bool should_dialog_be_hidden() const {
157     return current_step() == Step::kNotStarted;
158   }
159 
transport_availability()160   const TransportAvailabilityInfo* transport_availability() const {
161     return &transport_availability_;
162   }
163 
ble_adapter_is_powered()164   bool ble_adapter_is_powered() const {
165     return transport_availability()->is_ble_powered;
166   }
167 
selected_authenticator_id()168   const base::Optional<std::string>& selected_authenticator_id() const {
169     return ephemeral_state_.selected_authenticator_id_;
170   }
171 
172   // Starts the UX flow, by either showing the transport selection screen or
173   // the guided flow for them most likely transport.
174   //
175   // Valid action when at step: kNotStarted.
176   void StartFlow(
177       TransportAvailabilityInfo transport_availability,
178       base::Optional<device::FidoTransportProtocol> last_used_transport);
179 
180   // Restarts the UX flow.
181   void StartOver();
182 
183   // Starts the UX flow. Tries to figure out the most likely transport to be
184   // used, and starts the guided flow for that transport; or shows the manual
185   // transport selection screen if the transport could not be uniquely
186   // identified.
187   //
188   // Valid action when at step: kNotStarted.
189   void StartGuidedFlowForMostLikelyTransportOrShowTransportSelection();
190 
191   // Requests that the step-by-step wizard flow commence, guiding the user
192   // through using the Secutity Key with the given |transport|.
193   //
194   // Valid action when at step: kNotStarted.
195   // kTransportSelection, and steps where the other transports menu is shown,
196   // namely, kUsbInsertAndActivate, kCableActivate.
197   void StartGuidedFlowForTransport(AuthenticatorTransport transport);
198 
199   // Hides the modal Chrome UI dialog and shows the native Windows WebAuthn
200   // UI instead.
201   void HideDialogAndDispatchToNativeWindowsApi();
202 
203   // Displays a resident-key warning if needed and then calls
204   // |HideDialogAndDispatchToNativeWindowsApi|.
205   void StartWinNativeApi();
206 
207   // StartPhonePairing triggers the display of a QR code for pairing a new
208   // phone.
209   void StartPhonePairing();
210 
211   // Ensures that the Bluetooth adapter is powered before proceeding to |step|.
212   //  -- If the adapter is powered, advanced directly to |step|.
213   //  -- If the adapter is not powered, but Chrome can turn it automatically,
214   //     then advanced to the flow to turn on Bluetooth automatically.
215   //  -- Otherwise advanced to the manual Bluetooth power on flow.
216   //
217   // Valid action when at step: kNotStarted, kTransportSelection, and steps
218   // where the other transports menu is shown, namely, kUsbInsertAndActivate,
219   // kCableActivate.
220   void EnsureBleAdapterIsPoweredAndContinueWithCable();
221 
222   // Continues with the BLE/caBLE flow now that the Bluetooth adapter is
223   // powered.
224   //
225   // Valid action when at step: kBlePowerOnManual, kBlePowerOnAutomatic.
226   void ContinueWithFlowAfterBleAdapterPowered();
227 
228   // Turns on the BLE adapter automatically.
229   //
230   // Valid action when at step: kBlePowerOnAutomatic.
231   void PowerOnBleAdapter();
232 
233   // Tries if a USB device is present -- the user claims they plugged it in.
234   //
235   // Valid action when at step: kUsbInsert.
236   void TryUsbDevice();
237 
238   // Tries to use Touch ID -- either because the request requires it or because
239   // the user told us to. May show an error for unrecognized credential, or an
240   // Incognito mode interstitial, or proceed straight to the Touch ID prompt.
241   //
242   // Valid action when at all steps.
243   void StartTouchIdFlow();
244 
245   // Proceeds straight to the Touch ID prompt.
246   //
247   // Valid action when at all steps.
248   void HideDialogAndTryTouchId();
249 
250   // Cancels the flow as a result of the user clicking `Cancel` on the UI.
251   //
252   // Valid action at all steps.
253   void Cancel();
254 
255   // Called by the AuthenticatorRequestSheetModel subclasses when their state
256   // changes, which will trigger notifying observers of OnSheetModelChanged.
257   void OnSheetModelDidChange();
258 
259   // The |observer| must either outlive the object, or unregister itself on its
260   // destruction.
261   void AddObserver(Observer* observer);
262   void RemoveObserver(Observer* observer);
263 
264   // To be called when the Web Authentication request is complete.
265   void OnRequestComplete();
266 
267   // To be called when Web Authentication request times-out.
268   void OnRequestTimeout();
269 
270   // To be called when the user activates a security key that does not recognize
271   // any of the allowed credentials (during a GetAssertion request).
272   void OnActivatedKeyNotRegistered();
273 
274   // To be called when the user activates a security key that does recognize
275   // one of excluded credentials (during a MakeCredential request).
276   void OnActivatedKeyAlreadyRegistered();
277 
278   // To be called when the selected authenticator cannot currently handle PIN
279   // requests because it needs a power-cycle due to too many failures.
280   void OnSoftPINBlock();
281 
282   // To be called when the selected authenticator must be reset before
283   // performing any PIN operations because of too many failures.
284   void OnHardPINBlock();
285 
286   // To be called when the selected authenticator was removed while
287   // waiting for a PIN to be entered.
288   void OnAuthenticatorRemovedDuringPINEntry();
289 
290   // To be called when the selected authenticator doesn't have the requested
291   // resident key capability.
292   void OnAuthenticatorMissingResidentKeys();
293 
294   // To be called when the selected authenticator doesn't have the requested
295   // user verification capability.
296   void OnAuthenticatorMissingUserVerification();
297 
298   // To be called when the selected authenticator doesn't have the requested
299   // large blob capability.
300   void OnAuthenticatorMissingLargeBlob();
301 
302   // To be called when the selected authenticator doesn't support any of the
303   // COSEAlgorithmIdentifiers requested by the RP.
304   void OnNoCommonAlgorithms();
305 
306   // To be called when the selected authenticator cannot create a resident
307   // credential because of insufficient storage.
308   void OnAuthenticatorStorageFull();
309 
310   // To be called when the user denies consent, e.g. by clicking "Cancel" on the
311   // system Touch ID prompt.
312   void OnUserConsentDenied();
313 
314   // To be called when the user clicks "Cancel" in the native Windows UI.
315   // Returns true if the event was handled.
316   bool OnWinUserCancelled();
317 
318   // To be called when the Bluetooth adapter powered state changes.
319   void OnBluetoothPoweredStateChanged(bool powered);
320 
321   void SetRequestCallback(RequestCallback request_callback);
322 
323   void SetBluetoothAdapterPowerOnCallback(
324       base::RepeatingClosure bluetooth_adapter_power_on_callback);
325 
326   void SetPINCallback(base::OnceCallback<void(std::string)> pin_callback);
327 
328   // OnHavePIN is called when the user enters a PIN in the UI.
329   void OnHavePIN(const std::string& pin);
330 
331   // Called when the user needs to retry user verification with the number of
332   // |attempts| remaining.
333   void OnRetryUserVerification(int attempts);
334 
335   // OnResidentCredentialConfirmed is called when a user accepts a dialog
336   // confirming that they're happy to create a resident credential.
337   void OnResidentCredentialConfirmed();
338 
339   // OnAttestationPermissionResponse is called when the user either allows or
340   // disallows an attestation permission request.
341   void OnAttestationPermissionResponse(bool attestation_permission_granted);
342 
343   void AddAuthenticator(const device::FidoAuthenticator& authenticator);
344   void RemoveAuthenticator(base::StringPiece authenticator_id);
345 
346   // SelectAccount is called to trigger an account selection dialog.
347   void SelectAccount(
348       std::vector<device::AuthenticatorGetAssertionResponse> responses,
349       base::OnceCallback<void(device::AuthenticatorGetAssertionResponse)>
350           callback);
351 
352   // OnAccountSelected is called when one of the accounts from |SelectAccount|
353   // has been picked. |index| is the index of the selected account in
354   // |responses()|.
355   void OnAccountSelected(size_t index);
356 
357   void SetSelectedAuthenticatorForTesting(AuthenticatorReference authenticator);
358 
saved_authenticators()359   ObservableAuthenticatorList& saved_authenticators() {
360     return ephemeral_state_.saved_authenticators_;
361   }
362 
available_transports()363   const base::flat_set<AuthenticatorTransport>& available_transports() {
364     return transport_availability_.available_transports;
365   }
366 
cable_qr_string()367   const std::string& cable_qr_string() const { return *cable_qr_string_; }
368 
369   void CollectPIN(base::Optional<int> attempts,
370                   base::OnceCallback<void(std::string)> provide_pin_cb);
has_attempted_pin_entry()371   bool has_attempted_pin_entry() const {
372     return ephemeral_state_.has_attempted_pin_entry_;
373   }
pin_attempts()374   base::Optional<int> pin_attempts() const { return pin_attempts_; }
375 
376   void StartInlineBioEnrollment(base::OnceClosure next_callback);
377   void OnSampleCollected(int bio_samples_remaining);
378   void OnBioEnrollmentDone();
max_bio_samples()379   base::Optional<int> max_bio_samples() { return max_bio_samples_; }
bio_samples_remaining()380   base::Optional<int> bio_samples_remaining() { return bio_samples_remaining_; }
381 
382   // Flags the authenticator's internal user verification as locked.
set_internal_uv_locked()383   void set_internal_uv_locked() { uv_attempts_ = 0; }
uv_attempts()384   base::Optional<int> uv_attempts() const { return uv_attempts_; }
385 
386   void RequestAttestationPermission(base::OnceCallback<void(bool)> callback);
387 
responses()388   const std::vector<device::AuthenticatorGetAssertionResponse>& responses() {
389     return ephemeral_state_.responses_;
390   }
391 
set_has_attempted_pin_entry_for_testing()392   void set_has_attempted_pin_entry_for_testing() {
393     ephemeral_state_.has_attempted_pin_entry_ = true;
394   }
395 
set_incognito_mode(bool incognito_mode)396   void set_incognito_mode(bool incognito_mode) {
397     incognito_mode_ = incognito_mode;
398   }
399 
might_create_resident_credential()400   bool might_create_resident_credential() const {
401     return might_create_resident_credential_;
402   }
403 
set_might_create_resident_credential(bool v)404   void set_might_create_resident_credential(bool v) {
405     might_create_resident_credential_ = v;
406   }
407 
408   void set_cable_transport_info(
409       bool cable_extension_provided,
410       bool has_paired_phones,
411       const base::Optional<std::string>& cable_qr_string);
412 
win_native_api_enabled()413   bool win_native_api_enabled() const {
414     return transport_availability_.has_win_native_api_authenticator;
415   }
416 
cable_extension_provided()417   bool cable_extension_provided() const { return cable_extension_provided_; }
418 
relying_party_id()419   const std::string& relying_party_id() const { return relying_party_id_; }
420 
offer_try_again_in_ui()421   bool offer_try_again_in_ui() const { return offer_try_again_in_ui_; }
422 
423   base::WeakPtr<AuthenticatorRequestDialogModel> GetWeakPtr();
424 
425  private:
426   // Contains the state that will be reset when calling StartOver(). StartOver()
427   // might be called at an arbitrary point of execution.
428   struct EphemeralState {
429     EphemeralState();
430     ~EphemeralState();
431 
432     void Reset();
433 
434     // Represents the id of the Bluetooth authenticator that the user is trying
435     // to connect to or conduct WebAuthN request to via the WebAuthN UI.
436     base::Optional<std::string> selected_authenticator_id_;
437 
438     // Transport type and id of Mac TouchId and BLE authenticators are cached so
439     // that the WebAuthN request for the corresponding authenticators can be
440     // dispatched lazily after the user interacts with the UI element.
441     ObservableAuthenticatorList saved_authenticators_;
442 
443     bool has_attempted_pin_entry_ = false;
444 
445     // responses_ contains possible accounts to select between.
446     std::vector<device::AuthenticatorGetAssertionResponse> responses_;
447   };
448 
449   void DispatchRequestAsync(AuthenticatorReference* authenticator);
450   void DispatchRequestAsyncInternal(const std::string& authenticator_id);
451 
452   EphemeralState ephemeral_state_;
453 
454   // relying_party_id is the RP ID from Webauthn, essentially a domain name.
455   const std::string relying_party_id_;
456 
457   // The current step of the request UX flow that is currently shown.
458   Step current_step_ = Step::kNotStarted;
459 
460   // Determines which step to continue with once the Blueooth adapter is
461   // powered. Only set while the |current_step_| is either kBlePowerOnManual,
462   // kBlePowerOnAutomatic.
463   base::Optional<Step> next_step_once_ble_powered_;
464 
465   base::ObserverList<Observer>::Unchecked observers_;
466 
467   // These fields are only filled out when the UX flow is started.
468   TransportAvailabilityInfo transport_availability_;
469   base::Optional<device::FidoTransportProtocol> last_used_transport_;
470 
471   RequestCallback request_callback_;
472   base::RepeatingClosure bluetooth_adapter_power_on_callback_;
473 
474   base::Optional<int> max_bio_samples_;
475   base::Optional<int> bio_samples_remaining_;
476   base::OnceClosure bio_enrollment_callback_;
477 
478   base::OnceCallback<void(std::string)> pin_callback_;
479   base::Optional<int> pin_attempts_;
480   base::Optional<int> uv_attempts_;
481 
482   base::OnceCallback<void(bool)> attestation_callback_;
483 
484   // might_create_resident_credential_ records whether activating an
485   // authenticator may cause a resident credential to be created. A resident
486   // credential may be discovered by someone with physical access to the
487   // authenticator and thus has privacy implications.
488   bool might_create_resident_credential_ = false;
489 
490   base::OnceCallback<void(device::AuthenticatorGetAssertionResponse)>
491       selection_callback_;
492 
493   bool incognito_mode_ = false;
494 
495   // offer_try_again_in_ui_ indicates whether a button to retry the request
496   // should be included on the dialog sheet shown when encountering certain
497   // errors.
498   bool offer_try_again_in_ui_ = true;
499 
500   // cable_extension_provided_ indicates whether the request included a caBLE
501   // extension.
502   bool cable_extension_provided_ = false;
503   // have_paired_phones_ indicates whether this profile knows of any paired
504   // phones.
505   bool have_paired_phones_ = false;
506   base::Optional<std::string> cable_qr_string_;
507   // win_native_api_already_tried_ is true if the Windows-native UI has been
508   // displayed already and the user cancelled it. In this case, we shouldn't
509   // jump straight to showing it again.
510   bool win_native_api_already_tried_ = false;
511 
512   base::WeakPtrFactory<AuthenticatorRequestDialogModel> weak_factory_{this};
513 
514   DISALLOW_COPY_AND_ASSIGN(AuthenticatorRequestDialogModel);
515 };
516 
517 #endif  // CHROME_BROWSER_WEBAUTHN_AUTHENTICATOR_REQUEST_DIALOG_MODEL_H_
518