| // 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/get_assertion_task.h" |
| |
| #include <algorithm> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "device/base/features.h" |
| #include "device/fido/authenticator_get_assertion_response.h" |
| #include "device/fido/ctap2_device_operation.h" |
| #include "device/fido/ctap_empty_authenticator_request.h" |
| #include "device/fido/device_response_converter.h" |
| #include "device/fido/u2f_command_constructor.h" |
| #include "device/fido/u2f_sign_operation.h" |
| |
| namespace device { |
| |
| namespace { |
| |
| bool ResponseContainsUserIdentifiableInfo( |
| const AuthenticatorGetAssertionResponse& response) { |
| const auto& user_entity = response.user_entity(); |
| if (!user_entity) |
| return false; |
| |
| return user_entity->user_display_name() || user_entity->user_name() || |
| user_entity->user_icon_url(); |
| } |
| |
| } // namespace |
| |
| GetAssertionTask::GetAssertionTask(FidoDevice* device, |
| CtapGetAssertionRequest request, |
| GetAssertionTaskCallback callback) |
| : FidoTask(device), |
| request_(std::move(request)), |
| callback_(std::move(callback)), |
| weak_factory_(this) {} |
| |
| GetAssertionTask::~GetAssertionTask() = default; |
| |
| void GetAssertionTask::StartTask() { |
| if (base::FeatureList::IsEnabled(kNewCtap2Device) && |
| device()->supported_protocol() == ProtocolVersion::kCtap) { |
| GetAssertion(); |
| } else { |
| U2fSign(); |
| } |
| } |
| |
| void GetAssertionTask::GetAssertion() { |
| sign_operation_ = |
| std::make_unique<Ctap2DeviceOperation<CtapGetAssertionRequest, |
| AuthenticatorGetAssertionResponse>>( |
| device(), request_, |
| base::BindOnce(&GetAssertionTask::OnCtapGetAssertionResponseReceived, |
| weak_factory_.GetWeakPtr()), |
| base::BindOnce(&ReadCTAPGetAssertionResponse)); |
| sign_operation_->Start(); |
| } |
| |
| void GetAssertionTask::U2fSign() { |
| DCHECK(!device()->device_info()); |
| DCHECK_EQ(ProtocolVersion::kU2f, device()->supported_protocol()); |
| |
| sign_operation_ = std::make_unique<U2fSignOperation>( |
| device(), request_, |
| base::BindOnce(&GetAssertionTask::OnCtapGetAssertionResponseReceived, |
| weak_factory_.GetWeakPtr())); |
| sign_operation_->Start(); |
| } |
| |
| bool GetAssertionTask::CheckRequirementsOnReturnedUserEntities( |
| const AuthenticatorGetAssertionResponse& response) { |
| // If assertion has been made without user verification, user identifiable |
| // information must not be included. |
| if (!response.auth_data().obtained_user_verification() && |
| ResponseContainsUserIdentifiableInfo(response)) { |
| return false; |
| } |
| |
| // For resident key credentials, user id of the user entity is mandatory. |
| if ((!request_.allow_list() || request_.allow_list()->empty()) && |
| !response.user_entity()) { |
| return false; |
| } |
| |
| // When multiple accounts exist for specified RP ID, user entity is mandatory. |
| if (response.num_credentials().value_or(0u) > 1 && !response.user_entity()) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool GetAssertionTask::CheckRequirementsOnReturnedCredentialId( |
| const AuthenticatorGetAssertionResponse& response) { |
| if (device()->device_info() && |
| device()->device_info()->options().supports_resident_key()) { |
| return true; |
| } |
| |
| const auto& allow_list = request_.allow_list(); |
| return allow_list && |
| (allow_list->size() == 1 || |
| std::any_of(allow_list->cbegin(), allow_list->cend(), |
| [&response](const auto& credential) { |
| return credential.id() == response.raw_credential_id(); |
| })); |
| } |
| |
| void GetAssertionTask::OnCtapGetAssertionResponseReceived( |
| CtapDeviceResponseCode response_code, |
| base::Optional<AuthenticatorGetAssertionResponse> device_response) { |
| if (response_code != CtapDeviceResponseCode::kSuccess) { |
| std::move(callback_).Run(response_code, base::nullopt); |
| return; |
| } |
| |
| // TODO(martinkr): CheckRpIdHash invocation needs to move into the Request |
| // handler. See https://crbug.com/863988. |
| if (!device_response || !device_response->CheckRpIdHash(request_.rp_id()) || |
| !CheckRequirementsOnReturnedCredentialId(*device_response) || |
| !CheckRequirementsOnReturnedUserEntities(*device_response)) { |
| std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther, |
| base::nullopt); |
| return; |
| } |
| |
| std::move(callback_).Run(response_code, std::move(device_response)); |
| } |
| |
| } // namespace device |