// 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/fido_device_authenticator.h" #include #include #include #include "base/bind.h" #include "base/containers/contains.h" #include "base/feature_list.h" #include "base/logging.h" #include "base/threading/sequenced_task_runner_handle.h" #include "build/build_config.h" #include "build/chromeos_buildflags.h" #include "device/fido/appid_exclude_probe_task.h" #include "device/fido/authenticator_supported_options.h" #include "device/fido/credential_management.h" #include "device/fido/ctap_authenticator_selection_request.h" #include "device/fido/ctap_get_assertion_request.h" #include "device/fido/ctap_make_credential_request.h" #include "device/fido/features.h" #include "device/fido/fido_constants.h" #include "device/fido/fido_device.h" #include "device/fido/fido_parsing_utils.h" #include "device/fido/fido_types.h" #include "device/fido/get_assertion_task.h" #include "device/fido/large_blob.h" #include "device/fido/make_credential_task.h" #include "device/fido/pin.h" #include "device/fido/u2f_command_constructor.h" #include "device/fido/virtual_fido_device.h" namespace device { using ClientPinAvailability = AuthenticatorSupportedOptions::ClientPinAvailability; using UserVerificationAvailability = AuthenticatorSupportedOptions::UserVerificationAvailability; namespace { // Helper method for determining correct bio enrollment version. BioEnrollmentRequest::Version GetBioEnrollmentRequestVersion( const AuthenticatorSupportedOptions& options) { DCHECK(options.bio_enrollment_availability_preview != AuthenticatorSupportedOptions::BioEnrollmentAvailability:: kNotSupported || options.bio_enrollment_availability != AuthenticatorSupportedOptions::BioEnrollmentAvailability:: kNotSupported); return options.bio_enrollment_availability != AuthenticatorSupportedOptions::BioEnrollmentAvailability:: kNotSupported ? BioEnrollmentRequest::kDefault : BioEnrollmentRequest::kPreview; } CredentialManagementRequest::Version GetCredentialManagementRequestVersion( const AuthenticatorSupportedOptions& options) { DCHECK(options.supports_credential_management_preview || options.supports_credential_management); return options.supports_credential_management ? CredentialManagementRequest::kDefault : CredentialManagementRequest::kPreview; } } // namespace FidoDeviceAuthenticator::FidoDeviceAuthenticator( std::unique_ptr device) : device_(std::move(device)) {} FidoDeviceAuthenticator::~FidoDeviceAuthenticator() = default; void FidoDeviceAuthenticator::InitializeAuthenticator( base::OnceClosure callback) { base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce( &FidoDevice::DiscoverSupportedProtocolAndDeviceInfo, device()->GetWeakPtr(), base::BindOnce(&FidoDeviceAuthenticator::InitializeAuthenticatorDone, weak_factory_.GetWeakPtr(), std::move(callback)))); } void FidoDeviceAuthenticator::InitializeAuthenticatorDone( base::OnceClosure callback) { DCHECK(!options_); switch (device_->supported_protocol()) { case ProtocolVersion::kU2f: options_ = AuthenticatorSupportedOptions(); break; case ProtocolVersion::kCtap2: DCHECK(device_->device_info()) << "uninitialized device"; options_ = device_->device_info()->options; if (device_->device_info()->pin_protocols) { DCHECK(!device_->device_info()->pin_protocols->empty()); // Choose the highest supported version. chosen_pin_uv_auth_protocol_ = *(device_->device_info()->pin_protocols->end() - 1); } break; case ProtocolVersion::kUnknown: NOTREACHED() << "uninitialized device"; options_ = AuthenticatorSupportedOptions(); } std::move(callback).Run(); } void FidoDeviceAuthenticator::ExcludeAppIdCredentialsBeforeMakeCredential( CtapMakeCredentialRequest request, MakeCredentialOptions options, base::OnceCallback)> callback) { // If the device (or request) is U2F-only then |MakeCredential| will handle // the AppID-excluded credentials, if any. There's no interaction with PUATs // to worry about because U2F doesn't have them. // // (If the device is AlwaysUV then it should still support up=false requests // without a PUAT, so they aren't excluded here.) if (!MakeCredentialTask::WillUseCTAP2(device_.get(), request, options) || device_->NoSilentRequests()) { std::move(callback).Run(CtapDeviceResponseCode::kSuccess, absl::nullopt); return; } // This is a CTAP2 device. In CTAP 2.1, a PUAT is invalidated if a request is // made with a different RP ID, even if the PUAT isn't used on that request. // Therefore appidExclude probing has to happen before the PUAT is obtained. // For CTAP 2.0 devices we follow the same pattern, even though a PIN token // doesn't have that issue. RunTask(std::move(request), std::move(options), std::move(callback)); } void FidoDeviceAuthenticator::MakeCredential( CtapMakeCredentialRequest request, MakeCredentialOptions request_options, MakeCredentialCallback callback) { // If the authenticator has UV configured then UV will be required in // order to create a credential (as specified by CTAP 2.0), even if // user-verification is "discouraged". However, if the request is U2F-only // then that doesn't apply and UV must be set to discouraged so that the // request can be translated to U2F. if (!request.pin_auth && options_->user_verification_availability == UserVerificationAvailability::kSupportedAndConfigured && !options_->make_cred_uv_not_required && !request_options.make_u2f_api_credential) { request.user_verification = UserVerificationRequirement::kRequired; } else { request.user_verification = UserVerificationRequirement::kDiscouraged; } RunTask( std::move(request), std::move(request_options), std::move(callback)); } void FidoDeviceAuthenticator::GetAssertion(CtapGetAssertionRequest request, CtapGetAssertionOptions options, GetAssertionCallback callback) { if (!options.prf_inputs.empty()) { GetEphemeralKey(base::BindOnce( &FidoDeviceAuthenticator::OnHaveEphemeralKeyForGetAssertion, weak_factory_.GetWeakPtr(), std::move(request), std::move(options), std::move(callback))); return; } DoGetAssertion(std::move(request), std::move(options), std::move(callback)); } void FidoDeviceAuthenticator::OnHaveEphemeralKeyForGetAssertion( CtapGetAssertionRequest request, CtapGetAssertionOptions options, GetAssertionCallback callback, CtapDeviceResponseCode status, absl::optional key) { if (status != CtapDeviceResponseCode::kSuccess) { std::move(callback).Run(status, absl::nullopt); return; } options.pin_key_agreement = std::move(*key); DoGetAssertion(std::move(request), std::move(options), std::move(callback)); } void FidoDeviceAuthenticator::DoGetAssertion(CtapGetAssertionRequest request, CtapGetAssertionOptions options, GetAssertionCallback callback) { if (!request.pin_auth && options_->user_verification_availability == UserVerificationAvailability::kSupportedAndConfigured && request.user_verification != UserVerificationRequirement::kDiscouraged) { request.user_verification = UserVerificationRequirement::kRequired; } else { request.user_verification = UserVerificationRequirement::kDiscouraged; } RunTask( std::move(request), std::move(options), std::move(callback)); } void FidoDeviceAuthenticator::GetNextAssertion(GetAssertionCallback callback) { RunOperation( CtapGetNextAssertionRequest(), std::move(callback), base::BindOnce(&ReadCTAPGetAssertionResponse, device_->DeviceTransport()), GetAssertionTask::StringFixupPredicate); } void FidoDeviceAuthenticator::GetTouch(base::OnceClosure callback) { if (device()->device_info() && device()->device_info()->SupportsAtLeast(Ctap2Version::kCtap2_1)) { RunOperation( CtapAuthenticatorSelectionRequest(), base::BindOnce( [](std::string authenticator_id, base::OnceClosure callback, CtapDeviceResponseCode status, absl::optional _) { if (status == CtapDeviceResponseCode::kSuccess) { std::move(callback).Run(); return; } FIDO_LOG(DEBUG) << "Ignoring status " << static_cast(status) << " from " << authenticator_id; }, GetId(), std::move(callback)), base::BindOnce(&pin::EmptyResponse::Parse)); return; } MakeCredential( MakeCredentialTask::GetTouchRequest(device()), MakeCredentialOptions(), base::BindOnce( [](std::string authenticator_id, base::OnceCallback callback, CtapDeviceResponseCode status, absl::optional) { // If the device didn't understand/process the request it may // fail immediately. Rather than count that as a touch, ignore // those cases completely. if (status == CtapDeviceResponseCode::kSuccess || status == CtapDeviceResponseCode::kCtap2ErrPinNotSet || status == CtapDeviceResponseCode::kCtap2ErrPinInvalid || status == CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid) { std::move(callback).Run(); return; } FIDO_LOG(DEBUG) << "Ignoring status " << static_cast(status) << " from " << authenticator_id; }, GetId(), std::move(callback))); } void FidoDeviceAuthenticator::GetPinRetries(GetRetriesCallback callback) { DCHECK(Options()); DCHECK(Options()->client_pin_availability != ClientPinAvailability::kNotSupported); DCHECK(chosen_pin_uv_auth_protocol_); RunOperation( pin::PinRetriesRequest{*chosen_pin_uv_auth_protocol_}, std::move(callback), base::BindOnce(&pin::RetriesResponse::ParsePinRetries)); } void FidoDeviceAuthenticator::GetEphemeralKey( GetEphemeralKeyCallback callback) { DCHECK(Options()); DCHECK(Options()->client_pin_availability != ClientPinAvailability::kNotSupported || Options()->supports_pin_uv_auth_token || SupportsHMACSecretExtension()); DCHECK(chosen_pin_uv_auth_protocol_); RunOperation( pin::KeyAgreementRequest{*chosen_pin_uv_auth_protocol_}, std::move(callback), base::BindOnce(&pin::KeyAgreementResponse::Parse)); } void FidoDeviceAuthenticator::GetPINToken( std::string pin, std::vector permissions, absl::optional rp_id, GetTokenCallback callback) { DCHECK(Options()); DCHECK(Options()->client_pin_availability != ClientPinAvailability::kNotSupported); DCHECK_NE(permissions.size(), 0u); DCHECK(!((base::Contains(permissions, pin::Permissions::kMakeCredential)) || base::Contains(permissions, pin::Permissions::kGetAssertion)) || rp_id); GetEphemeralKey(base::BindOnce( &FidoDeviceAuthenticator::OnHaveEphemeralKeyForGetPINToken, weak_factory_.GetWeakPtr(), std::move(pin), std::move(permissions), std::move(rp_id), std::move(callback))); } void FidoDeviceAuthenticator::OnHaveEphemeralKeyForGetPINToken( std::string pin, std::vector permissions, absl::optional rp_id, GetTokenCallback callback, CtapDeviceResponseCode status, absl::optional key) { if (status != CtapDeviceResponseCode::kSuccess) { std::move(callback).Run(status, absl::nullopt); return; } if (Options()->supports_pin_uv_auth_token) { pin::PinTokenWithPermissionsRequest request(*chosen_pin_uv_auth_protocol_, pin, *key, permissions, rp_id); std::vector shared_key = request.shared_key(); RunOperation( std::move(request), std::move(callback), base::BindOnce(&pin::TokenResponse::Parse, *chosen_pin_uv_auth_protocol_, std::move(shared_key))); return; } pin::PinTokenRequest request(*chosen_pin_uv_auth_protocol_, pin, *key); std::vector shared_key = request.shared_key(); RunOperation( std::move(request), std::move(callback), base::BindOnce(&pin::TokenResponse::Parse, *chosen_pin_uv_auth_protocol_, std::move(shared_key))); } void FidoDeviceAuthenticator::SetPIN(const std::string& pin, SetPINCallback callback) { DCHECK(Options()); DCHECK(Options()->client_pin_availability != ClientPinAvailability::kNotSupported); GetEphemeralKey(base::BindOnce( &FidoDeviceAuthenticator::OnHaveEphemeralKeyForSetPIN, weak_factory_.GetWeakPtr(), std::move(pin), std::move(callback))); } void FidoDeviceAuthenticator::OnHaveEphemeralKeyForSetPIN( std::string pin, SetPINCallback callback, CtapDeviceResponseCode status, absl::optional key) { if (status != CtapDeviceResponseCode::kSuccess) { std::move(callback).Run(status, absl::nullopt); return; } RunOperation( pin::SetRequest(*chosen_pin_uv_auth_protocol_, pin, *key), std::move(callback), base::BindOnce(&pin::EmptyResponse::Parse)); } void FidoDeviceAuthenticator::ChangePIN(const std::string& old_pin, const std::string& new_pin, SetPINCallback callback) { DCHECK(Options()); DCHECK(Options()->client_pin_availability != ClientPinAvailability::kNotSupported); GetEphemeralKey( base::BindOnce(&FidoDeviceAuthenticator::OnHaveEphemeralKeyForChangePIN, weak_factory_.GetWeakPtr(), std::move(old_pin), std::move(new_pin), std::move(callback))); } void FidoDeviceAuthenticator::OnHaveEphemeralKeyForChangePIN( std::string old_pin, std::string new_pin, SetPINCallback callback, CtapDeviceResponseCode status, absl::optional key) { if (status != CtapDeviceResponseCode::kSuccess) { std::move(callback).Run(status, absl::nullopt); return; } RunOperation( pin::ChangeRequest(*chosen_pin_uv_auth_protocol_, old_pin, new_pin, *key), std::move(callback), base::BindOnce(&pin::EmptyResponse::Parse)); } FidoAuthenticator::PINUVDisposition FidoDeviceAuthenticator::PINUVDispositionForMakeCredential( const CtapMakeCredentialRequest& request, const FidoRequestHandlerBase::Observer* observer) { DCHECK(device_->SupportedProtocolIsInitialized()); DCHECK(options_); const bool can_collect_pin = observer && observer->SupportsPIN(); const bool pin_supported = Options()->client_pin_availability != ClientPinAvailability::kNotSupported; const bool pin_configured = Options()->client_pin_availability == ClientPinAvailability::kSupportedAndPinSet; const bool uv_configured = Options()->user_verification_availability == UserVerificationAvailability::kSupportedAndConfigured; // CTAP 2.0 requires a PIN for credential creation once a PIN has been set. // Thus, if fallback to U2F isn't possible, a PIN will be needed if set. const bool u2f_fallback_possible = device()->device_info() && device()->device_info()->versions.contains(ProtocolVersion::kU2f) && IsConvertibleToU2fRegisterCommand(request) && !ShouldPreferCTAP2EvenIfItNeedsAPIN(request); // CTAP 2.1 authenticators on the other hand can indicate that they allow // credential creation with PIN or UV. const bool can_make_ctap2_credential_without_uv = request.user_verification == UserVerificationRequirement::kDiscouraged && options_->make_cred_uv_not_required; const UserVerificationRequirement uv_requirement = (pin_configured && !u2f_fallback_possible && !can_make_ctap2_credential_without_uv) ? UserVerificationRequirement::kRequired : request.user_verification; if (uv_requirement == UserVerificationRequirement::kDiscouraged || (uv_requirement == UserVerificationRequirement::kPreferred && ((!pin_configured || !can_collect_pin) && !uv_configured))) { return PINUVDisposition::kNoUV; } // Authenticators with built-in UV that don't support UV token should try // sending the request as-is with uv=true first. if (uv_configured && !CanGetUvToken()) { return (can_collect_pin && pin_supported) ? PINUVDisposition::kNoTokenInternalUVPINFallback : PINUVDisposition::kNoTokenInternalUV; } const bool can_get_token = (can_collect_pin && pin_supported) || CanGetUvToken(); if (can_get_token) { return PINUVDisposition::kGetToken; } return PINUVDisposition::kUnsatisfiable; } FidoAuthenticator::PINUVDisposition FidoDeviceAuthenticator::PINUVDispositionForGetAssertion( const CtapGetAssertionRequest& request, const FidoRequestHandlerBase::Observer* observer) { // TODO(crbug.com/1149405): GetAssertion requests don't allow in-line UV // enrollment. Perhaps we should change this and align with MakeCredential // behavior. const bool can_collect_pin = observer && observer->SupportsPIN(); const bool pin_configured = Options()->client_pin_availability == ClientPinAvailability::kSupportedAndPinSet; const bool uv_configured = Options()->user_verification_availability == UserVerificationAvailability::kSupportedAndConfigured; const UserVerificationRequirement uv_requirement = request.allow_list.empty() ? UserVerificationRequirement::kRequired : request.user_verification; if (uv_requirement == UserVerificationRequirement::kDiscouraged || (uv_requirement == UserVerificationRequirement::kPreferred && ((!pin_configured || !can_collect_pin) && !uv_configured))) { return PINUVDisposition::kNoUV; } // Authenticators with built-in UV that don't support UV token should try // sending the request as-is with uv=true first. if (uv_configured && !CanGetUvToken()) { return (can_collect_pin && pin_configured) ? PINUVDisposition::kNoTokenInternalUVPINFallback : PINUVDisposition::kNoTokenInternalUV; } if ((can_collect_pin && pin_configured) || CanGetUvToken()) { return PINUVDisposition::kGetToken; } return PINUVDisposition::kUnsatisfiable; } void FidoDeviceAuthenticator::GetCredentialsMetadata( const pin::TokenResponse& pin_token, GetCredentialsMetadataCallback callback) { DCHECK(Options()->supports_credential_management || Options()->supports_credential_management_preview); DCHECK(chosen_pin_uv_auth_protocol_ == pin_token.protocol()); RunOperation( CredentialManagementRequest::ForGetCredsMetadata( GetCredentialManagementRequestVersion(*Options()), pin_token), std::move(callback), base::BindOnce(&CredentialsMetadataResponse::Parse)); } struct FidoDeviceAuthenticator::EnumerateCredentialsState { explicit EnumerateCredentialsState(pin::TokenResponse pin_token_) : pin_token(pin_token_) {} EnumerateCredentialsState(EnumerateCredentialsState&&) = default; EnumerateCredentialsState& operator=(EnumerateCredentialsState&&) = default; pin::TokenResponse pin_token; size_t rp_count = 0; size_t current_rp = 0; size_t current_rp_credential_count = 0; FidoDeviceAuthenticator::EnumerateCredentialsCallback callback; std::vector responses; std::vector> rp_id_hashes; }; void FidoDeviceAuthenticator::EnumerateCredentials( const pin::TokenResponse& pin_token, EnumerateCredentialsCallback callback) { DCHECK(Options()->supports_credential_management || Options()->supports_credential_management_preview); DCHECK(chosen_pin_uv_auth_protocol_ == pin_token.protocol()); EnumerateCredentialsState state(pin_token); state.callback = std::move(callback); RunOperation( CredentialManagementRequest::ForEnumerateRPsBegin( GetCredentialManagementRequestVersion(*Options()), pin_token), base::BindOnce(&FidoDeviceAuthenticator::OnEnumerateRPsDone, weak_factory_.GetWeakPtr(), std::move(state)), base::BindOnce(&EnumerateRPsResponse::Parse, /*expect_rp_count=*/true), &EnumerateRPsResponse::StringFixupPredicate); } // TaskClearProxy interposes |callback| and resets |task_| before it runs. template void FidoDeviceAuthenticator::TaskClearProxy( base::OnceCallback callback, Args... args) { DCHECK(task_); DCHECK(!operation_); task_.reset(); std::move(callback).Run(std::forward(args)...); } // OperationClearProxy interposes |callback| and resets |operation_| before it // runs. template void FidoDeviceAuthenticator::OperationClearProxy( base::OnceCallback callback, Args... args) { DCHECK(operation_); DCHECK(!task_); operation_.reset(); std::move(callback).Run(std::forward(args)...); } // RunTask starts a |FidoTask| and ensures that |task_| is reset when the given // callback is called. template void FidoDeviceAuthenticator::RunTask( RequestArgs&&... request_args, base::OnceCallback)> callback) { DCHECK(!task_); DCHECK(!operation_); DCHECK(device_->SupportedProtocolIsInitialized()) << "InitializeAuthenticator() must be called first."; task_ = std::make_unique( device_.get(), std::forward(request_args)..., base::BindOnce( &FidoDeviceAuthenticator::TaskClearProxy>, weak_factory_.GetWeakPtr(), std::move(callback))); } // RunOperation starts a |Ctap2DeviceOperation| and ensures that |operation_| is // reset when the given completion callback is called. template void FidoDeviceAuthenticator::RunOperation( Request request, base::OnceCallback)> callback, base::OnceCallback< absl::optional(const absl::optional&)> parser, bool (*string_fixup_predicate)(const std::vector&)) { DCHECK(!task_); DCHECK(!operation_); DCHECK(device_->SupportedProtocolIsInitialized()) << "InitializeAuthenticator() must be called first."; operation_ = std::make_unique>( device_.get(), std::move(request), base::BindOnce(&FidoDeviceAuthenticator::OperationClearProxy< CtapDeviceResponseCode, absl::optional>, weak_factory_.GetWeakPtr(), std::move(callback)), std::move(parser), string_fixup_predicate); operation_->Start(); } void FidoDeviceAuthenticator::OnEnumerateRPsDone( EnumerateCredentialsState state, CtapDeviceResponseCode status, absl::optional response) { DCHECK_EQ(state.rp_id_hashes.size(), state.responses.size()); DCHECK_LE(state.rp_id_hashes.size(), state.rp_count); if (status != CtapDeviceResponseCode::kSuccess) { std::move(state.callback).Run(status, absl::nullopt); return; } if (state.rp_count == 0) { if (response->rp_count == 0) { std::move(state.callback).Run(status, std::move(state.responses)); return; } state.rp_count = response->rp_count; } DCHECK(response->rp); DCHECK(response->rp_id_hash); state.rp_id_hashes.push_back(*response->rp_id_hash); state.responses.emplace_back(*response->rp); if (state.rp_id_hashes.size() < state.rp_count) { // Get the next RP. RunOperation( CredentialManagementRequest::ForEnumerateRPsGetNext( GetCredentialManagementRequestVersion(*Options())), base::BindOnce(&FidoDeviceAuthenticator::OnEnumerateRPsDone, weak_factory_.GetWeakPtr(), std::move(state)), base::BindOnce(&EnumerateRPsResponse::Parse, /*expect_rp_count=*/false), &EnumerateRPsResponse::StringFixupPredicate); return; } auto request = CredentialManagementRequest::ForEnumerateCredentialsBegin( GetCredentialManagementRequestVersion(*Options()), state.pin_token, state.rp_id_hashes.front()); RunOperation( std::move(request), base::BindOnce(&FidoDeviceAuthenticator::OnEnumerateCredentialsDone, weak_factory_.GetWeakPtr(), std::move(state)), base::BindOnce(&EnumerateCredentialsResponse::Parse, /*expect_credential_count=*/true), &EnumerateCredentialsResponse::StringFixupPredicate); } void FidoDeviceAuthenticator::OnEnumerateCredentialsDone( EnumerateCredentialsState state, CtapDeviceResponseCode status, absl::optional response) { DCHECK_EQ(state.rp_id_hashes.size(), state.responses.size()); DCHECK_EQ(state.rp_id_hashes.size(), state.rp_count); DCHECK_LT(state.current_rp, state.rp_count); if (status != CtapDeviceResponseCode::kSuccess) { std::move(state.callback).Run(status, absl::nullopt); return; } if (state.current_rp_credential_count == 0) { // First credential for this RP. DCHECK_GT(response->credential_count, 0u); state.current_rp_credential_count = response->credential_count; } AggregatedEnumerateCredentialsResponse& current_aggregated_response = state.responses.at(state.current_rp); current_aggregated_response.credentials.push_back(std::move(*response)); if (current_aggregated_response.credentials.size() < state.current_rp_credential_count) { // Fetch the next credential for this RP. RunOperation( CredentialManagementRequest::ForEnumerateCredentialsGetNext( GetCredentialManagementRequestVersion(*Options())), base::BindOnce(&FidoDeviceAuthenticator::OnEnumerateCredentialsDone, weak_factory_.GetWeakPtr(), std::move(state)), base::BindOnce(&EnumerateCredentialsResponse::Parse, /*expect_credential_count=*/false), &EnumerateCredentialsResponse::StringFixupPredicate); return; } if (++state.current_rp < state.rp_count) { // Enumerate credentials for the next RP. state.current_rp_credential_count = 0; auto request = CredentialManagementRequest::ForEnumerateCredentialsBegin( GetCredentialManagementRequestVersion(*Options()), state.pin_token, state.rp_id_hashes.at(state.current_rp)); RunOperation( std::move(request), base::BindOnce(&FidoDeviceAuthenticator::OnEnumerateCredentialsDone, weak_factory_.GetWeakPtr(), std::move(state)), base::BindOnce(&EnumerateCredentialsResponse::Parse, /*expect_credential_count=*/true), &EnumerateCredentialsResponse::StringFixupPredicate); return; } std::move(state.callback) .Run(CtapDeviceResponseCode::kSuccess, std::move(state.responses)); return; } void FidoDeviceAuthenticator::DeleteCredential( const pin::TokenResponse& pin_token, const PublicKeyCredentialDescriptor& credential_id, DeleteCredentialCallback callback) { DCHECK(Options()->supports_credential_management || Options()->supports_credential_management_preview); DCHECK(chosen_pin_uv_auth_protocol_ == pin_token.protocol()); RunOperation( CredentialManagementRequest::ForDeleteCredential( GetCredentialManagementRequestVersion(*Options()), pin_token, credential_id), std::move(callback), base::BindOnce(&DeleteCredentialResponse::Parse), /*string_fixup_predicate=*/nullptr); } bool FidoDeviceAuthenticator::SupportsUpdateUserInformation() const { return device_->device_info() && device_->device_info()->SupportsAtLeast(Ctap2Version::kCtap2_1); } void FidoDeviceAuthenticator::UpdateUserInformation( const pin::TokenResponse& pin_token, const PublicKeyCredentialDescriptor& credential_id, const PublicKeyCredentialUserEntity& updated_user, UpdateUserInformationCallback callback) { DCHECK(Options()->supports_credential_management || Options()->supports_credential_management_preview); DCHECK(chosen_pin_uv_auth_protocol_ == pin_token.protocol()); RunOperation( CredentialManagementRequest::ForUpdateUserInformation( GetCredentialManagementRequestVersion(*Options()), pin_token, credential_id, updated_user), std::move(callback), base::BindOnce(&UpdateUserInformationResponse::Parse), /*string_fixup_predicate=*/nullptr); } void FidoDeviceAuthenticator::GetModality(BioEnrollmentCallback callback) { RunOperation( BioEnrollmentRequest::ForGetModality( GetBioEnrollmentRequestVersion(*Options())), std::move(callback), base::BindOnce(&BioEnrollmentResponse::Parse)); } void FidoDeviceAuthenticator::GetSensorInfo(BioEnrollmentCallback callback) { RunOperation( BioEnrollmentRequest::ForGetSensorInfo( GetBioEnrollmentRequestVersion(*Options())), std::move(callback), base::BindOnce(&BioEnrollmentResponse::Parse)); } void FidoDeviceAuthenticator::BioEnrollFingerprint( const pin::TokenResponse& pin_token, absl::optional> template_id, BioEnrollmentCallback callback) { DCHECK(chosen_pin_uv_auth_protocol_ == pin_token.protocol()); RunOperation( template_id ? BioEnrollmentRequest::ForEnrollNextSample( GetBioEnrollmentRequestVersion(*Options()), std::move(pin_token), std::move(*template_id)) : BioEnrollmentRequest::ForEnrollBegin( GetBioEnrollmentRequestVersion(*Options()), std::move(pin_token)), std::move(callback), base::BindOnce(&BioEnrollmentResponse::Parse)); } void FidoDeviceAuthenticator::BioEnrollRename( const pin::TokenResponse& pin_token, std::vector id, std::string name, BioEnrollmentCallback callback) { DCHECK(chosen_pin_uv_auth_protocol_ == pin_token.protocol()); RunOperation( BioEnrollmentRequest::ForRename( GetBioEnrollmentRequestVersion(*Options()), pin_token, std::move(id), std::move(name)), std::move(callback), base::BindOnce(&BioEnrollmentResponse::Parse)); } void FidoDeviceAuthenticator::BioEnrollDelete( const pin::TokenResponse& pin_token, std::vector template_id, BioEnrollmentCallback callback) { DCHECK(chosen_pin_uv_auth_protocol_ == pin_token.protocol()); RunOperation( BioEnrollmentRequest::ForDelete( GetBioEnrollmentRequestVersion(*Options()), pin_token, std::move(template_id)), std::move(callback), base::BindOnce(&BioEnrollmentResponse::Parse)); } void FidoDeviceAuthenticator::BioEnrollCancel(BioEnrollmentCallback callback) { RunOperation( BioEnrollmentRequest::ForCancel( GetBioEnrollmentRequestVersion(*Options())), std::move(callback), base::BindOnce(&BioEnrollmentResponse::Parse)); } void FidoDeviceAuthenticator::BioEnrollEnumerate( const pin::TokenResponse& pin_token, BioEnrollmentCallback callback) { DCHECK(chosen_pin_uv_auth_protocol_ == pin_token.protocol()); RunOperation( BioEnrollmentRequest::ForEnumerate( GetBioEnrollmentRequestVersion(*Options()), std::move(pin_token)), std::move(callback), base::BindOnce(&BioEnrollmentResponse::Parse)); } void FidoDeviceAuthenticator::WriteLargeBlob( LargeBlob large_blob, const LargeBlobKey& large_blob_key, const absl::optional pin_uv_auth_token, base::OnceCallback callback) { auto pin_uv_auth_token_copy = pin_uv_auth_token; FetchLargeBlobArray( pin_uv_auth_token_copy, LargeBlobArrayReader(), base::BindOnce(&FidoDeviceAuthenticator::OnHaveLargeBlobArrayForWrite, weak_factory_.GetWeakPtr(), large_blob, large_blob_key, std::move(pin_uv_auth_token), std::move(callback))); } void FidoDeviceAuthenticator::ReadLargeBlob( const std::vector& large_blob_keys, const absl::optional pin_uv_auth_token, LargeBlobReadCallback callback) { DCHECK(!large_blob_keys.empty()); FetchLargeBlobArray( std::move(pin_uv_auth_token), LargeBlobArrayReader(), base::BindOnce(&FidoDeviceAuthenticator::OnHaveLargeBlobArrayForRead, weak_factory_.GetWeakPtr(), large_blob_keys, std::move(callback))); } void FidoDeviceAuthenticator::FetchLargeBlobArray( const absl::optional pin_uv_auth_token, LargeBlobArrayReader large_blob_array_reader, base::OnceCallback)> callback) { size_t bytes_to_read = max_large_blob_fragment_length(); LargeBlobsRequest request = LargeBlobsRequest::ForRead(bytes_to_read, large_blob_array_reader.size()); RunOperation( std::move(request), base::BindOnce(&FidoDeviceAuthenticator::OnReadLargeBlobFragment, weak_factory_.GetWeakPtr(), bytes_to_read, std::move(large_blob_array_reader), std::move(pin_uv_auth_token), std::move(callback)), base::BindOnce(&LargeBlobsResponse::ParseForRead, bytes_to_read)); } void FidoDeviceAuthenticator::OnReadLargeBlobFragment( const size_t bytes_requested, LargeBlobArrayReader large_blob_array_reader, const absl::optional pin_uv_auth_token, base::OnceCallback)> callback, CtapDeviceResponseCode status, absl::optional response) { if (status != CtapDeviceResponseCode::kSuccess) { std::move(callback).Run(status, absl::nullopt); return; } DCHECK(response && response->config()); large_blob_array_reader.Append(*response->config()); if (response->config()->size() == bytes_requested) { // More data may be available, read the next fragment. FetchLargeBlobArray(std::move(pin_uv_auth_token), std::move(large_blob_array_reader), std::move(callback)); return; } std::move(callback).Run(CtapDeviceResponseCode::kSuccess, std::move(large_blob_array_reader)); } void FidoDeviceAuthenticator::OnHaveLargeBlobArrayForWrite( LargeBlob large_blob, const LargeBlobKey& large_blob_key, const absl::optional pin_uv_auth_token, base::OnceCallback callback, CtapDeviceResponseCode status, absl::optional large_blob_array_reader) { if (status != CtapDeviceResponseCode::kSuccess) { std::move(callback).Run(status); return; } absl::optional> large_blob_array = large_blob_array_reader->Materialize(); if (!large_blob_array) { // The large blob array is corrupted. Replace it completely with a new one. // TODO(nsatragno): but maybe we want to do something else like trying // again? It might have been corrupted while transported. Decide when we // have hardware to test. large_blob_array.emplace(); return; } auto existing_large_blob = std::find_if(large_blob_array->begin(), large_blob_array->end(), [&large_blob_key](const LargeBlobData& blob) { return blob.Decrypt(large_blob_key); }); LargeBlobData new_large_blob_data(large_blob_key, std::move(large_blob)); if (existing_large_blob != large_blob_array->end()) { *existing_large_blob = std::move(new_large_blob_data); } else { large_blob_array->emplace_back(std::move(new_large_blob_data)); } LargeBlobArrayWriter writer(*large_blob_array); if (writer.size() > *device_->device_info()->max_serialized_large_blob_array) { std::move(callback).Run(CtapDeviceResponseCode::kCtap2ErrRequestTooLarge); return; } WriteLargeBlobArray(std::move(pin_uv_auth_token), std::move(writer), std::move(callback)); } void FidoDeviceAuthenticator::WriteLargeBlobArray( const absl::optional pin_uv_auth_token, LargeBlobArrayWriter large_blob_array_writer, base::OnceCallback callback) { LargeBlobArrayFragment fragment = large_blob_array_writer.Pop(max_large_blob_fragment_length()); LargeBlobsRequest request = LargeBlobsRequest::ForWrite( std::move(fragment), large_blob_array_writer.size()); if (pin_uv_auth_token) { DCHECK(chosen_pin_uv_auth_protocol_ == pin_uv_auth_token->protocol()); request.SetPinParam(*pin_uv_auth_token); } RunOperation( std::move(request), base::BindOnce(&FidoDeviceAuthenticator::OnWriteLargeBlobFragment, weak_factory_.GetWeakPtr(), std::move(large_blob_array_writer), std::move(pin_uv_auth_token), std::move(callback)), base::BindOnce(&LargeBlobsResponse::ParseForWrite)); } void FidoDeviceAuthenticator::OnWriteLargeBlobFragment( LargeBlobArrayWriter large_blob_array_writer, const absl::optional pin_uv_auth_token, base::OnceCallback callback, CtapDeviceResponseCode status, absl::optional response) { if (status != CtapDeviceResponseCode::kSuccess) { std::move(callback).Run(status); return; } if (large_blob_array_writer.has_remaining_fragments()) { WriteLargeBlobArray(std::move(pin_uv_auth_token), std::move(large_blob_array_writer), std::move(callback)); return; } std::move(callback).Run(CtapDeviceResponseCode::kSuccess); } void FidoDeviceAuthenticator::OnHaveLargeBlobArrayForRead( const std::vector& large_blob_keys, LargeBlobReadCallback callback, CtapDeviceResponseCode status, absl::optional large_blob_array_reader) { if (status != CtapDeviceResponseCode::kSuccess) { std::move(callback).Run(status, absl::nullopt); return; } absl::optional> large_blob_array = large_blob_array_reader->Materialize(); if (!large_blob_array) { std::move(callback).Run(CtapDeviceResponseCode::kCtap2ErrIntegrityFailure, absl::nullopt); return; } std::vector> result; for (const LargeBlobData& blob : *large_blob_array) { for (const LargeBlobKey& key : large_blob_keys) { absl::optional plaintext = blob.Decrypt(key); if (plaintext) { result.emplace_back(std::make_pair(key, std::move(*plaintext))); break; } } } std::move(callback).Run(CtapDeviceResponseCode::kSuccess, std::move(result)); } absl::optional> FidoDeviceAuthenticator::GetAlgorithms() { if (device_->supported_protocol() == ProtocolVersion::kU2f) { static constexpr int32_t kU2fAlgorithms[1] = { static_cast(CoseAlgorithmIdentifier::kEs256)}; return kU2fAlgorithms; } const absl::optional& get_info_response = device_->device_info(); if (get_info_response) { return get_info_response->algorithms; } return absl::nullopt; } bool FidoDeviceAuthenticator::DiscoverableCredentialStorageFull() const { return device_->device_info()->remaining_discoverable_credentials == 0u; } void FidoDeviceAuthenticator::Reset(ResetCallback callback) { DCHECK(device_->SupportedProtocolIsInitialized()) << "InitializeAuthenticator() must be called first."; RunOperation( pin::ResetRequest(), std::move(callback), base::BindOnce(&pin::ResetResponse::Parse)); } void FidoDeviceAuthenticator::Cancel() { if (operation_) { operation_->Cancel(); } if (task_) { task_->Cancel(); } } std::string FidoDeviceAuthenticator::GetId() const { return device_->GetId(); } std::string FidoDeviceAuthenticator::GetDisplayName() const { return device_->GetDisplayName(); } ProtocolVersion FidoDeviceAuthenticator::SupportedProtocol() const { DCHECK(device_->SupportedProtocolIsInitialized()); return device_->supported_protocol(); } bool FidoDeviceAuthenticator::SupportsHMACSecretExtension() const { const absl::optional& get_info_response = device_->device_info(); return get_info_response && get_info_response->extensions && base::Contains(*get_info_response->extensions, kExtensionHmacSecret); } bool FidoDeviceAuthenticator::SupportsEnterpriseAttestation() const { DCHECK(device_->SupportedProtocolIsInitialized()); if (device_->supported_protocol() == ProtocolVersion::kU2f) { // U2F devices always "support" enterprise attestation because it turns into // a bit in the makeCredential command that is ignored if not supported. return true; } return options_ && options_->enterprise_attestation; } bool FidoDeviceAuthenticator::SupportsCredBlobOfSize(size_t num_bytes) const { const absl::optional& get_info_response = device_->device_info(); return get_info_response && get_info_response->max_cred_blob_length && num_bytes <= get_info_response->max_cred_blob_length.value(); } const absl::optional& FidoDeviceAuthenticator::Options() const { return options_; } absl::optional FidoDeviceAuthenticator::AuthenticatorTransport() const { return device_->DeviceTransport(); } bool FidoDeviceAuthenticator::IsInPairingMode() const { return device_->IsInPairingMode(); } bool FidoDeviceAuthenticator::IsPaired() const { return device_->IsPaired(); } bool FidoDeviceAuthenticator::RequiresBlePairingPin() const { return device_->RequiresBlePairingPin(); } void FidoDeviceAuthenticator::SetTaskForTesting( std::unique_ptr task) { task_ = std::move(task); } void FidoDeviceAuthenticator::GetUvRetries(GetRetriesCallback callback) { DCHECK(Options()); DCHECK(Options()->user_verification_availability != UserVerificationAvailability::kNotSupported); DCHECK(chosen_pin_uv_auth_protocol_); RunOperation( pin::UvRetriesRequest{*chosen_pin_uv_auth_protocol_}, std::move(callback), base::BindOnce(&pin::RetriesResponse::ParseUvRetries)); } bool FidoDeviceAuthenticator::CanGetUvToken() { return options_->user_verification_availability == AuthenticatorSupportedOptions::UserVerificationAvailability:: kSupportedAndConfigured && options_->supports_pin_uv_auth_token; } void FidoDeviceAuthenticator::GetUvToken( std::vector permissions, absl::optional rp_id, GetTokenCallback callback) { GetEphemeralKey( base::BindOnce(&FidoDeviceAuthenticator::OnHaveEphemeralKeyForUvToken, weak_factory_.GetWeakPtr(), std::move(rp_id), std::move(permissions), std::move(callback))); } uint32_t FidoDeviceAuthenticator::CurrentMinPINLength() { return ForcePINChange() ? kMinPinLength : NewMinPINLength(); } uint32_t FidoDeviceAuthenticator::NewMinPINLength() { return device()->device_info()->min_pin_length.value_or(kMinPinLength); } bool FidoDeviceAuthenticator::ForcePINChange() { return device()->device_info()->force_pin_change.value_or(false); } void FidoDeviceAuthenticator::OnHaveEphemeralKeyForUvToken( absl::optional rp_id, std::vector permissions, GetTokenCallback callback, CtapDeviceResponseCode status, absl::optional key) { if (status != CtapDeviceResponseCode::kSuccess) { std::move(callback).Run(status, absl::nullopt); return; } DCHECK(key); pin::UvTokenRequest request(*chosen_pin_uv_auth_protocol_, *key, std::move(rp_id), permissions); std::vector shared_key = request.shared_key(); RunOperation( std::move(request), std::move(callback), base::BindOnce(&pin::TokenResponse::Parse, *chosen_pin_uv_auth_protocol_, std::move(shared_key))); } size_t FidoDeviceAuthenticator::max_large_blob_fragment_length() { return device_->device_info()->max_msg_size ? *device_->device_info()->max_msg_size - kLargeBlobReadEncodingOverhead : kLargeBlobDefaultMaxFragmentLength; } base::WeakPtr FidoDeviceAuthenticator::GetWeakPtr() { return weak_factory_.GetWeakPtr(); } } // namespace device