blob: 056edd070a72cbad8d231fa952b7a1e078273fd9 [file] [log] [blame]
// 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/bind_helpers.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "components/apdu/apdu_response.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)),
weak_factory_(this) {}
U2fRegisterOperation::~U2fRegisterOperation() = default;
void U2fRegisterOperation::Start() {
DCHECK(IsConvertibleToU2fRegisterCommand(request()));
const auto& exclude_list = request().exclude_list();
if (exclude_list && !exclude_list->empty()) {
// First try signing with the excluded credentials to see whether this
// device should be excluded.
TrySign();
} else {
TryRegistration();
}
}
void U2fRegisterOperation::TrySign() {
DispatchDeviceRequest(
ConvertToU2fSignCommand(request(), excluded_key_handle()),
base::BindOnce(&U2fRegisterOperation::OnCheckForExcludedKeyHandle,
weak_factory_.GetWeakPtr()));
}
void U2fRegisterOperation::OnCheckForExcludedKeyHandle(
base::Optional<std::vector<uint8_t>> device_response) {
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,
base::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::TrySign,
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.
if (++current_key_handle_index_ < request().exclude_list()->size()) {
TrySign();
} else {
// Reached the end of exclude list with no duplicate credential.
// Proceed with registration.
TryRegistration();
}
break;
default:
// Some sort of failure occurred. Silently drop device request.
std::move(callback())
.Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt);
break;
}
}
void U2fRegisterOperation::TryRegistration() {
DispatchDeviceRequest(
ConvertToU2fRegisterCommand(request()),
base::BindOnce(&U2fRegisterOperation::OnRegisterResponseReceived,
weak_factory_.GetWeakPtr()));
}
void U2fRegisterOperation::OnRegisterResponseReceived(
base::Optional<std::vector<uint8_t>> device_response) {
auto result = apdu::ApduResponse::Status::SW_WRONG_DATA;
const auto apdu_response =
device_response
? apdu::ApduResponse::CreateFromMessage(std::move(*device_response))
: base::nullopt;
if (apdu_response) {
result = apdu_response->status();
}
switch (result) {
case apdu::ApduResponse::Status::SW_NO_ERROR: {
auto response =
AuthenticatorMakeCredentialResponse::CreateFromU2fRegisterResponse(
device()->DeviceTransport(),
fido_parsing_utils::CreateSHA256Hash(request().rp().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::TryRegistration,
weak_factory_.GetWeakPtr()),
kU2fRetryDelay);
break;
default:
// An error has occurred, quit trying this device.
std::move(callback())
.Run(CtapDeviceResponseCode::kCtap2ErrOther, base::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().value()[current_key_handle_index_].id();
}
} // namespace device