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