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 @@ ...@@ -6,10 +6,19 @@
#include <utility> #include <utility>
#include "base/optional.h" #include "device/fido/u2f_parsing_utils.h"
namespace device { namespace device {
namespace {
MATCHER_P(IsCtapHidCommand, expected_command, "") {
return arg.size() >= 5 &&
arg[4] == (0x80 | static_cast<uint8_t>(expected_command));
}
} // namespace
MockHidConnection::MockHidConnection( MockHidConnection::MockHidConnection(
device::mojom::HidDeviceInfoPtr device, device::mojom::HidDeviceInfoPtr device,
device::mojom::HidConnectionRequest request, device::mojom::HidConnectionRequest request,
...@@ -45,6 +54,31 @@ void MockHidConnection::SetNonce(base::span<uint8_t const> nonce) { ...@@ -45,6 +54,31 @@ void MockHidConnection::SetNonce(base::span<uint8_t const> nonce) {
nonce_ = std::vector<uint8_t>(nonce.begin(), nonce.end()); 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; bool FakeHidConnection::mock_connection_error_ = false;
FakeHidConnection::FakeHidConnection(device::mojom::HidDeviceInfoPtr device) FakeHidConnection::FakeHidConnection(device::mojom::HidDeviceInfoPtr device)
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "base/containers/span.h" #include "base/containers/span.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/ptr_util.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/binding_set.h"
#include "mojo/public/cpp/bindings/interface_ptr_set.h" #include "mojo/public/cpp/bindings/interface_ptr_set.h"
#include "mojo/public/cpp/bindings/strong_binding.h" #include "mojo/public/cpp/bindings/strong_binding.h"
...@@ -47,6 +48,9 @@ class MockHidConnection : public device::mojom::HidConnection { ...@@ -47,6 +48,9 @@ class MockHidConnection : public device::mojom::HidConnection {
SendFeatureReportCallback callback) override; SendFeatureReportCallback callback) override;
void SetNonce(base::span<uint8_t const> nonce); void SetNonce(base::span<uint8_t const> nonce);
void ExpectWriteHidInit();
void ExpectHidWriteWithCommand(FidoHidDeviceCommand cmd);
const std::vector<uint8_t>& connection_channel_id() const { const std::vector<uint8_t>& connection_channel_id() const {
return connection_channel_id_; return connection_channel_id_;
} }
......
...@@ -69,7 +69,6 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleDevice : public FidoDevice { ...@@ -69,7 +69,6 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleDevice : public FidoDevice {
void StopTimeout(); void StopTimeout();
void OnTimeout(); void OnTimeout();
State state_ = State::kInit;
base::OneShotTimer timer_; base::OneShotTimer timer_;
std::unique_ptr<FidoBleConnection> connection_; std::unique_ptr<FidoBleConnection> connection_;
......
...@@ -51,6 +51,8 @@ const std::array<uint8_t, 6> kU2fVersionResponse = {'U', '2', 'F', ...@@ -51,6 +51,8 @@ const std::array<uint8_t, 6> kU2fVersionResponse = {'U', '2', 'F',
'_', 'V', '2'}; '_', 'V', '2'};
const base::TimeDelta kDeviceTimeout = base::TimeDelta::FromSeconds(3); const base::TimeDelta kDeviceTimeout = base::TimeDelta::FromSeconds(3);
const base::TimeDelta kHidKeepAliveDelay =
base::TimeDelta::FromMilliseconds(100);
const char kFormatKey[] = "fmt"; const char kFormatKey[] = "fmt";
const char kAttestationStatementKey[] = "attStmt"; const char kAttestationStatementKey[] = "attStmt";
......
...@@ -260,6 +260,11 @@ extern const std::array<uint8_t, 6> kU2fVersionResponse; ...@@ -260,6 +260,11 @@ extern const std::array<uint8_t, 6> kU2fVersionResponse;
// Maximum wait time before client error outs on device. // Maximum wait time before client error outs on device.
COMPONENT_EXPORT(DEVICE_FIDO) extern const base::TimeDelta kDeviceTimeout; 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 // String key values for attestation object as a response to MakeCredential
// request. // request.
COMPONENT_EXPORT(DEVICE_FIDO) extern const char kFormatKey[]; COMPONENT_EXPORT(DEVICE_FIDO) extern const char kFormatKey[];
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include "base/bind.h" #include "base/bind.h"
#include "base/bind_helpers.h" #include "base/bind_helpers.h"
#include "base/command_line.h" #include "base/command_line.h"
#include "base/logging.h"
#include "base/threading/thread_task_runner_handle.h" #include "base/threading/thread_task_runner_handle.h"
#include "crypto/random.h" #include "crypto/random.h"
#include "device/fido/fido_hid_message.h" #include "device/fido/fido_hid_message.h"
...@@ -26,7 +27,6 @@ static constexpr uint8_t kReportId = 0x00; ...@@ -26,7 +27,6 @@ static constexpr uint8_t kReportId = 0x00;
FidoHidDevice::FidoHidDevice(device::mojom::HidDeviceInfoPtr device_info, FidoHidDevice::FidoHidDevice(device::mojom::HidDeviceInfoPtr device_info,
device::mojom::HidManager* hid_manager) device::mojom::HidManager* hid_manager)
: FidoDevice(), : FidoDevice(),
state_(State::kInit),
hid_manager_(hid_manager), hid_manager_(hid_manager),
device_info_(std::move(device_info)), device_info_(std::move(device_info)),
weak_factory_(this) {} weak_factory_(this) {}
...@@ -61,9 +61,11 @@ void FidoHidDevice::Transition(std::vector<uint8_t> command, ...@@ -61,9 +61,11 @@ void FidoHidDevice::Transition(std::vector<uint8_t> command,
state_ = State::kBusy; state_ = State::kBusy;
ArmTimeout(repeating_callback); ArmTimeout(repeating_callback);
// Write message to the device. // Write message to the device.
const auto command_type = supported_protocol() == ProtocolVersion::kCtap
? FidoHidDeviceCommand::kCbor
: FidoHidDeviceCommand::kMsg;
WriteMessage( WriteMessage(
FidoHidMessage::Create(channel_id_, FidoHidDeviceCommand::kMsg, FidoHidMessage::Create(channel_id_, command_type, std::move(command)),
std::move(command)),
true, true,
base::BindOnce(&FidoHidDevice::MessageReceived, base::BindOnce(&FidoHidDevice::MessageReceived,
weak_factory_.GetWeakPtr(), repeating_callback)); weak_factory_.GetWeakPtr(), repeating_callback));
...@@ -273,13 +275,34 @@ void FidoHidDevice::MessageReceived(DeviceCallback callback, ...@@ -273,13 +275,34 @@ void FidoHidDevice::MessageReceived(DeviceCallback callback,
std::unique_ptr<FidoHidMessage> message) { std::unique_ptr<FidoHidMessage> message) {
if (state_ == State::kDeviceError) if (state_ == State::kDeviceError)
return; return;
timeout_callback_.Cancel(); 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; state_ = State::kDeviceError;
Transition(std::vector<uint8_t>(), std::move(callback)); Transition(std::vector<uint8_t>(), std::move(callback));
return; return;
} }
auto response = message->GetMessagePayload();
state_ = State::kReady; state_ = State::kReady;
base::WeakPtr<FidoHidDevice> self = weak_factory_.GetWeakPtr(); base::WeakPtr<FidoHidDevice> self = weak_factory_.GetWeakPtr();
std::move(callback).Run( std::move(callback).Run(
...@@ -310,6 +333,15 @@ void FidoHidDevice::TryWink(WinkCallback callback) { ...@@ -310,6 +333,15 @@ void FidoHidDevice::TryWink(WinkCallback callback) {
weak_factory_.GetWeakPtr(), std::move(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, void FidoHidDevice::OnWink(WinkCallback callback,
bool success, bool success,
std::unique_ptr<FidoHidMessage> response) { std::unique_ptr<FidoHidMessage> response) {
......
...@@ -92,16 +92,17 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoHidDevice : public FidoDevice { ...@@ -92,16 +92,17 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoHidDevice : public FidoDevice {
bool success, bool success,
uint8_t report_id, uint8_t report_id,
const base::Optional<std::vector<uint8_t>>& buf); const base::Optional<std::vector<uint8_t>>& buf);
void OnKeepAlive(DeviceCallback callback);
void OnWink(WinkCallback callback, void OnWink(WinkCallback callback,
bool success, bool success,
std::unique_ptr<FidoHidMessage> response); std::unique_ptr<FidoHidMessage> response);
void ArmTimeout(DeviceCallback callback); void ArmTimeout(DeviceCallback callback);
void OnTimeout(DeviceCallback callback); void OnTimeout(DeviceCallback callback);
base::WeakPtr<FidoDevice> GetWeakPtr() override; base::WeakPtr<FidoDevice> GetWeakPtr() override;
uint32_t channel_id_ = kBroadcastChannel; uint32_t channel_id_ = kBroadcastChannel;
uint8_t capabilities_ = 0; uint8_t capabilities_ = 0;
State state_ = State::kInit;
base::CancelableOnceClosure timeout_callback_; base::CancelableOnceClosure timeout_callback_;
std::queue<std::pair<std::vector<uint8_t>, DeviceCallback>> std::queue<std::pair<std::vector<uint8_t>, DeviceCallback>>
......
This diff is collapsed.
...@@ -52,6 +52,10 @@ void FidoRequestHandlerBase::DiscoveryStarted(FidoDiscovery* discovery, ...@@ -52,6 +52,10 @@ void FidoRequestHandlerBase::DiscoveryStarted(FidoDiscovery* discovery,
void FidoRequestHandlerBase::DeviceAdded(FidoDiscovery* discovery, void FidoRequestHandlerBase::DeviceAdded(FidoDiscovery* discovery,
FidoDevice* device) { FidoDevice* device) {
DCHECK(!base::ContainsKey(ongoing_tasks(), device->GetId())); 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)); 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