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 #include "device/fido/make_credential_request_handler.h"
6 
7 #include <set>
8 #include <string>
9 #include <utility>
10 
11 #include "base/bind.h"
12 #include "base/metrics/histogram_functions.h"
13 #include "base/stl_util.h"
14 #include "build/build_config.h"
15 #include "components/cbor/diagnostic_writer.h"
16 #include "components/device_event_log/device_event_log.h"
17 #include "device/fido/fido_authenticator.h"
18 #include "device/fido/fido_parsing_utils.h"
19 #include "device/fido/fido_transport_protocol.h"
20 #include "device/fido/make_credential_task.h"
21 #include "device/fido/pin.h"
22 
23 #if defined(OS_WIN)
24 #include "device/fido/win/authenticator.h"
25 #include "device/fido/win/type_conversions.h"
26 #include "third_party/microsoft_webauthn/webauthn.h"
27 #endif
28 
29 namespace device {
30 
31 using ClientPinAvailability =
32     AuthenticatorSupportedOptions::ClientPinAvailability;
33 using MakeCredentialPINDisposition =
34     FidoAuthenticator::MakeCredentialPINDisposition;
35 
36 namespace {
37 
ConvertDeviceResponseCode(CtapDeviceResponseCode device_response_code)38 base::Optional<MakeCredentialStatus> ConvertDeviceResponseCode(
39     CtapDeviceResponseCode device_response_code) {
40   switch (device_response_code) {
41     case CtapDeviceResponseCode::kSuccess:
42       return MakeCredentialStatus::kSuccess;
43 
44     // Only returned after the user interacted with the authenticator.
45     case CtapDeviceResponseCode::kCtap2ErrCredentialExcluded:
46       return MakeCredentialStatus::kUserConsentButCredentialExcluded;
47 
48     // The user explicitly denied the operation. Touch ID returns this error
49     // when the user cancels the macOS prompt. External authenticators may
50     // return it e.g. after the user fails fingerprint verification.
51     case CtapDeviceResponseCode::kCtap2ErrOperationDenied:
52       return MakeCredentialStatus::kUserConsentDenied;
53 
54     // External authenticators may return this error if internal user
55     // verification fails for a make credential request or if the pin token is
56     // not valid.
57     case CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid:
58       return MakeCredentialStatus::kUserConsentDenied;
59 
60     case CtapDeviceResponseCode::kCtap2ErrKeyStoreFull:
61       return MakeCredentialStatus::kStorageFull;
62 
63     // For all other errors, the authenticator will be dropped, and other
64     // authenticators may continue.
65     default:
66       return base::nullopt;
67   }
68 }
69 
70 // IsCandidateAuthenticatorPreTouch returns true if the given authenticator
71 // should even blink for a request.
IsCandidateAuthenticatorPreTouch(FidoAuthenticator * authenticator,const AuthenticatorSelectionCriteria & authenticator_selection_criteria)72 bool IsCandidateAuthenticatorPreTouch(
73     FidoAuthenticator* authenticator,
74     const AuthenticatorSelectionCriteria& authenticator_selection_criteria) {
75   const auto& opt_options = authenticator->Options();
76   if (!opt_options) {
77     // This authenticator doesn't know its capabilities yet, so we need
78     // to assume it can handle the request. This is the case for Windows,
79     // where we proxy the request to the native API.
80     return true;
81   }
82 
83   if ((authenticator_selection_criteria.authenticator_attachment() ==
84            AuthenticatorAttachment::kPlatform &&
85        !opt_options->is_platform_device) ||
86       (authenticator_selection_criteria.authenticator_attachment() ==
87            AuthenticatorAttachment::kCrossPlatform &&
88        opt_options->is_platform_device)) {
89     return false;
90   }
91 
92   return true;
93 }
94 
95 // IsCandidateAuthenticatorPostTouch returns a value other than |kSuccess| if
96 // the given authenticator cannot handle a request.
IsCandidateAuthenticatorPostTouch(const CtapMakeCredentialRequest & request,FidoAuthenticator * authenticator,const AuthenticatorSelectionCriteria & authenticator_selection_criteria,const FidoRequestHandlerBase::Observer * observer)97 MakeCredentialStatus IsCandidateAuthenticatorPostTouch(
98     const CtapMakeCredentialRequest& request,
99     FidoAuthenticator* authenticator,
100     const AuthenticatorSelectionCriteria& authenticator_selection_criteria,
101     const FidoRequestHandlerBase::Observer* observer) {
102   const auto& opt_options = authenticator->Options();
103 #if defined(OS_WIN)
104   if (authenticator->IsWinNativeApiAuthenticator()) {
105     // This authenticator doesn't know its capabilities yet, so we need
106     // to assume it can handle the request. This is the case for Windows,
107     // where we proxy the request to the native API.
108     DCHECK(!opt_options);
109 
110     if (request.cred_protect && request.cred_protect->second &&
111         !static_cast<WinWebAuthnApiAuthenticator*>(authenticator)
112              ->SupportsCredProtectExtension()) {
113       return MakeCredentialStatus::kAuthenticatorMissingResidentKeys;
114     }
115 
116     return MakeCredentialStatus::kSuccess;
117   }
118 #endif  // defined(OS_WIN)
119 
120   DCHECK(opt_options);
121 
122   if (authenticator_selection_criteria.require_resident_key() &&
123       !opt_options->supports_resident_key) {
124     return MakeCredentialStatus::kAuthenticatorMissingResidentKeys;
125   }
126 
127   if (request.cred_protect && request.cred_protect->second &&
128       !authenticator->Options()->supports_cred_protect) {
129     return MakeCredentialStatus::kAuthenticatorMissingResidentKeys;
130   }
131 
132   if (authenticator->WillNeedPINToMakeCredential(request, observer) ==
133       MakeCredentialPINDisposition::kUnsatisfiable) {
134     return MakeCredentialStatus::kAuthenticatorMissingUserVerification;
135   }
136 
137   return MakeCredentialStatus::kSuccess;
138 }
139 
GetTransportsAllowedByRP(const AuthenticatorSelectionCriteria & authenticator_selection_criteria)140 base::flat_set<FidoTransportProtocol> GetTransportsAllowedByRP(
141     const AuthenticatorSelectionCriteria& authenticator_selection_criteria) {
142   const auto attachment_type =
143       authenticator_selection_criteria.authenticator_attachment();
144   switch (attachment_type) {
145     case AuthenticatorAttachment::kPlatform:
146       return {FidoTransportProtocol::kInternal};
147     case AuthenticatorAttachment::kCrossPlatform:
148       return {FidoTransportProtocol::kUsbHumanInterfaceDevice,
149               FidoTransportProtocol::kBluetoothLowEnergy,
150               FidoTransportProtocol::kNearFieldCommunication,
151               FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy};
152     case AuthenticatorAttachment::kAny:
153       return {FidoTransportProtocol::kInternal,
154               FidoTransportProtocol::kNearFieldCommunication,
155               FidoTransportProtocol::kUsbHumanInterfaceDevice,
156               FidoTransportProtocol::kBluetoothLowEnergy,
157               FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy};
158   }
159 
160   NOTREACHED();
161   return base::flat_set<FidoTransportProtocol>();
162 }
163 
ReportMakeCredentialRequestTransport(FidoAuthenticator * authenticator)164 void ReportMakeCredentialRequestTransport(FidoAuthenticator* authenticator) {
165   if (authenticator->AuthenticatorTransport()) {
166     base::UmaHistogramEnumeration(
167         "WebAuthentication.MakeCredentialRequestTransport",
168         *authenticator->AuthenticatorTransport());
169   }
170 }
171 
172 // ValidateResponseExtensions returns true iff |extensions| is valid as a
173 // response to |request| from an authenticator that reports that it supports
174 // |options|.
ValidateResponseExtensions(const CtapMakeCredentialRequest & request,const AuthenticatorSupportedOptions & options,const cbor::Value & extensions)175 bool ValidateResponseExtensions(const CtapMakeCredentialRequest& request,
176                                 const AuthenticatorSupportedOptions& options,
177                                 const cbor::Value& extensions) {
178   if (!extensions.is_map()) {
179     return false;
180   }
181 
182   for (const auto& it : extensions.GetMap()) {
183     if (!it.first.is_string()) {
184       return false;
185     }
186     const std::string& ext_name = it.first.GetString();
187 
188     if (ext_name == kExtensionCredProtect) {
189       if (!options.supports_cred_protect || !it.second.is_integer()) {
190         return false;
191       }
192 
193       // The authenticator can return any valid credProtect value that is
194       // equal to, or greater than, what was requested, including when
195       // nothing was requested.
196       const int64_t requested_level =
197           request.cred_protect
198               ? base::strict_cast<int64_t>(request.cred_protect->first)
199               : 1;
200       const int64_t returned_level = it.second.GetInteger();
201 
202       if (returned_level < requested_level ||
203           returned_level >
204               base::strict_cast<int64_t>(CredProtect::kUVRequired)) {
205         FIDO_LOG(ERROR) << "Returned credProtect level (" << returned_level
206                         << ") is invalid or less than the requested level ("
207                         << requested_level << ")";
208         return false;
209       }
210     } else if (ext_name == kExtensionHmacSecret) {
211       if (!request.hmac_secret || !it.second.is_bool()) {
212         return false;
213       }
214     } else {
215       // Authenticators may not return unknown extensions.
216       return false;
217     }
218   }
219 
220   return true;
221 }
222 
223 }  // namespace
224 
MakeCredentialRequestHandler(FidoDiscoveryFactory * fido_discovery_factory,const base::flat_set<FidoTransportProtocol> & supported_transports,CtapMakeCredentialRequest request,AuthenticatorSelectionCriteria authenticator_selection_criteria,bool allow_skipping_pin_touch,CompletionCallback completion_callback)225 MakeCredentialRequestHandler::MakeCredentialRequestHandler(
226     FidoDiscoveryFactory* fido_discovery_factory,
227     const base::flat_set<FidoTransportProtocol>& supported_transports,
228     CtapMakeCredentialRequest request,
229     AuthenticatorSelectionCriteria authenticator_selection_criteria,
230     bool allow_skipping_pin_touch,
231     CompletionCallback completion_callback)
232     : FidoRequestHandlerBase(
233           fido_discovery_factory,
234           base::STLSetIntersection<base::flat_set<FidoTransportProtocol>>(
235               supported_transports,
236               GetTransportsAllowedByRP(authenticator_selection_criteria))),
237       completion_callback_(std::move(completion_callback)),
238       request_(std::move(request)),
239       authenticator_selection_criteria_(
240           std::move(authenticator_selection_criteria)),
241       allow_skipping_pin_touch_(allow_skipping_pin_touch) {
242   transport_availability_info().request_type =
243       FidoRequestHandlerBase::RequestType::kMakeCredential;
244 
245   // Only send the googleAndroidClientData extension to authenticators that
246   // support it.
247   if (request_.android_client_data_ext) {
248     android_client_data_ext_ = *request_.android_client_data_ext;
249     request_.android_client_data_ext.reset();
250   }
251 
252   // Set the rk, uv and attachment fields, which were only initialized to
253   // default values up to here.  TODO(martinkr): Initialize these fields earlier
254   // (in AuthenticatorImpl) and get rid of the separate
255   // AuthenticatorSelectionCriteriaParameter.
256   if (authenticator_selection_criteria_.require_resident_key()) {
257     request_.resident_key_required = true;
258     request_.user_verification = UserVerificationRequirement::kRequired;
259   } else {
260     request_.resident_key_required = false;
261     request_.user_verification =
262         authenticator_selection_criteria_.user_verification_requirement();
263   }
264   request_.authenticator_attachment =
265       authenticator_selection_criteria_.authenticator_attachment();
266 
267   Start();
268 }
269 
270 MakeCredentialRequestHandler::~MakeCredentialRequestHandler() = default;
271 
DispatchRequest(FidoAuthenticator * authenticator)272 void MakeCredentialRequestHandler::DispatchRequest(
273     FidoAuthenticator* authenticator) {
274   DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
275 
276   if (state_ != State::kWaitingForTouch ||
277       !IsCandidateAuthenticatorPreTouch(authenticator,
278                                         authenticator_selection_criteria_)) {
279     return;
280   }
281 
282   if (IsCandidateAuthenticatorPostTouch(
283           request_, authenticator, authenticator_selection_criteria_,
284           observer()) != MakeCredentialStatus::kSuccess) {
285 #if defined(OS_WIN)
286     // If the Windows API cannot handle a request, just reject the request
287     // outright. There are no other authenticators to attempt, so calling
288     // GetTouch() would not make sense.
289     if (authenticator->IsWinNativeApiAuthenticator()) {
290       HandleInapplicableAuthenticator(authenticator);
291       return;
292     }
293 #endif  // defined(OS_WIN)
294 
295     if (authenticator->Options() &&
296         authenticator->Options()->is_platform_device) {
297       HandleInapplicableAuthenticator(authenticator);
298       return;
299     }
300 
301     // This authenticator does not meet requirements, but make it flash anyway
302     // so the user understands that it's functional. A descriptive error message
303     // will be shown if the user selects it.
304     authenticator->GetTouch(base::BindOnce(
305         &MakeCredentialRequestHandler::HandleInapplicableAuthenticator,
306         weak_factory_.GetWeakPtr(), authenticator));
307     return;
308   }
309 
310   switch (authenticator->WillNeedPINToMakeCredential(request_, observer())) {
311     case MakeCredentialPINDisposition::kUsePIN:
312       // Skip asking for touch if this is the only available authenticator.
313       if (active_authenticators().size() == 1 && allow_skipping_pin_touch_) {
314         CollectPINThenSendRequest(authenticator);
315         return;
316       }
317       // A PIN will be needed. Just request a touch to let the user select
318       // this authenticator if they wish.
319       authenticator->GetTouch(base::BindOnce(
320           &MakeCredentialRequestHandler::CollectPINThenSendRequest,
321           weak_factory_.GetWeakPtr(), authenticator));
322       return;
323 
324     case MakeCredentialPINDisposition::kSetPIN:
325       // Skip asking for touch if this is the only available authenticator.
326       if (active_authenticators().size() == 1 && allow_skipping_pin_touch_) {
327         SetPINThenSendRequest(authenticator);
328         return;
329       }
330       // A PIN will be needed. Just request a touch to let the user select
331       // this authenticator if they wish.
332       authenticator->GetTouch(
333           base::BindOnce(&MakeCredentialRequestHandler::SetPINThenSendRequest,
334                          weak_factory_.GetWeakPtr(), authenticator));
335       return;
336 
337     case MakeCredentialPINDisposition::kNoPIN:
338     case MakeCredentialPINDisposition::kUsePINForFallback:
339       break;
340 
341     case MakeCredentialPINDisposition::kUnsatisfiable:
342       // |IsCandidateAuthenticatorPostTouch| should have handled this case.
343       NOTREACHED();
344       return;
345   }
346 
347   CtapMakeCredentialRequest request(request_);
348   if (authenticator->Options()) {
349     // If the authenticator has UV configured then UV will be required in
350     // order to create a credential (as specified by CTAP 2.0), even if
351     // user-verification is "discouraged". However, if the request is U2F-only
352     // then that doesn't apply and UV must be set to discouraged so that the
353     // request can be translated to U2F. Platform authenticators are exempted
354     // from this UV enforcement.
355     if (authenticator->Options()->user_verification_availability ==
356             AuthenticatorSupportedOptions::UserVerificationAvailability::
357                 kSupportedAndConfigured &&
358         !request_.is_u2f_only &&
359         authenticator->AuthenticatorTransport() !=
360             FidoTransportProtocol::kInternal) {
361       if (authenticator->Options()->supports_uv_token) {
362         authenticator->GetUvToken(
363             base::BindOnce(&MakeCredentialRequestHandler::OnHaveUvToken,
364                            weak_factory_.GetWeakPtr(), authenticator));
365         return;
366       }
367       request.user_verification = UserVerificationRequirement::kRequired;
368     } else {
369       request.user_verification = UserVerificationRequirement::kDiscouraged;
370     }
371 
372     if (request.cred_protect &&
373         !authenticator->Options()->supports_cred_protect) {
374       request.cred_protect.reset();
375     }
376     if (android_client_data_ext_ && authenticator->Options() &&
377         authenticator->Options()->supports_android_client_data_ext) {
378       request.android_client_data_ext = *android_client_data_ext_;
379     }
380   }
381 
382   ReportMakeCredentialRequestTransport(authenticator);
383 
384   authenticator->MakeCredential(
385       std::move(request),
386       base::BindOnce(&MakeCredentialRequestHandler::HandleResponse,
387                      weak_factory_.GetWeakPtr(), authenticator));
388 }
389 
AuthenticatorRemoved(FidoDiscoveryBase * discovery,FidoAuthenticator * authenticator)390 void MakeCredentialRequestHandler::AuthenticatorRemoved(
391     FidoDiscoveryBase* discovery,
392     FidoAuthenticator* authenticator) {
393   DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
394 
395   FidoRequestHandlerBase::AuthenticatorRemoved(discovery, authenticator);
396 
397   if (authenticator == authenticator_) {
398     authenticator_ = nullptr;
399     if (state_ == State::kWaitingForPIN || state_ == State::kWaitingForNewPIN ||
400         state_ == State::kWaitingForSecondTouch) {
401       state_ = State::kFinished;
402       std::move(completion_callback_)
403           .Run(MakeCredentialStatus::kAuthenticatorRemovedDuringPINEntry,
404                base::nullopt, nullptr);
405     }
406   }
407 }
408 
HandleResponse(FidoAuthenticator * authenticator,CtapDeviceResponseCode status,base::Optional<AuthenticatorMakeCredentialResponse> response)409 void MakeCredentialRequestHandler::HandleResponse(
410     FidoAuthenticator* authenticator,
411     CtapDeviceResponseCode status,
412     base::Optional<AuthenticatorMakeCredentialResponse> response) {
413   DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
414 
415   if (state_ != State::kWaitingForTouch &&
416       state_ != State::kWaitingForSecondTouch) {
417     return;
418   }
419 
420 #if defined(OS_WIN)
421   if (authenticator->IsWinNativeApiAuthenticator()) {
422     state_ = State::kFinished;
423     CancelActiveAuthenticators(authenticator->GetId());
424     std::move(completion_callback_)
425         .Run(WinCtapDeviceResponseCodeToMakeCredentialStatus(status),
426              std::move(response), authenticator);
427     return;
428   }
429 #endif
430 
431   // Requests that require a PIN should follow the |GetTouch| path initially.
432   MakeCredentialPINDisposition will_need_pin =
433       authenticator->WillNeedPINToMakeCredential(request_, observer());
434   DCHECK(state_ == State::kWaitingForSecondTouch ||
435          will_need_pin == MakeCredentialPINDisposition::kNoPIN ||
436          will_need_pin == MakeCredentialPINDisposition::kUsePINForFallback);
437 
438   if ((status == CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid ||
439        status == CtapDeviceResponseCode::kCtap2ErrPinRequired) &&
440       authenticator->WillNeedPINToMakeCredential(request_, observer()) ==
441           MakeCredentialPINDisposition::kUsePINForFallback) {
442     // Some authenticators will return this error immediately without user
443     // interaction when internal UV is locked.
444     if (AuthenticatorMayHaveReturnedImmediately(authenticator->GetId())) {
445       authenticator->GetTouch(base::BindOnce(
446           &MakeCredentialRequestHandler::StartPINFallbackForInternalUv,
447           weak_factory_.GetWeakPtr(), authenticator));
448       return;
449     }
450     StartPINFallbackForInternalUv(authenticator);
451     return;
452   }
453 
454   const base::Optional<MakeCredentialStatus> maybe_result =
455       ConvertDeviceResponseCode(status);
456   if (!maybe_result) {
457     if (state_ == State::kWaitingForSecondTouch) {
458       std::move(completion_callback_)
459           .Run(MakeCredentialStatus::kAuthenticatorResponseInvalid,
460                base::nullopt, authenticator);
461     } else {
462       FIDO_LOG(ERROR) << "Ignoring status " << static_cast<int>(status)
463                       << " from " << authenticator->GetDisplayName();
464     }
465     return;
466   }
467 
468   state_ = State::kFinished;
469   CancelActiveAuthenticators(authenticator->GetId());
470 
471   if (status != CtapDeviceResponseCode::kSuccess) {
472     FIDO_LOG(ERROR) << "Failing make credential request due to status "
473                     << static_cast<int>(status) << " from "
474                     << authenticator->GetDisplayName();
475     std::move(completion_callback_)
476         .Run(*maybe_result, base::nullopt, authenticator);
477     return;
478   }
479 
480   const auto rp_id_hash = fido_parsing_utils::CreateSHA256Hash(request_.rp.id);
481 
482   if (!response || response->GetRpIdHash() != rp_id_hash) {
483     FIDO_LOG(ERROR)
484         << "Failing make credential request due to bad response from "
485         << authenticator->GetDisplayName();
486     std::move(completion_callback_)
487         .Run(MakeCredentialStatus::kAuthenticatorResponseInvalid, base::nullopt,
488              authenticator);
489     return;
490   }
491 
492   const base::Optional<cbor::Value>& extensions =
493       response->attestation_object().authenticator_data().extensions();
494   if (extensions && !ValidateResponseExtensions(
495                         request_, *authenticator->Options(), *extensions)) {
496     FIDO_LOG(ERROR)
497         << "Failing make credential request due to extensions block: "
498         << cbor::DiagnosticWriter::Write(*extensions);
499     std::move(completion_callback_)
500         .Run(MakeCredentialStatus::kAuthenticatorResponseInvalid, base::nullopt,
501              authenticator);
502     return;
503   }
504 
505   if (response->android_client_data_ext() &&
506       (!android_client_data_ext_ || !authenticator->Options() ||
507        !authenticator->Options()->supports_android_client_data_ext ||
508        !IsValidAndroidClientDataJSON(
509            *android_client_data_ext_,
510            base::StringPiece(reinterpret_cast<const char*>(
511                                  response->android_client_data_ext()->data()),
512                              response->android_client_data_ext()->size())))) {
513     FIDO_LOG(ERROR) << "Invalid androidClientData extension";
514     std::move(completion_callback_)
515         .Run(MakeCredentialStatus::kAuthenticatorResponseInvalid, base::nullopt,
516              authenticator);
517     return;
518   }
519 
520   if (authenticator->AuthenticatorTransport()) {
521     base::UmaHistogramEnumeration(
522         "WebAuthentication.MakeCredentialResponseTransport",
523         *authenticator->AuthenticatorTransport());
524   }
525 
526   std::move(completion_callback_)
527       .Run(MakeCredentialStatus::kSuccess, std::move(response), authenticator);
528 }
529 
CollectPINThenSendRequest(FidoAuthenticator * authenticator)530 void MakeCredentialRequestHandler::CollectPINThenSendRequest(
531     FidoAuthenticator* authenticator) {
532   if (state_ != State::kWaitingForTouch) {
533     return;
534   }
535   DCHECK(observer());
536   state_ = State::kGettingRetries;
537   CancelActiveAuthenticators(authenticator->GetId());
538   authenticator_ = authenticator;
539   authenticator_->GetPinRetries(
540       base::BindOnce(&MakeCredentialRequestHandler::OnRetriesResponse,
541                      weak_factory_.GetWeakPtr()));
542 }
543 
StartPINFallbackForInternalUv(FidoAuthenticator * authenticator)544 void MakeCredentialRequestHandler::StartPINFallbackForInternalUv(
545     FidoAuthenticator* authenticator) {
546   DCHECK(authenticator->WillNeedPINToMakeCredential(request_, observer()) ==
547          MakeCredentialPINDisposition::kUsePINForFallback);
548   observer()->OnInternalUserVerificationLocked();
549   CollectPINThenSendRequest(authenticator);
550 }
551 
SetPINThenSendRequest(FidoAuthenticator * authenticator)552 void MakeCredentialRequestHandler::SetPINThenSendRequest(
553     FidoAuthenticator* authenticator) {
554   DCHECK(authenticator->WillNeedPINToMakeCredential(request_, observer()) ==
555          MakeCredentialPINDisposition::kSetPIN);
556   if (state_ != State::kWaitingForTouch) {
557     return;
558   }
559   state_ = State::kWaitingForNewPIN;
560   CancelActiveAuthenticators(authenticator->GetId());
561   authenticator_ = authenticator;
562   observer()->CollectPIN(
563       base::nullopt, base::BindOnce(&MakeCredentialRequestHandler::OnHavePIN,
564                                     weak_factory_.GetWeakPtr()));
565 }
566 
HandleInternalUvLocked(FidoAuthenticator * authenticator)567 void MakeCredentialRequestHandler::HandleInternalUvLocked(
568     FidoAuthenticator* authenticator) {
569   state_ = State::kFinished;
570   CancelActiveAuthenticators(authenticator->GetId());
571   std::move(completion_callback_)
572       .Run(MakeCredentialStatus::kAuthenticatorMissingUserVerification,
573            base::nullopt, nullptr);
574 }
575 
HandleInapplicableAuthenticator(FidoAuthenticator * authenticator)576 void MakeCredentialRequestHandler::HandleInapplicableAuthenticator(
577     FidoAuthenticator* authenticator) {
578   // User touched an authenticator that cannot handle this request.
579   state_ = State::kFinished;
580   CancelActiveAuthenticators(authenticator->GetId());
581   const MakeCredentialStatus capability_error =
582       IsCandidateAuthenticatorPostTouch(request_, authenticator,
583                                         authenticator_selection_criteria_,
584                                         observer());
585   DCHECK_NE(capability_error, MakeCredentialStatus::kSuccess);
586   std::move(completion_callback_).Run(capability_error, base::nullopt, nullptr);
587 }
588 
OnHavePIN(std::string pin)589 void MakeCredentialRequestHandler::OnHavePIN(std::string pin) {
590   DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
591   DCHECK(state_ == State::kWaitingForPIN || state_ == State::kWaitingForNewPIN);
592   DCHECK(pin::IsValid(pin));
593 
594   if (authenticator_ == nullptr) {
595     // Authenticator was detached. The request will already have been canceled
596     // but this callback may have been waiting in a queue.
597     DCHECK(!completion_callback_);
598     return;
599   }
600 
601   if (state_ == State::kWaitingForPIN) {
602     state_ = State::kRequestWithPIN;
603     authenticator_->GetPINToken(
604         std::move(pin),
605         base::BindOnce(&MakeCredentialRequestHandler::OnHavePINToken,
606                        weak_factory_.GetWeakPtr()));
607     return;
608   }
609 
610   DCHECK_EQ(state_, State::kWaitingForNewPIN);
611   state_ = State::kSettingPIN;
612   authenticator_->SetPIN(
613       pin, base::BindOnce(&MakeCredentialRequestHandler::OnHaveSetPIN,
614                           weak_factory_.GetWeakPtr(), pin));
615 }
616 
OnRetriesResponse(CtapDeviceResponseCode status,base::Optional<pin::RetriesResponse> response)617 void MakeCredentialRequestHandler::OnRetriesResponse(
618     CtapDeviceResponseCode status,
619     base::Optional<pin::RetriesResponse> response) {
620   DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
621   DCHECK_EQ(state_, State::kGettingRetries);
622   if (status != CtapDeviceResponseCode::kSuccess) {
623     state_ = State::kFinished;
624     std::move(completion_callback_)
625         .Run(MakeCredentialStatus::kAuthenticatorResponseInvalid, base::nullopt,
626              nullptr);
627     return;
628   }
629   if (response->retries == 0) {
630     state_ = State::kFinished;
631     std::move(completion_callback_)
632         .Run(MakeCredentialStatus::kHardPINBlock, base::nullopt, nullptr);
633     return;
634   }
635   state_ = State::kWaitingForPIN;
636   observer()->CollectPIN(
637       response->retries,
638       base::BindOnce(&MakeCredentialRequestHandler::OnHavePIN,
639                      weak_factory_.GetWeakPtr()));
640 }
641 
OnHaveSetPIN(std::string pin,CtapDeviceResponseCode status,base::Optional<pin::EmptyResponse> response)642 void MakeCredentialRequestHandler::OnHaveSetPIN(
643     std::string pin,
644     CtapDeviceResponseCode status,
645     base::Optional<pin::EmptyResponse> response) {
646   DCHECK_EQ(state_, State::kSettingPIN);
647 
648   if (status != CtapDeviceResponseCode::kSuccess) {
649     state_ = State::kFinished;
650     std::move(completion_callback_)
651         .Run(MakeCredentialStatus::kAuthenticatorResponseInvalid, base::nullopt,
652              nullptr);
653     return;
654   }
655 
656   // Having just set the PIN, we need to immediately turn around and use it to
657   // get a PIN token.
658   state_ = State::kRequestWithPIN;
659   authenticator_->GetPINToken(
660       std::move(pin),
661       base::BindOnce(&MakeCredentialRequestHandler::OnHavePINToken,
662                      weak_factory_.GetWeakPtr()));
663 }
664 
OnHavePINToken(CtapDeviceResponseCode status,base::Optional<pin::TokenResponse> response)665 void MakeCredentialRequestHandler::OnHavePINToken(
666     CtapDeviceResponseCode status,
667     base::Optional<pin::TokenResponse> response) {
668   DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
669   DCHECK_EQ(state_, State::kRequestWithPIN);
670 
671   if (status == CtapDeviceResponseCode::kCtap2ErrPinInvalid) {
672     state_ = State::kGettingRetries;
673     authenticator_->GetPinRetries(
674         base::BindOnce(&MakeCredentialRequestHandler::OnRetriesResponse,
675                        weak_factory_.GetWeakPtr()));
676     return;
677   }
678 
679   if (status != CtapDeviceResponseCode::kSuccess) {
680     state_ = State::kFinished;
681     MakeCredentialStatus ret;
682     switch (status) {
683       case CtapDeviceResponseCode::kCtap2ErrPinAuthBlocked:
684         ret = MakeCredentialStatus::kSoftPINBlock;
685         break;
686       case CtapDeviceResponseCode::kCtap2ErrPinBlocked:
687         ret = MakeCredentialStatus::kHardPINBlock;
688         break;
689       default:
690         ret = MakeCredentialStatus::kAuthenticatorResponseInvalid;
691         break;
692     }
693     std::move(completion_callback_).Run(ret, base::nullopt, nullptr);
694     return;
695   }
696 
697   DispatchRequestWithToken(std::move(*response));
698 }
699 
OnUvRetriesResponse(CtapDeviceResponseCode status,base::Optional<pin::RetriesResponse> response)700 void MakeCredentialRequestHandler::OnUvRetriesResponse(
701     CtapDeviceResponseCode status,
702     base::Optional<pin::RetriesResponse> response) {
703   if (status != CtapDeviceResponseCode::kSuccess) {
704     FIDO_LOG(ERROR) << "OnUvRetriesResponse() failed for "
705                     << authenticator_->GetDisplayName();
706     state_ = State::kFinished;
707     std::move(completion_callback_)
708         .Run(MakeCredentialStatus::kAuthenticatorResponseInvalid, base::nullopt,
709              nullptr);
710     return;
711   }
712   state_ = State::kWaitingForTouch;
713   if (response->retries == 0) {
714     // Fall back to PIN if able.
715     if (authenticator_->WillNeedPINToMakeCredential(request_, observer()) ==
716         MakeCredentialPINDisposition::kUsePINForFallback) {
717       StartPINFallbackForInternalUv(authenticator_);
718       return;
719     }
720     HandleInternalUvLocked(authenticator_);
721     return;
722   }
723   observer()->OnRetryUserVerification(response->retries);
724   authenticator_->GetUvToken(
725       base::BindOnce(&MakeCredentialRequestHandler::OnHaveUvToken,
726                      weak_factory_.GetWeakPtr(), authenticator_));
727 }
728 
OnHaveUvToken(FidoAuthenticator * authenticator,CtapDeviceResponseCode status,base::Optional<pin::TokenResponse> response)729 void MakeCredentialRequestHandler::OnHaveUvToken(
730     FidoAuthenticator* authenticator,
731     CtapDeviceResponseCode status,
732     base::Optional<pin::TokenResponse> response) {
733   DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
734   if (state_ != State::kWaitingForTouch) {
735     return;
736   }
737 
738   if (status == CtapDeviceResponseCode::kCtap2ErrPinInvalid ||
739       status == CtapDeviceResponseCode::kCtap2ErrOperationDenied ||
740       status == CtapDeviceResponseCode::kCtap2ErrUvBlocked) {
741     if (status == CtapDeviceResponseCode::kCtap2ErrUvBlocked) {
742       // This error is returned immediately without user interaction. Ask for a
743       // touch and fall back to PIN.
744       FIDO_LOG(DEBUG) << "Internal UV blocked for "
745                       << authenticator->GetDisplayName()
746                       << ", falling back to PIN.";
747       if (authenticator->WillNeedPINToMakeCredential(request_, observer()) ==
748           MakeCredentialPINDisposition::kUsePINForFallback) {
749         authenticator->GetTouch(base::BindOnce(
750             &MakeCredentialRequestHandler::StartPINFallbackForInternalUv,
751             weak_factory_.GetWeakPtr(), authenticator));
752         return;
753       }
754       authenticator->GetTouch(
755           base::BindOnce(&MakeCredentialRequestHandler::HandleInternalUvLocked,
756                          weak_factory_.GetWeakPtr(), authenticator));
757       return;
758     }
759     DCHECK(status == CtapDeviceResponseCode::kCtap2ErrPinInvalid ||
760            status == CtapDeviceResponseCode::kCtap2ErrOperationDenied);
761     CancelActiveAuthenticators(authenticator->GetId());
762     authenticator_ = authenticator;
763     state_ = State::kGettingRetries;
764     authenticator->GetUvRetries(
765         base::BindOnce(&MakeCredentialRequestHandler::OnUvRetriesResponse,
766                        weak_factory_.GetWeakPtr()));
767     return;
768   }
769 
770   if (status != CtapDeviceResponseCode::kSuccess) {
771     FIDO_LOG(ERROR) << "Ignoring status " << static_cast<int>(status)
772                     << " from " << authenticator->GetDisplayName();
773     return;
774   }
775 
776   CancelActiveAuthenticators(authenticator->GetId());
777   authenticator_ = authenticator;
778   DispatchRequestWithToken(std::move(*response));
779 }
780 
DispatchRequestWithToken(pin::TokenResponse token)781 void MakeCredentialRequestHandler::DispatchRequestWithToken(
782     pin::TokenResponse token) {
783   observer()->FinishCollectToken();
784   state_ = State::kWaitingForSecondTouch;
785   CtapMakeCredentialRequest request(request_);
786   request.pin_auth = token.PinAuth(request.client_data_hash);
787   request.pin_protocol = pin::kProtocolVersion;
788   // Do not do internal UV again.
789   request.user_verification = UserVerificationRequirement::kDiscouraged;
790   if (request.cred_protect && authenticator_->Options() &&
791       !authenticator_->Options()->supports_cred_protect) {
792     request.cred_protect.reset();
793   }
794   if (android_client_data_ext_ && authenticator_->Options() &&
795       authenticator_->Options()->supports_android_client_data_ext) {
796     request.android_client_data_ext = *android_client_data_ext_;
797   }
798 
799   ReportMakeCredentialRequestTransport(authenticator_);
800 
801   authenticator_->MakeCredential(
802       std::move(request),
803       base::BindOnce(&MakeCredentialRequestHandler::HandleResponse,
804                      weak_factory_.GetWeakPtr(), authenticator_));
805 }
806 
807 }  // namespace device
808