1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051 |
- // 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.
- #include "device/fido/make_credential_request_handler.h"
- #include <set>
- #include <utility>
- #include "base/barrier_closure.h"
- #include "base/bind.h"
- #include "base/containers/contains.h"
- #include "base/containers/cxx20_erase.h"
- #include "base/metrics/histogram_functions.h"
- #include "base/stl_util.h"
- #include "base/strings/string_number_conversions.h"
- #include "build/build_config.h"
- #include "build/chromeos_buildflags.h"
- #include "components/cbor/diagnostic_writer.h"
- #include "components/device_event_log/device_event_log.h"
- #include "device/fido/fido_authenticator.h"
- #include "device/fido/fido_discovery_factory.h"
- #include "device/fido/fido_parsing_utils.h"
- #include "device/fido/fido_transport_protocol.h"
- #include "device/fido/filter.h"
- #include "device/fido/make_credential_task.h"
- #if BUILDFLAG(IS_WIN)
- #include "device/fido/win/authenticator.h"
- #include "device/fido/win/type_conversions.h"
- #include "third_party/microsoft_webauthn/webauthn.h"
- #endif
- #if BUILDFLAG(IS_CHROMEOS)
- #include "device/fido/cros/authenticator.h"
- #endif
- namespace device {
- using PINUVDisposition = FidoAuthenticator::PINUVDisposition;
- using BioEnrollmentAvailability =
- AuthenticatorSupportedOptions::BioEnrollmentAvailability;
- namespace {
- // Permissions requested for PinUvAuthToken. GetAssertion is needed for silent
- // probing of credentials.
- const std::set<pin::Permissions> GetMakeCredentialRequestPermissions(
- FidoAuthenticator* authenticator) {
- std::set<pin::Permissions> permissions = {pin::Permissions::kMakeCredential,
- pin::Permissions::kGetAssertion};
- if (authenticator->Options() &&
- authenticator->Options()->bio_enrollment_availability ==
- BioEnrollmentAvailability::kSupportedButUnprovisioned) {
- permissions.insert(pin::Permissions::kBioEnrollment);
- }
- return permissions;
- }
- absl::optional<MakeCredentialStatus> ConvertDeviceResponseCode(
- CtapDeviceResponseCode device_response_code) {
- switch (device_response_code) {
- case CtapDeviceResponseCode::kSuccess:
- return MakeCredentialStatus::kSuccess;
- // Only returned after the user interacted with the authenticator.
- case CtapDeviceResponseCode::kCtap2ErrCredentialExcluded:
- return MakeCredentialStatus::kUserConsentButCredentialExcluded;
- // The user explicitly denied the operation. Touch ID returns this error
- // when the user cancels the macOS prompt. External authenticators may
- // return it e.g. after the user fails fingerprint verification.
- case CtapDeviceResponseCode::kCtap2ErrOperationDenied:
- return MakeCredentialStatus::kUserConsentDenied;
- // External authenticators may return this error if internal user
- // verification fails for a make credential request or if the pin token is
- // not valid.
- case CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid:
- return MakeCredentialStatus::kUserConsentDenied;
- case CtapDeviceResponseCode::kCtap2ErrKeyStoreFull:
- return MakeCredentialStatus::kStorageFull;
- // For all other errors, the authenticator will be dropped, and other
- // authenticators may continue.
- default:
- return absl::nullopt;
- }
- }
- // IsCandidateAuthenticatorPreTouch returns true if the given authenticator
- // should even blink for a request.
- bool IsCandidateAuthenticatorPreTouch(
- FidoAuthenticator* authenticator,
- AuthenticatorAttachment requested_attachment,
- bool allow_platform_authenticator_for_make_credential_request) {
- const auto& opt_options = authenticator->Options();
- if (!opt_options) {
- // This authenticator doesn't know its capabilities yet, so we need
- // to assume it can handle the request. This is the case for Windows,
- // where we proxy the request to the native API.
- return true;
- }
- if ((requested_attachment == AuthenticatorAttachment::kPlatform &&
- !opt_options->is_platform_device) ||
- (requested_attachment == AuthenticatorAttachment::kCrossPlatform &&
- opt_options->is_platform_device &&
- !allow_platform_authenticator_for_make_credential_request)) {
- return false;
- }
- return true;
- }
- // IsCandidateAuthenticatorPostTouch returns a value other than |kSuccess| if
- // the given authenticator cannot handle a request.
- MakeCredentialStatus IsCandidateAuthenticatorPostTouch(
- const CtapMakeCredentialRequest& request,
- FidoAuthenticator* authenticator,
- const MakeCredentialOptions& options,
- const FidoRequestHandlerBase::Observer* observer) {
- if (options.cred_protect_request && options.cred_protect_request->second &&
- !authenticator->SupportsCredProtectExtension()) {
- return MakeCredentialStatus::kAuthenticatorMissingResidentKeys;
- }
- const absl::optional<AuthenticatorSupportedOptions>& auth_options =
- authenticator->Options();
- if (!auth_options) {
- // This authenticator doesn't know its capabilities yet, so we need
- // to assume it can handle the request. This is the case for Windows,
- // where we proxy the request to the native API.
- return MakeCredentialStatus::kSuccess;
- }
- #if BUILDFLAG(IS_CHROMEOS)
- // Allow dispatch of UP-only cross-platform requests to the platform
- // authenticator to ensure backwards compatibility with the legacy
- // DeviceSecondFactorAuthentication enterprise policy.
- if (options.authenticator_attachment ==
- AuthenticatorAttachment::kCrossPlatform &&
- auth_options->is_platform_device) {
- if (options.resident_key == ResidentKeyRequirement::kRequired) {
- return MakeCredentialStatus::kAuthenticatorMissingResidentKeys;
- }
- if (options.user_verification == UserVerificationRequirement::kRequired) {
- return MakeCredentialStatus::kAuthenticatorMissingUserVerification;
- }
- return MakeCredentialStatus::kSuccess;
- }
- #endif
- if (options.resident_key == ResidentKeyRequirement::kRequired &&
- !auth_options->supports_resident_key) {
- return MakeCredentialStatus::kAuthenticatorMissingResidentKeys;
- }
- if (authenticator->PINUVDispositionForMakeCredential(request, observer) ==
- PINUVDisposition::kUnsatisfiable) {
- return MakeCredentialStatus::kAuthenticatorMissingUserVerification;
- }
- // The largeBlobs extension only works for resident credentials on CTAP 2.1
- // authenticators.
- if (options.large_blob_support == LargeBlobSupport::kRequired &&
- (!auth_options->supports_large_blobs || !request.resident_key_required)) {
- return MakeCredentialStatus::kAuthenticatorMissingLargeBlob;
- }
- absl::optional<base::span<const int32_t>> supported_algorithms(
- authenticator->GetAlgorithms());
- if (supported_algorithms) {
- // Substitution of defaults should have happened by this point.
- DCHECK(!request.public_key_credential_params.public_key_credential_params()
- .empty());
- bool at_least_one_common_algorithm = false;
- for (const auto& algo :
- request.public_key_credential_params.public_key_credential_params()) {
- if (algo.type != CredentialType::kPublicKey) {
- continue;
- }
- if (base::Contains(*supported_algorithms, algo.algorithm)) {
- at_least_one_common_algorithm = true;
- break;
- }
- }
- if (!at_least_one_common_algorithm) {
- return MakeCredentialStatus::kNoCommonAlgorithms;
- }
- }
- return MakeCredentialStatus::kSuccess;
- }
- base::flat_set<FidoTransportProtocol> GetTransportsAllowedByRP(
- AuthenticatorAttachment authenticator_attachment) {
- switch (authenticator_attachment) {
- case AuthenticatorAttachment::kPlatform:
- return {FidoTransportProtocol::kInternal};
- case AuthenticatorAttachment::kCrossPlatform:
- return {
- FidoTransportProtocol::kUsbHumanInterfaceDevice,
- FidoTransportProtocol::kBluetoothLowEnergy,
- FidoTransportProtocol::kNearFieldCommunication,
- FidoTransportProtocol::kHybrid,
- FidoTransportProtocol::kAndroidAccessory,
- };
- case AuthenticatorAttachment::kAny:
- return {
- FidoTransportProtocol::kInternal,
- FidoTransportProtocol::kNearFieldCommunication,
- FidoTransportProtocol::kUsbHumanInterfaceDevice,
- FidoTransportProtocol::kBluetoothLowEnergy,
- FidoTransportProtocol::kHybrid,
- FidoTransportProtocol::kAndroidAccessory,
- };
- }
- NOTREACHED();
- return base::flat_set<FidoTransportProtocol>();
- }
- void ReportMakeCredentialRequestTransport(FidoAuthenticator* authenticator) {
- if (authenticator->AuthenticatorTransport()) {
- base::UmaHistogramEnumeration(
- "WebAuthentication.MakeCredentialRequestTransport",
- *authenticator->AuthenticatorTransport());
- }
- }
- // CredProtectForAuthenticator translates a |CredProtectRequest| to a
- // |CredProtect| value given the capabilities of a specific authenticator.
- CredProtect CredProtectForAuthenticator(
- CredProtectRequest request,
- const FidoAuthenticator& authenticator) {
- switch (request) {
- case CredProtectRequest::kUVOptional:
- return CredProtect::kUVOptional;
- case CredProtectRequest::kUVOrCredIDRequired:
- return CredProtect::kUVOrCredIDRequired;
- case CredProtectRequest::kUVRequired:
- return CredProtect::kUVRequired;
- case CredProtectRequest::kUVOrCredIDRequiredOrBetter:
- if (authenticator.Options() &&
- authenticator.Options()->default_cred_protect ==
- CredProtect::kUVRequired) {
- return CredProtect::kUVRequired;
- }
- return CredProtect::kUVOrCredIDRequired;
- }
- }
- // ValidateResponseExtensions returns true iff |extensions| is valid as a
- // response to |request| from an authenticator that reports that it supports
- // |options|.
- bool ValidateResponseExtensions(const CtapMakeCredentialRequest& request,
- const MakeCredentialOptions& options,
- const FidoAuthenticator& authenticator,
- const cbor::Value& extensions) {
- if (!extensions.is_map()) {
- return false;
- }
- for (const auto& it : extensions.GetMap()) {
- if (!it.first.is_string()) {
- return false;
- }
- const std::string& ext_name = it.first.GetString();
- if (ext_name == kExtensionCredProtect) {
- if (!authenticator.SupportsCredProtectExtension() ||
- !it.second.is_integer()) {
- return false;
- }
- // The authenticator can return any valid credProtect value that is
- // equal to, or greater than, what was requested, including when
- // nothing was requested.
- const int64_t requested_level =
- options.cred_protect_request
- ? static_cast<int64_t>(CredProtectForAuthenticator(
- options.cred_protect_request->first, authenticator))
- : 1;
- const int64_t returned_level = it.second.GetInteger();
- if (returned_level < requested_level ||
- returned_level >
- base::strict_cast<int64_t>(CredProtect::kUVRequired)) {
- FIDO_LOG(ERROR) << "Returned credProtect level (" << returned_level
- << ") is invalid or less than the requested level ("
- << requested_level << ")";
- return false;
- }
- } else if (ext_name == kExtensionHmacSecret) {
- if (!request.hmac_secret || !it.second.is_bool()) {
- return false;
- }
- } else if (ext_name == kExtensionCredBlob) {
- if (!request.cred_blob || !it.second.is_bool()) {
- return false;
- }
- } else if (ext_name == kExtensionMinPINLength) {
- if (!request.min_pin_length_requested || !it.second.is_unsigned()) {
- return false;
- }
- } else {
- // Authenticators may not return unknown extensions.
- return false;
- }
- }
- return true;
- }
- // ResponseValid returns whether |response| is permissible for the given
- // |authenticator| and |request|.
- bool ResponseValid(const FidoAuthenticator& authenticator,
- const CtapMakeCredentialRequest& request,
- const AuthenticatorMakeCredentialResponse& response,
- const MakeCredentialOptions& options) {
- if (response.GetRpIdHash() !=
- fido_parsing_utils::CreateSHA256Hash(request.rp.id)) {
- FIDO_LOG(ERROR) << "Invalid RP ID hash";
- return false;
- }
- const absl::optional<cbor::Value>& extensions =
- response.attestation_object().authenticator_data().extensions();
- if (extensions && !ValidateResponseExtensions(request, options, authenticator,
- *extensions)) {
- FIDO_LOG(ERROR) << "Invalid extensions block: "
- << cbor::DiagnosticWriter::Write(*extensions);
- return false;
- }
- if (response.enterprise_attestation_returned &&
- (request.attestation_preference !=
- AttestationConveyancePreference::
- kEnterpriseIfRPListedOnAuthenticator &&
- request.attestation_preference !=
- AttestationConveyancePreference::kEnterpriseApprovedByBrowser)) {
- FIDO_LOG(ERROR) << "Enterprise attestation returned but not requested.";
- return false;
- }
- if (request.large_blob_key && !response.large_blob_key()) {
- FIDO_LOG(ERROR) << "Large blob key requested but not returned";
- return false;
- }
- return true;
- }
- } // namespace
- MakeCredentialRequestHandler::MakeCredentialRequestHandler(
- FidoDiscoveryFactory* fido_discovery_factory,
- const base::flat_set<FidoTransportProtocol>& supported_transports,
- CtapMakeCredentialRequest request,
- const MakeCredentialOptions& options,
- CompletionCallback completion_callback)
- : completion_callback_(std::move(completion_callback)),
- request_(std::move(request)),
- options_(options) {
- // These parts of the request should be filled in by
- // |SpecializeRequestForAuthenticator|.
- DCHECK_EQ(request_.authenticator_attachment, AuthenticatorAttachment::kAny);
- DCHECK(!request_.resident_key_required);
- DCHECK(!request_.cred_protect);
- DCHECK(!request_.cred_protect_enforce);
- transport_availability_info().request_type = FidoRequestType::kMakeCredential;
- transport_availability_info().is_off_the_record_context =
- options_.is_off_the_record_context;
- transport_availability_info().resident_key_requirement =
- options_.resident_key;
- base::flat_set<FidoTransportProtocol> allowed_transports =
- GetTransportsAllowedByRP(options.authenticator_attachment);
- #if BUILDFLAG(IS_CHROMEOS)
- // Attempt to instantiate the ChromeOS platform authenticator for
- // power-button-only requests for compatibility with the legacy
- // DeviceSecondFactorAuthentication policy, if that policy is enabled.
- if (!options_.make_u2f_api_credential &&
- options_.authenticator_attachment ==
- AuthenticatorAttachment::kCrossPlatform) {
- allow_platform_authenticator_for_cross_platform_request_ = true;
- fido_discovery_factory->set_require_legacy_cros_authenticator(true);
- allowed_transports.insert(FidoTransportProtocol::kInternal);
- }
- #endif
- InitDiscoveries(
- fido_discovery_factory,
- base::STLSetIntersection<base::flat_set<FidoTransportProtocol>>(
- supported_transports, allowed_transports));
- Start();
- }
- MakeCredentialRequestHandler::~MakeCredentialRequestHandler() = default;
- void MakeCredentialRequestHandler::DispatchRequest(
- FidoAuthenticator* authenticator) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
- if (state_ != State::kWaitingForTouch ||
- !IsCandidateAuthenticatorPreTouch(
- authenticator, options_.authenticator_attachment,
- allow_platform_authenticator_for_cross_platform_request_)) {
- return;
- }
- const std::string authenticator_name = authenticator->GetDisplayName();
- switch (fido_filter::Evaluate(
- fido_filter::Operation::MAKE_CREDENTIAL, request_.rp.id,
- authenticator_name,
- std::pair<fido_filter::IDType, base::span<const uint8_t>>(
- fido_filter::IDType::USER_ID, request_.user.id))) {
- case fido_filter::Action::ALLOW:
- break;
- case fido_filter::Action::NO_ATTESTATION:
- suppress_attestation_ = true;
- break;
- case fido_filter::Action::BLOCK:
- FIDO_LOG(DEBUG) << "Filtered request to device " << authenticator_name;
- return;
- }
- for (const auto& cred : request_.exclude_list) {
- if (fido_filter::Evaluate(
- fido_filter::Operation::MAKE_CREDENTIAL, request_.rp.id,
- authenticator_name,
- std::pair<fido_filter::IDType, base::span<const uint8_t>>(
- fido_filter::IDType::CREDENTIAL_ID, cred.id)) ==
- fido_filter::Action::BLOCK) {
- FIDO_LOG(DEBUG) << "Filtered request to device " << authenticator_name
- << " for credential ID " << base::HexEncode(cred.id);
- return;
- }
- }
- std::unique_ptr<CtapMakeCredentialRequest> request(
- new CtapMakeCredentialRequest(request_));
- SpecializeRequestForAuthenticator(request.get(), authenticator);
- const MakeCredentialStatus post_touch_status =
- IsCandidateAuthenticatorPostTouch(*request.get(), authenticator, options_,
- observer());
- if (post_touch_status != MakeCredentialStatus::kSuccess) {
- #if BUILDFLAG(IS_WIN)
- // If the Windows API cannot handle a request, just reject the request
- // outright. There are no other authenticators to attempt, so calling
- // GetTouch() would not make sense.
- if (authenticator->GetType() == FidoAuthenticator::Type::kWinNative) {
- HandleInapplicableAuthenticator(authenticator, post_touch_status);
- return;
- }
- #endif // BUILDFLAG(IS_WIN)
- if (authenticator->Options() &&
- authenticator->Options()->is_platform_device) {
- HandleInapplicableAuthenticator(authenticator, post_touch_status);
- return;
- }
- // This authenticator does not meet requirements, but make it flash anyway
- // so the user understands that it's functional. A descriptive error message
- // will be shown if the user selects it.
- authenticator->GetTouch(base::BindOnce(
- &MakeCredentialRequestHandler::HandleInapplicableAuthenticator,
- weak_factory_.GetWeakPtr(), authenticator, post_touch_status));
- return;
- }
- if (request->app_id_exclude && !request->exclude_list.empty()) {
- auto request_copy = *request;
- authenticator->ExcludeAppIdCredentialsBeforeMakeCredential(
- std::move(request_copy), options_,
- base::BindOnce(
- &MakeCredentialRequestHandler::DispatchRequestAfterAppIdExclude,
- weak_factory_.GetWeakPtr(), std::move(request), authenticator));
- } else {
- DispatchRequestAfterAppIdExclude(std::move(request), authenticator,
- CtapDeviceResponseCode::kSuccess,
- absl::nullopt);
- }
- }
- void MakeCredentialRequestHandler::DispatchRequestAfterAppIdExclude(
- std::unique_ptr<CtapMakeCredentialRequest> request,
- FidoAuthenticator* authenticator,
- CtapDeviceResponseCode status,
- absl::optional<bool> unused) {
- if (state_ != State::kWaitingForTouch) {
- return;
- }
- switch (status) {
- case CtapDeviceResponseCode::kSuccess:
- break;
- case CtapDeviceResponseCode::kCtap2ErrCredentialExcluded:
- // This authenticator contains an excluded credential. If touched, fail
- // the request.
- authenticator->GetTouch(base::BindOnce(
- &MakeCredentialRequestHandler::HandleExcludedAuthenticator,
- weak_factory_.GetWeakPtr(), authenticator));
- return;
- default:
- std::move(completion_callback_)
- .Run(MakeCredentialStatus::kAuthenticatorResponseInvalid,
- absl::nullopt, authenticator);
- return;
- }
- const bool skip_pin_touch =
- active_authenticators().size() == 1 && options_.allow_skipping_pin_touch;
- auto uv_disposition = authenticator->PINUVDispositionForMakeCredential(
- *request.get(), observer());
- switch (uv_disposition) {
- case PINUVDisposition::kNoUV:
- case PINUVDisposition::kNoTokenInternalUV:
- case PINUVDisposition::kNoTokenInternalUVPINFallback:
- break;
- case PINUVDisposition::kGetToken:
- ObtainPINUVAuthToken(authenticator, skip_pin_touch,
- /*internal_uv_locked=*/false);
- return;
- case PINUVDisposition::kUnsatisfiable:
- // |IsCandidateAuthenticatorPostTouch| should have handled this case.
- NOTREACHED();
- return;
- }
- ReportMakeCredentialRequestTransport(authenticator);
- auto request_copy(*request.get()); // can't copy and move in the same stmt.
- authenticator->MakeCredential(
- std::move(request_copy), options_,
- base::BindOnce(&MakeCredentialRequestHandler::HandleResponse,
- weak_factory_.GetWeakPtr(), authenticator,
- std::move(request), base::ElapsedTimer()));
- }
- void MakeCredentialRequestHandler::AuthenticatorRemoved(
- FidoDiscoveryBase* discovery,
- FidoAuthenticator* authenticator) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
- auth_token_requester_map_.erase(authenticator);
- FidoRequestHandlerBase::AuthenticatorRemoved(discovery, authenticator);
- if (authenticator == selected_authenticator_for_pin_uv_auth_token_) {
- selected_authenticator_for_pin_uv_auth_token_ = nullptr;
- // Authenticator could have been removed during PIN entry, PIN fallback
- // after failed internal UV, or bio enrollment. Bail and show an error.
- if (state_ != State::kFinished) {
- state_ = State::kFinished;
- std::move(completion_callback_)
- .Run(MakeCredentialStatus::kAuthenticatorRemovedDuringPINEntry,
- absl::nullopt, nullptr);
- }
- }
- }
- bool MakeCredentialRequestHandler::AuthenticatorSelectedForPINUVAuthToken(
- FidoAuthenticator* authenticator) {
- if (state_ != State::kWaitingForTouch) {
- // Some other authenticator was selected in the meantime.
- FIDO_LOG(DEBUG) << "Rejecting select request from AuthTokenRequester "
- "because another authenticator was already selected.";
- return false;
- }
- state_ = State::kWaitingForToken;
- selected_authenticator_for_pin_uv_auth_token_ = authenticator;
- base::EraseIf(auth_token_requester_map_, [authenticator](auto& entry) {
- return entry.first != authenticator;
- });
- CancelActiveAuthenticators(authenticator->GetId());
- return true;
- }
- void MakeCredentialRequestHandler::CollectPIN(
- pin::PINEntryReason reason,
- pin::PINEntryError error,
- uint32_t min_pin_length,
- int attempts,
- ProvidePINCallback provide_pin_cb) {
- DCHECK_EQ(state_, State::kWaitingForToken);
- observer()->CollectPIN({.reason = reason,
- .error = error,
- .min_pin_length = min_pin_length,
- .attempts = attempts},
- std::move(provide_pin_cb));
- }
- void MakeCredentialRequestHandler::PromptForInternalUVRetry(int attempts) {
- if (state_ != State::kWaitingForTouch && state_ != State::kWaitingForToken) {
- // Some other authenticator was touched in the meantime.
- return;
- }
- observer()->OnRetryUserVerification(attempts);
- }
- void MakeCredentialRequestHandler::HavePINUVAuthTokenResultForAuthenticator(
- FidoAuthenticator* authenticator,
- AuthTokenRequester::Result result,
- absl::optional<pin::TokenResponse> token_response) {
- absl::optional<MakeCredentialStatus> error;
- switch (result) {
- case AuthTokenRequester::Result::kPreTouchUnsatisfiableRequest:
- case AuthTokenRequester::Result::kPreTouchAuthenticatorResponseInvalid:
- FIDO_LOG(ERROR) << "Ignoring MakeCredentialStatus="
- << static_cast<int>(result) << " from "
- << authenticator->GetId();
- return;
- case AuthTokenRequester::Result::kPostTouchAuthenticatorInternalUVLock:
- error = MakeCredentialStatus::kAuthenticatorMissingUserVerification;
- break;
- case AuthTokenRequester::Result::kPostTouchAuthenticatorResponseInvalid:
- error = MakeCredentialStatus::kAuthenticatorResponseInvalid;
- break;
- case AuthTokenRequester::Result::kPostTouchAuthenticatorOperationDenied:
- error = MakeCredentialStatus::kUserConsentDenied;
- break;
- case AuthTokenRequester::Result::kPostTouchAuthenticatorPINSoftLock:
- error = MakeCredentialStatus::kSoftPINBlock;
- break;
- case AuthTokenRequester::Result::kPostTouchAuthenticatorPINHardLock:
- error = MakeCredentialStatus::kHardPINBlock;
- break;
- case AuthTokenRequester::Result::kSuccess:
- break;
- }
- // Pre touch events should be handled above.
- DCHECK_EQ(state_, State::kWaitingForToken);
- DCHECK_EQ(selected_authenticator_for_pin_uv_auth_token_, authenticator);
- if (error) {
- state_ = State::kFinished;
- std::move(completion_callback_).Run(*error, absl::nullopt, authenticator);
- return;
- }
- DCHECK_EQ(result, AuthTokenRequester::Result::kSuccess);
- auto request = std::make_unique<CtapMakeCredentialRequest>(request_);
- SpecializeRequestForAuthenticator(request.get(), authenticator);
- // If the authenticator supports biometric enrollment but is not enrolled,
- // offer enrollment with the request.
- if (authenticator->Options()->bio_enrollment_availability ==
- BioEnrollmentAvailability::kSupportedButUnprovisioned ||
- authenticator->Options()->bio_enrollment_availability_preview ==
- BioEnrollmentAvailability::kSupportedButUnprovisioned) {
- state_ = State::kBioEnrollment;
- bio_enroller_ =
- std::make_unique<BioEnroller>(this, authenticator, *token_response);
- bio_enrollment_complete_barrier_.emplace(base::BarrierClosure(
- 2, base::BindOnce(&MakeCredentialRequestHandler::OnEnrollmentComplete,
- weak_factory_.GetWeakPtr(), std::move(request))));
- observer()->StartBioEnrollment(
- base::BindOnce(&MakeCredentialRequestHandler::OnEnrollmentDismissed,
- weak_factory_.GetWeakPtr()));
- return;
- }
- DispatchRequestWithToken(authenticator, std::move(request),
- std::move(*token_response));
- }
- void MakeCredentialRequestHandler::ObtainPINUVAuthToken(
- FidoAuthenticator* authenticator,
- bool skip_pin_touch,
- bool internal_uv_locked) {
- AuthTokenRequester::Options options;
- options.token_permissions =
- GetMakeCredentialRequestPermissions(authenticator);
- options.rp_id = request_.rp.id;
- options.skip_pin_touch = skip_pin_touch;
- options.internal_uv_locked = internal_uv_locked;
- auth_token_requester_map_.insert(
- {authenticator, std::make_unique<AuthTokenRequester>(
- this, authenticator, std::move(options))});
- auth_token_requester_map_.at(authenticator)->ObtainPINUVAuthToken();
- }
- void MakeCredentialRequestHandler::HandleResponse(
- FidoAuthenticator* authenticator,
- std::unique_ptr<CtapMakeCredentialRequest> request,
- base::ElapsedTimer request_timer,
- CtapDeviceResponseCode status,
- absl::optional<AuthenticatorMakeCredentialResponse> response) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
- if (state_ != State::kWaitingForTouch &&
- state_ != State::kWaitingForResponseWithToken) {
- FIDO_LOG(DEBUG) << "Ignoring response from "
- << authenticator->GetDisplayName()
- << " because no longer waiting for touch";
- return;
- }
- if (selected_authenticator_for_pin_uv_auth_token_ &&
- authenticator != selected_authenticator_for_pin_uv_auth_token_) {
- FIDO_LOG(DEBUG) << "Ignoring response from "
- << authenticator->GetDisplayName()
- << " because another authenticator was selected";
- return;
- }
- #if BUILDFLAG(IS_WIN)
- if (authenticator->GetType() == FidoAuthenticator::Type::kWinNative) {
- state_ = State::kFinished;
- if (status != CtapDeviceResponseCode::kSuccess) {
- std::move(completion_callback_)
- .Run(WinCtapDeviceResponseCodeToMakeCredentialStatus(status),
- absl::nullopt, authenticator);
- return;
- }
- if (!response ||
- !ResponseValid(*authenticator, *request, *response, options_)) {
- FIDO_LOG(ERROR)
- << "Failing make credential request due to bad response from "
- << authenticator->GetDisplayName();
- std::move(completion_callback_)
- .Run(MakeCredentialStatus::kWinNotAllowedError, absl::nullopt,
- authenticator);
- return;
- }
- CancelActiveAuthenticators(authenticator->GetId());
- response->attestation_should_be_filtered = suppress_attestation_;
- std::move(completion_callback_)
- .Run(WinCtapDeviceResponseCodeToMakeCredentialStatus(status),
- std::move(*response), authenticator);
- return;
- }
- #endif
- // If we requested UV from an authentiator without uvToken support, UV failed,
- // and the authenticator supports PIN, fall back to that.
- if (request->user_verification != UserVerificationRequirement::kDiscouraged &&
- !request->pin_auth &&
- (status == CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid ||
- status == CtapDeviceResponseCode::kCtap2ErrPinRequired) &&
- authenticator->PINUVDispositionForMakeCredential(*request, observer()) ==
- PINUVDisposition::kNoTokenInternalUVPINFallback) {
- // Authenticators without uvToken support will return this error immediately
- // without user interaction when internal UV is locked.
- const base::TimeDelta response_time = request_timer.Elapsed();
- if (response_time < kMinExpectedAuthenticatorResponseTime) {
- FIDO_LOG(DEBUG) << "Authenticator is probably locked, response_time="
- << response_time;
- ObtainPINUVAuthToken(authenticator, /*skip_pin_touch=*/false,
- /*internal_uv_locked=*/true);
- return;
- }
- ObtainPINUVAuthToken(authenticator, /*skip_pin_touch=*/true,
- /*internal_uv_locked=*/true);
- return;
- }
- if (options_.resident_key == ResidentKeyRequirement::kPreferred &&
- request->resident_key_required &&
- status == CtapDeviceResponseCode::kCtap2ErrKeyStoreFull) {
- FIDO_LOG(DEBUG) << "Downgrading rk=preferred to non-resident credential "
- "because key storage is full";
- request->resident_key_required = false;
- CtapMakeCredentialRequest request_copy(*request);
- authenticator->MakeCredential(
- std::move(request_copy), options_,
- base::BindOnce(&MakeCredentialRequestHandler::HandleResponse,
- weak_factory_.GetWeakPtr(), authenticator,
- std::move(request), base::ElapsedTimer()));
- return;
- }
- if (status == CtapDeviceResponseCode::kCtap2ErrUnsupportedAlgorithm) {
- // The authenticator didn't support any of the requested public-key
- // algorithms. This status will have been returned immediately.
- // Collect a touch and tell the user that it's unsupported.
- authenticator->GetTouch(base::BindOnce(
- &MakeCredentialRequestHandler::HandleInapplicableAuthenticator,
- weak_factory_.GetWeakPtr(), authenticator,
- MakeCredentialStatus::kNoCommonAlgorithms));
- return;
- }
- const absl::optional<MakeCredentialStatus> maybe_result =
- ConvertDeviceResponseCode(status);
- if (!maybe_result) {
- if (state_ == State::kWaitingForResponseWithToken) {
- std::move(completion_callback_)
- .Run(MakeCredentialStatus::kAuthenticatorResponseInvalid,
- absl::nullopt, authenticator);
- } else {
- FIDO_LOG(ERROR) << "Ignoring status " << static_cast<int>(status)
- << " from " << authenticator->GetDisplayName();
- }
- return;
- }
- state_ = State::kFinished;
- CancelActiveAuthenticators(authenticator->GetId());
- if (status != CtapDeviceResponseCode::kSuccess) {
- FIDO_LOG(ERROR) << "Failing make credential request due to status "
- << static_cast<int>(status) << " from "
- << authenticator->GetDisplayName();
- std::move(completion_callback_)
- .Run(*maybe_result, absl::nullopt, authenticator);
- return;
- }
- if (!response ||
- !ResponseValid(*authenticator, *request, *response, options_)) {
- FIDO_LOG(ERROR)
- << "Failing make credential request due to bad response from "
- << authenticator->GetDisplayName();
- std::move(completion_callback_)
- .Run(MakeCredentialStatus::kAuthenticatorResponseInvalid, absl::nullopt,
- authenticator);
- return;
- }
- if (authenticator->AuthenticatorTransport()) {
- base::UmaHistogramEnumeration(
- "WebAuthentication.MakeCredentialResponseTransport",
- *authenticator->AuthenticatorTransport());
- }
- response->attestation_should_be_filtered = suppress_attestation_;
- std::move(completion_callback_)
- .Run(MakeCredentialStatus::kSuccess, std::move(*response), authenticator);
- }
- void MakeCredentialRequestHandler::HandleExcludedAuthenticator(
- FidoAuthenticator* authenticator) {
- // User touched an authenticator that contains an AppID-based excluded
- // credential.
- state_ = State::kFinished;
- CancelActiveAuthenticators(authenticator->GetId());
- std::move(completion_callback_)
- .Run(MakeCredentialStatus::kUserConsentButCredentialExcluded,
- absl::nullopt, nullptr);
- }
- void MakeCredentialRequestHandler::HandleInapplicableAuthenticator(
- FidoAuthenticator* authenticator,
- MakeCredentialStatus status) {
- // User touched an authenticator that cannot handle this request.
- DCHECK_NE(status, MakeCredentialStatus::kSuccess);
- state_ = State::kFinished;
- CancelActiveAuthenticators(authenticator->GetId());
- std::move(completion_callback_).Run(status, absl::nullopt, nullptr);
- }
- void MakeCredentialRequestHandler::OnSampleCollected(
- BioEnrollmentSampleStatus status,
- int samples_remaining) {
- observer()->OnSampleCollected(samples_remaining);
- }
- void MakeCredentialRequestHandler::OnEnrollmentDone(
- absl::optional<std::vector<uint8_t>> template_id) {
- state_ = State::kBioEnrollmentDone;
- bio_enrollment_complete_barrier_->Run();
- }
- void MakeCredentialRequestHandler::OnEnrollmentError(
- CtapDeviceResponseCode status) {
- bio_enroller_.reset();
- state_ = State::kFinished;
- std::move(completion_callback_)
- .Run(MakeCredentialStatus::kAuthenticatorResponseInvalid, absl::nullopt,
- nullptr);
- }
- void MakeCredentialRequestHandler::OnEnrollmentDismissed() {
- if (state_ != State::kBioEnrollmentDone) {
- // There is still an inflight enrollment request. Cancel it.
- bio_enroller_->Cancel();
- }
- bio_enrollment_complete_barrier_->Run();
- }
- void MakeCredentialRequestHandler::OnEnrollmentComplete(
- std::unique_ptr<CtapMakeCredentialRequest> request) {
- DCHECK(state_ == State::kBioEnrollmentDone);
- bio_enrollment_complete_barrier_.reset();
- auto token = bio_enroller_->token();
- FidoAuthenticator* authenticator = bio_enroller_->authenticator();
- DCHECK_EQ(authenticator, selected_authenticator_for_pin_uv_auth_token_);
- bio_enroller_.reset();
- DispatchRequestWithToken(authenticator, std::move(request), std::move(token));
- }
- void MakeCredentialRequestHandler::DispatchRequestWithToken(
- FidoAuthenticator* authenticator,
- std::unique_ptr<CtapMakeCredentialRequest> request,
- pin::TokenResponse token) {
- observer()->FinishCollectToken();
- state_ = State::kWaitingForResponseWithToken;
- std::tie(request->pin_protocol, request->pin_auth) =
- token.PinAuth(request->client_data_hash);
- request->pin_token_for_exclude_list_probing = std::move(token);
- ReportMakeCredentialRequestTransport(authenticator);
- auto request_copy(*request.get()); // can't copy and move in the same stmt.
- authenticator->MakeCredential(
- std::move(request_copy), options_,
- base::BindOnce(&MakeCredentialRequestHandler::HandleResponse,
- weak_factory_.GetWeakPtr(), authenticator,
- std::move(request), base::ElapsedTimer()));
- }
- void MakeCredentialRequestHandler::SpecializeRequestForAuthenticator(
- CtapMakeCredentialRequest* request,
- const FidoAuthenticator* authenticator) {
- #if BUILDFLAG(IS_CHROMEOS)
- if (authenticator->AuthenticatorTransport() ==
- FidoTransportProtocol::kInternal &&
- options_.authenticator_attachment ==
- AuthenticatorAttachment::kCrossPlatform) {
- request->resident_key_required = false;
- request->user_verification = UserVerificationRequirement::kDiscouraged;
- // None of the other options below are applicable.
- return;
- }
- #endif
- // Only Windows cares about |authenticator_attachment| on the request.
- request->authenticator_attachment = options_.authenticator_attachment;
- const absl::optional<AuthenticatorSupportedOptions>&
- auth_options_empty_on_win = authenticator->Options();
- switch (options_.resident_key) {
- case ResidentKeyRequirement::kRequired:
- request->resident_key_required = true;
- break;
- case ResidentKeyRequirement::kPreferred: {
- // Create a resident key if the authenticator supports it, has sufficient
- // storage space for another credential, and we can obtain UV via client
- // PIN or an internal modality.
- request->resident_key_required =
- #if BUILDFLAG(IS_WIN)
- // Windows does not yet support rk=preferred.
- authenticator->GetType() != FidoAuthenticator::Type::kWinNative &&
- #endif
- auth_options_empty_on_win &&
- auth_options_empty_on_win->supports_resident_key &&
- !authenticator->DiscoverableCredentialStorageFull() &&
- (observer()->SupportsPIN() ||
- auth_options_empty_on_win->user_verification_availability ==
- AuthenticatorSupportedOptions::UserVerificationAvailability::
- kSupportedAndConfigured);
- break;
- }
- case ResidentKeyRequirement::kDiscouraged:
- request->resident_key_required = false;
- break;
- }
- switch (options_.large_blob_support) {
- case LargeBlobSupport::kRequired:
- request->large_blob_key = true;
- break;
- case LargeBlobSupport::kPreferred:
- request->large_blob_key =
- auth_options_empty_on_win &&
- auth_options_empty_on_win->supports_large_blobs &&
- request->resident_key_required;
- break;
- case LargeBlobSupport::kNotRequested:
- request->large_blob_key = false;
- break;
- }
- if (!options_.make_u2f_api_credential &&
- (request->resident_key_required ||
- (auth_options_empty_on_win && auth_options_empty_on_win->always_uv))) {
- request->user_verification = UserVerificationRequirement::kRequired;
- } else {
- request->user_verification = options_.user_verification;
- }
- if (options_.cred_protect_request &&
- authenticator->SupportsCredProtectExtension()) {
- request->cred_protect = CredProtectForAuthenticator(
- options_.cred_protect_request->first, *authenticator);
- request->cred_protect_enforce = options_.cred_protect_request->second;
- }
- if (request->hmac_secret && !authenticator->SupportsHMACSecretExtension()) {
- request->hmac_secret = false;
- }
- if (request->large_blob_key && auth_options_empty_on_win &&
- !auth_options_empty_on_win->supports_large_blobs) {
- request->large_blob_key = false;
- }
- if (request->min_pin_length_requested && auth_options_empty_on_win &&
- !auth_options_empty_on_win->supports_min_pin_length_extension) {
- request->min_pin_length_requested = false;
- }
- if (!authenticator->SupportsEnterpriseAttestation()) {
- switch (request->attestation_preference) {
- case AttestationConveyancePreference::kEnterpriseApprovedByBrowser:
- // If enterprise attestation is approved by policy then downgrade to
- // "direct" if not supported. Otherwise we have the strange behaviour
- // that kEnterpriseApprovedByBrowser turns into "none" on Windows
- // without EP support, or macOS/Chrome OS platform authenticators, but
- // "direct" elsewhere.
- request->attestation_preference =
- AttestationConveyancePreference::kDirect;
- break;
- case AttestationConveyancePreference::
- kEnterpriseIfRPListedOnAuthenticator:
- request->attestation_preference =
- AttestationConveyancePreference::kNone;
- break;
- default:
- break;
- }
- }
- if (request->cred_blob &&
- !authenticator->SupportsCredBlobOfSize(request->cred_blob->size())) {
- request->cred_blob.reset();
- }
- }
- } // namespace device
|