| // 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/u2f_register_operation.h" |
| |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/threading/sequenced_task_runner_handle.h" |
| #include "components/apdu/apdu_response.h" |
| #include "components/device_event_log/device_event_log.h" |
| #include "device/fido/authenticator_make_credential_response.h" |
| #include "device/fido/ctap_make_credential_request.h" |
| #include "device/fido/device_response_converter.h" |
| #include "device/fido/fido_constants.h" |
| #include "device/fido/fido_device.h" |
| #include "device/fido/fido_parsing_utils.h" |
| #include "device/fido/u2f_command_constructor.h" |
| |
| namespace device { |
| |
| U2fRegisterOperation::U2fRegisterOperation( |
| FidoDevice* device, |
| const CtapMakeCredentialRequest& request, |
| DeviceResponseCallback callback) |
| : DeviceOperation(device, request, std::move(callback)) {} |
| |
| U2fRegisterOperation::~U2fRegisterOperation() = default; |
| |
| void U2fRegisterOperation::Start() { |
| DCHECK(IsConvertibleToU2fRegisterCommand(request())); |
| |
| if (!request().exclude_list.empty()) { |
| // First try signing with the excluded credentials to see whether this |
| // device should be excluded. |
| WinkAndTrySign(); |
| } else { |
| WinkAndTryRegistration(); |
| } |
| } |
| |
| void U2fRegisterOperation::Cancel() { |
| canceled_ = true; |
| } |
| |
| void U2fRegisterOperation::WinkAndTrySign() { |
| device()->TryWink(base::BindOnce(&U2fRegisterOperation::TrySign, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void U2fRegisterOperation::TrySign() { |
| absl::optional<std::vector<uint8_t>> sign_command; |
| if (probing_alternative_rp_id_) { |
| CtapMakeCredentialRequest sign_request(request()); |
| sign_request.rp.id = *request().app_id_exclude; |
| sign_command = ConvertToU2fSignCommandWithBogusChallenge( |
| sign_request, excluded_key_handle()); |
| } else { |
| sign_command = ConvertToU2fSignCommandWithBogusChallenge( |
| request(), excluded_key_handle()); |
| } |
| |
| DispatchU2FCommand( |
| std::move(sign_command), |
| base::BindOnce(&U2fRegisterOperation::OnCheckForExcludedKeyHandle, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void U2fRegisterOperation::OnCheckForExcludedKeyHandle( |
| absl::optional<std::vector<uint8_t>> device_response) { |
| if (canceled_) { |
| return; |
| } |
| |
| auto result = apdu::ApduResponse::Status::SW_WRONG_DATA; |
| if (device_response) { |
| const auto apdu_response = |
| apdu::ApduResponse::CreateFromMessage(std::move(*device_response)); |
| if (apdu_response) { |
| result = apdu_response->status(); |
| } |
| } |
| |
| // Older U2F devices may respond with the length of the input as an error |
| // response if the length is unexpected. |
| if (result == |
| static_cast<apdu::ApduResponse::Status>(excluded_key_handle().size())) { |
| result = apdu::ApduResponse::Status::SW_WRONG_LENGTH; |
| } |
| |
| switch (result) { |
| case apdu::ApduResponse::Status::SW_NO_ERROR: |
| // Duplicate registration found. The device has already collected |
| // user-presence. |
| std::move(callback()) |
| .Run(CtapDeviceResponseCode::kCtap2ErrCredentialExcluded, |
| absl::nullopt); |
| break; |
| |
| case apdu::ApduResponse::Status::SW_CONDITIONS_NOT_SATISFIED: |
| // Duplicate registration found. Waiting for user touch. |
| base::SequencedTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&U2fRegisterOperation::WinkAndTrySign, |
| weak_factory_.GetWeakPtr()), |
| kU2fRetryDelay); |
| break; |
| |
| case apdu::ApduResponse::Status::SW_WRONG_DATA: |
| case apdu::ApduResponse::Status::SW_WRONG_LENGTH: |
| // Continue to iterate through the provided key handles in the exclude |
| // list to check for already registered keys. |
| current_key_handle_index_++; |
| if (current_key_handle_index_ == request().exclude_list.size() && |
| !probing_alternative_rp_id_ && request().app_id_exclude) { |
| // All elements of |request().exclude_list| have been tested, but |
| // there's a second AppID so they need to be tested again. |
| probing_alternative_rp_id_ = true; |
| current_key_handle_index_ = 0; |
| } |
| if (current_key_handle_index_ < request().exclude_list.size()) { |
| WinkAndTrySign(); |
| } else { |
| // Reached the end of exclude list with no duplicate credential. |
| // Proceed with registration. |
| WinkAndTryRegistration(); |
| } |
| break; |
| |
| default: |
| // Some sort of failure occurred. Silently drop device request. |
| FIDO_LOG(ERROR) << "Unexpected status " << static_cast<int>(result) |
| << " from U2F device"; |
| std::move(callback()) |
| .Run(CtapDeviceResponseCode::kCtap2ErrOther, absl::nullopt); |
| break; |
| } |
| } |
| |
| void U2fRegisterOperation::WinkAndTryRegistration() { |
| device()->TryWink(base::BindOnce(&U2fRegisterOperation::TryRegistration, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void U2fRegisterOperation::TryRegistration() { |
| DispatchU2FCommand( |
| ConvertToU2fRegisterCommand(request()), |
| base::BindOnce(&U2fRegisterOperation::OnRegisterResponseReceived, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void U2fRegisterOperation::OnRegisterResponseReceived( |
| absl::optional<std::vector<uint8_t>> device_response) { |
| if (canceled_) { |
| return; |
| } |
| |
| auto result = apdu::ApduResponse::Status::SW_WRONG_DATA; |
| const auto apdu_response = |
| device_response |
| ? apdu::ApduResponse::CreateFromMessage(std::move(*device_response)) |
| : absl::nullopt; |
| if (apdu_response) { |
| result = apdu_response->status(); |
| } |
| |
| switch (result) { |
| case apdu::ApduResponse::Status::SW_NO_ERROR: { |
| FIDO_LOG(DEBUG) |
| << "Received successful U2F register response from authenticator: " |
| << base::HexEncode(apdu_response->data().data(), |
| apdu_response->data().size()); |
| auto response = |
| AuthenticatorMakeCredentialResponse::CreateFromU2fRegisterResponse( |
| device()->DeviceTransport(), |
| fido_parsing_utils::CreateSHA256Hash(request().rp.id), |
| apdu_response->data()); |
| std::move(callback()) |
| .Run(CtapDeviceResponseCode::kSuccess, std::move(response)); |
| break; |
| } |
| |
| case apdu::ApduResponse::Status::SW_CONDITIONS_NOT_SATISFIED: |
| // Waiting for user touch, retry after delay. |
| base::SequencedTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&U2fRegisterOperation::WinkAndTryRegistration, |
| weak_factory_.GetWeakPtr()), |
| kU2fRetryDelay); |
| break; |
| |
| default: |
| // An error has occurred, quit trying this device. |
| FIDO_LOG(ERROR) << "Unexpected status " << static_cast<int>(result) |
| << " from U2F device"; |
| std::move(callback()) |
| .Run(CtapDeviceResponseCode::kCtap2ErrOther, absl::nullopt); |
| break; |
| } |
| } |
| |
| const std::vector<uint8_t>& U2fRegisterOperation::excluded_key_handle() const { |
| DCHECK_LT(current_key_handle_index_, request().exclude_list.size()); |
| return request().exclude_list[current_key_handle_index_].id; |
| } |
| |
| } // namespace device |