Commit f856c63c authored by Jun Choi's avatar Jun Choi Committed by Commit Bot

Implement KeepAlive logic for CTAP HID device

CTAP HID transport specification adds KEEP_ALIVE command. Add logic for
handling keep alive messages. That is, message from HID device should be
re-read after 100 milliseconds.

Change-Id: Iecb002f141d1a57f5753d25d728a5f1d851323db
Reviewed-on: https://chromium-review.googlesource.com/947193
Commit-Queue: Jun Choi <hongjunchoi@chromium.org>
Reviewed-by: default avatarJan Wilken Dörrie <jdoerrie@chromium.org>
Cr-Commit-Position: refs/heads/master@{#548626}
parent f1ef9d9b
......@@ -6,10 +6,19 @@
#include <utility>
#include "base/optional.h"
#include "device/fido/u2f_parsing_utils.h"
namespace device {
namespace {
MATCHER_P(IsCtapHidCommand, expected_command, "") {
return arg.size() >= 5 &&
arg[4] == (0x80 | static_cast<uint8_t>(expected_command));
}
} // namespace
MockHidConnection::MockHidConnection(
device::mojom::HidDeviceInfoPtr device,
device::mojom::HidConnectionRequest request,
......@@ -45,6 +54,31 @@ void MockHidConnection::SetNonce(base::span<uint8_t const> nonce) {
nonce_ = std::vector<uint8_t>(nonce.begin(), nonce.end());
}
void MockHidConnection::ExpectWriteHidInit() {
EXPECT_CALL(*this, WritePtr(::testing::_,
IsCtapHidCommand(FidoHidDeviceCommand::kInit),
::testing::_))
.WillOnce(::testing::Invoke(
[&](auto&&, const std::vector<uint8_t>& buffer,
device::mojom::HidConnection::WriteCallback* cb) {
ASSERT_EQ(64u, buffer.size());
// First 7 bytes are 4 bytes of channel id, one byte representing
// HID command, 2 bytes for payload length.
SetNonce(base::make_span(buffer).subspan(7, 8));
std::move(*cb).Run(true);
}));
}
void MockHidConnection::ExpectHidWriteWithCommand(FidoHidDeviceCommand cmd) {
EXPECT_CALL(*this,
WritePtr(::testing::_, IsCtapHidCommand(cmd), ::testing::_))
.WillOnce(::testing::Invoke(
[&](auto&&, const std::vector<uint8_t>& buffer,
device::mojom::HidConnection::WriteCallback* cb) {
std::move(*cb).Run(true);
}));
}
bool FakeHidConnection::mock_connection_error_ = false;
FakeHidConnection::FakeHidConnection(device::mojom::HidDeviceInfoPtr device)
......
......@@ -13,6 +13,7 @@
#include "base/containers/span.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "device/fido/fido_constants.h"
#include "mojo/public/cpp/bindings/binding_set.h"
#include "mojo/public/cpp/bindings/interface_ptr_set.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
......@@ -47,6 +48,9 @@ class MockHidConnection : public device::mojom::HidConnection {
SendFeatureReportCallback callback) override;
void SetNonce(base::span<uint8_t const> nonce);
void ExpectWriteHidInit();
void ExpectHidWriteWithCommand(FidoHidDeviceCommand cmd);
const std::vector<uint8_t>& connection_channel_id() const {
return connection_channel_id_;
}
......
......@@ -69,7 +69,6 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleDevice : public FidoDevice {
void StopTimeout();
void OnTimeout();
State state_ = State::kInit;
base::OneShotTimer timer_;
std::unique_ptr<FidoBleConnection> connection_;
......
......@@ -51,6 +51,8 @@ const std::array<uint8_t, 6> kU2fVersionResponse = {'U', '2', 'F',
'_', 'V', '2'};
const base::TimeDelta kDeviceTimeout = base::TimeDelta::FromSeconds(3);
const base::TimeDelta kHidKeepAliveDelay =
base::TimeDelta::FromMilliseconds(100);
const char kFormatKey[] = "fmt";
const char kAttestationStatementKey[] = "attStmt";
......
......@@ -260,6 +260,11 @@ extern const std::array<uint8_t, 6> kU2fVersionResponse;
// Maximum wait time before client error outs on device.
COMPONENT_EXPORT(DEVICE_FIDO) extern const base::TimeDelta kDeviceTimeout;
// Interval wait time before retrying reading on HID connection when
// CTAPHID_KEEPALIVE message has been received.
// https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html#ctaphid_keepalive-0x3b
COMPONENT_EXPORT(DEVICE_FIDO) extern const base::TimeDelta kHidKeepAliveDelay;
// String key values for attestation object as a response to MakeCredential
// request.
COMPONENT_EXPORT(DEVICE_FIDO) extern const char kFormatKey[];
......
......@@ -7,6 +7,7 @@
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/threading/thread_task_runner_handle.h"
#include "crypto/random.h"
#include "device/fido/fido_hid_message.h"
......@@ -26,7 +27,6 @@ static constexpr uint8_t kReportId = 0x00;
FidoHidDevice::FidoHidDevice(device::mojom::HidDeviceInfoPtr device_info,
device::mojom::HidManager* hid_manager)
: FidoDevice(),
state_(State::kInit),
hid_manager_(hid_manager),
device_info_(std::move(device_info)),
weak_factory_(this) {}
......@@ -61,9 +61,11 @@ void FidoHidDevice::Transition(std::vector<uint8_t> command,
state_ = State::kBusy;
ArmTimeout(repeating_callback);
// Write message to the device.
const auto command_type = supported_protocol() == ProtocolVersion::kCtap
? FidoHidDeviceCommand::kCbor
: FidoHidDeviceCommand::kMsg;
WriteMessage(
FidoHidMessage::Create(channel_id_, FidoHidDeviceCommand::kMsg,
std::move(command)),
FidoHidMessage::Create(channel_id_, command_type, std::move(command)),
true,
base::BindOnce(&FidoHidDevice::MessageReceived,
weak_factory_.GetWeakPtr(), repeating_callback));
......@@ -273,13 +275,34 @@ void FidoHidDevice::MessageReceived(DeviceCallback callback,
std::unique_ptr<FidoHidMessage> message) {
if (state_ == State::kDeviceError)
return;
timeout_callback_.Cancel();
if (!success) {
if (!success || !message) {
state_ = State::kDeviceError;
Transition(std::vector<uint8_t>(), std::move(callback));
return;
}
const auto cmd = message->cmd();
// If received HID packet has keep_alive as command type, re-read after delay.
if (supported_protocol() == ProtocolVersion::kCtap &&
cmd == FidoHidDeviceCommand::kKeepAlive) {
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&FidoHidDevice::OnKeepAlive, weak_factory_.GetWeakPtr(),
std::move(callback)),
kHidKeepAliveDelay);
return;
}
if (cmd != FidoHidDeviceCommand::kMsg && cmd != FidoHidDeviceCommand::kCbor) {
DLOG(ERROR) << "Unexpected HID device command received.";
state_ = State::kDeviceError;
Transition(std::vector<uint8_t>(), std::move(callback));
return;
}
auto response = message->GetMessagePayload();
state_ = State::kReady;
base::WeakPtr<FidoHidDevice> self = weak_factory_.GetWeakPtr();
std::move(callback).Run(
......@@ -310,6 +333,15 @@ void FidoHidDevice::TryWink(WinkCallback callback) {
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void FidoHidDevice::OnKeepAlive(DeviceCallback callback) {
auto repeating_callback =
base::AdaptCallbackForRepeating(std::move(callback));
ArmTimeout(repeating_callback);
ReadMessage(base::BindOnce(&FidoHidDevice::MessageReceived,
weak_factory_.GetWeakPtr(),
std::move(repeating_callback)));
}
void FidoHidDevice::OnWink(WinkCallback callback,
bool success,
std::unique_ptr<FidoHidMessage> response) {
......
......@@ -92,16 +92,17 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoHidDevice : public FidoDevice {
bool success,
uint8_t report_id,
const base::Optional<std::vector<uint8_t>>& buf);
void OnKeepAlive(DeviceCallback callback);
void OnWink(WinkCallback callback,
bool success,
std::unique_ptr<FidoHidMessage> response);
void ArmTimeout(DeviceCallback callback);
void OnTimeout(DeviceCallback callback);
base::WeakPtr<FidoDevice> GetWeakPtr() override;
uint32_t channel_id_ = kBroadcastChannel;
uint8_t capabilities_ = 0;
State state_ = State::kInit;
base::CancelableOnceClosure timeout_callback_;
std::queue<std::pair<std::vector<uint8_t>, DeviceCallback>>
......
This diff is collapsed.
......@@ -52,6 +52,10 @@ void FidoRequestHandlerBase::DiscoveryStarted(FidoDiscovery* discovery,
void FidoRequestHandlerBase::DeviceAdded(FidoDiscovery* discovery,
FidoDevice* device) {
DCHECK(!base::ContainsKey(ongoing_tasks(), device->GetId()));
// All devices are initially assumed to support CTAP protocol and thus
// AuthenticatorGetInfo command is sent to all connected devices. If device
// errors out, then it is assumed to support U2F protocol.
device->set_supported_protocol(ProtocolVersion::kCtap);
ongoing_tasks_.emplace(device->GetId(), CreateTaskForNewDevice(device));
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment