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>>
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#include "device/fido/fido_constants.h" #include "device/fido/fido_constants.h"
#include "device/fido/fido_hid_device.h" #include "device/fido/fido_hid_device.h"
#include "device/fido/test_callback_receiver.h" #include "device/fido/test_callback_receiver.h"
#include "device/fido/u2f_parsing_utils.h"
#include "device/fido/u2f_request.h" #include "device/fido/u2f_request.h"
#include "mojo/public/cpp/bindings/binding.h" #include "mojo/public/cpp/bindings/binding.h"
#include "mojo/public/cpp/bindings/interface_request.h" #include "mojo/public/cpp/bindings/interface_request.h"
...@@ -28,45 +29,53 @@ namespace device { ...@@ -28,45 +29,53 @@ namespace device {
using ::testing::_; using ::testing::_;
using ::testing::Invoke; using ::testing::Invoke;
using ::testing::WithArg;
using ::testing::WithArgs;
namespace { namespace {
std::string HexEncode(base::span<const uint8_t> in) { // HID_MSG(83), followed by payload length(0008), followed by 'U2F_V2', followed
return base::HexEncode(in.data(), in.size()); // by APDU response code(9000).
} constexpr uint8_t kMockVersionResponseSuffix[] = {
0x83, 0x00, 0x08, 0x55, 0x32, 0x46, 0x5f, 0x56, 0x32, 0x90, 0x00};
std::vector<uint8_t> HexDecode(base::StringPiece in) { // HID_KEEP_ALIVE(bb), followed by payload length(0001), followed by
std::vector<uint8_t> out; // status processing(01) byte.
bool result = base::HexStringToBytes(in.as_string(), &out); constexpr uint8_t kMockKeepAliveResponseSuffix[] = {0xbb, 0x00, 0x01, 0x01};
DCHECK(result);
return out;
}
// Converts hex encoded StringPiece to byte vector and pads zero to fit HID // 4 byte broadcast channel id(ffffffff), followed by an HID_INIT command(86),
// packet size. // followed by a fixed size payload length(11). 8 byte nonce and 4 byte channel
std::vector<uint8_t> MakePacket(base::StringPiece hex) { // ID must be appended to create a well formed HID_INIT packet.
std::vector<uint8_t> out = HexDecode(hex); constexpr uint8_t kInitResponsePrefix[] = {
out.resize(64); 0xff, 0xff, 0xff, 0xff, 0x86, 0x00, 0x11,
return out; };
}
// Returns HID_INIT request to send to device with mock connection. // Returns HID_INIT request to send to device with mock connection.
std::vector<uint8_t> CreateMockInitResponse(std::vector<uint8_t> nonce, std::vector<uint8_t> CreateMockInitResponse(
std::vector<uint8_t> channel_id) { base::span<const uint8_t> nonce,
// 4 bytes of broadcast channel identifier(ffffffff), followed by base::span<const uint8_t> channel_id) {
// HID_INIT command(86) and 2 byte payload length(11). auto init_response = u2f_parsing_utils::Materialize(kInitResponsePrefix);
return MakePacket("ffffffff860011" + HexEncode(nonce) + u2f_parsing_utils::Append(&init_response, nonce);
HexEncode(channel_id)); u2f_parsing_utils::Append(&init_response, channel_id);
init_response.resize(64);
return init_response;
}
// Returns HID keep alive message encoded into HID packet format.
std::vector<uint8_t> GetKeepAliveHidMessage(
base::span<const uint8_t> channel_id) {
auto response = u2f_parsing_utils::Materialize(channel_id);
u2f_parsing_utils::Append(&response, kMockKeepAliveResponseSuffix);
response.resize(64);
return response;
} }
// Returns "U2F_v2" as a mock response to version request with given channel id. // Returns "U2F_v2" as a mock response to version request with given channel id.
std::vector<uint8_t> CreateMockVersionResponse( std::vector<uint8_t> CreateMockResponse(
std::vector<uint8_t> channel_id) { base::span<const uint8_t> channel_id,
// HID_MSG command(83), followed by payload length(0008), followed by base::span<const uint8_t> response_buffer) {
// hex encoded "U2F_V2" and NO_ERROR response code(9000). auto response = u2f_parsing_utils::Materialize(channel_id);
return MakePacket(HexEncode(channel_id) + "8300085532465f56329000"); u2f_parsing_utils::Append(&response, response_buffer);
response.resize(64);
return response;
} }
// Returns U2F_V2 version response formatted in APDU response encoding. // Returns U2F_V2 version response formatted in APDU response encoding.
...@@ -127,19 +136,16 @@ using TestDeviceCallbackReceiver = ...@@ -127,19 +136,16 @@ using TestDeviceCallbackReceiver =
class FidoHidDeviceTest : public ::testing::Test { class FidoHidDeviceTest : public ::testing::Test {
public: public:
FidoHidDeviceTest()
: scoped_task_environment_(
base::test::ScopedTaskEnvironment::MainThreadType::UI) {}
void SetUp() override { void SetUp() override {
fake_hid_manager_ = std::make_unique<FakeHidManager>(); fake_hid_manager_ = std::make_unique<FakeHidManager>();
fake_hid_manager_->AddBinding2(mojo::MakeRequest(&hid_manager_)); fake_hid_manager_->AddBinding2(mojo::MakeRequest(&hid_manager_));
} }
protected: protected:
base::test::ScopedTaskEnvironment scoped_task_environment_{
base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME};
device::mojom::HidManagerPtr hid_manager_; device::mojom::HidManagerPtr hid_manager_;
std::unique_ptr<FakeHidManager> fake_hid_manager_; std::unique_ptr<FakeHidManager> fake_hid_manager_;
base::test::ScopedTaskEnvironment scoped_task_environment_;
}; };
TEST_F(FidoHidDeviceTest, TestConnectionFailure) { TEST_F(FidoHidDeviceTest, TestConnectionFailure) {
...@@ -223,65 +229,55 @@ TEST_F(FidoHidDeviceTest, TestDeviceError) { ...@@ -223,65 +229,55 @@ TEST_F(FidoHidDeviceTest, TestDeviceError) {
} }
TEST_F(FidoHidDeviceTest, TestRetryChannelAllocation) { TEST_F(FidoHidDeviceTest, TestRetryChannelAllocation) {
const std::vector<uint8_t> kIncorrectNonce = {0x00, 0x00, 0x00, 0x00, constexpr uint8_t kIncorrectNonce[] = {0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00}; 0x00, 0x00, 0x00, 0x00};
const std::vector<uint8_t> kChannelId = {0x01, 0x02, 0x03, 0x04}; constexpr uint8_t kChannelId[] = {0x01, 0x02, 0x03, 0x04};
auto hid_device = TestHidDevice(); auto hid_device = TestHidDevice();
// Replace device HID connection with custom client connection bound to mock // Replace device HID connection with custom client connection bound to mock
// server-side mojo connection. // server-side mojo connection.
device::mojom::HidConnectionPtr connection_client; device::mojom::HidConnectionPtr connection_client;
MockHidConnection mock_connection( MockHidConnection mock_connection(hid_device.Clone(),
hid_device.Clone(), mojo::MakeRequest(&connection_client), kChannelId); mojo::MakeRequest(&connection_client),
u2f_parsing_utils::Materialize(kChannelId));
// Delegate custom functions to be invoked for mock hid connection.
EXPECT_CALL(mock_connection, WritePtr(_, _, _)) // Initial write for establishing a channel ID.
// HID_INIT request to authenticator for channel allocation. mock_connection.ExpectWriteHidInit();
.WillOnce(WithArgs<1, 2>(
Invoke([&](const std::vector<uint8_t>& buffer,
device::mojom::HidConnection::WriteCallback* cb) {
mock_connection.SetNonce(base::make_span(buffer).subspan(7, 8));
std::move(*cb).Run(true);
})))
// HID_MSG request to authenticator for version request. // HID_MSG request to authenticator for version request.
.WillOnce(WithArgs<2>( mock_connection.ExpectHidWriteWithCommand(FidoHidDeviceCommand::kMsg);
Invoke([](device::mojom::HidConnection::WriteCallback* cb) {
std::move(*cb).Run(true);
})));
EXPECT_CALL(mock_connection, ReadPtr(_)) EXPECT_CALL(mock_connection, ReadPtr(_))
// First response to HID_INIT request with incorrect nonce. // First response to HID_INIT request with an incorrect nonce.
.WillOnce(WithArg<0>( .WillOnce(
Invoke([kIncorrectNonce, &mock_connection]( Invoke([kIncorrectNonce, &mock_connection](auto* cb) {
device::mojom::HidConnection::ReadCallback* cb) {
std::move(*cb).Run( std::move(*cb).Run(
true, 0, true, 0,
CreateMockInitResponse( CreateMockInitResponse(
kIncorrectNonce, mock_connection.connection_channel_id())); kIncorrectNonce, mock_connection.connection_channel_id()));
}))) }))
// Second response to HID_INIT request with correct nonce. // Second response to HID_INIT request with a correct nonce.
.WillOnce(WithArg<0>(Invoke( .WillOnce(Invoke(
[&mock_connection](device::mojom::HidConnection::ReadCallback* cb) { [&mock_connection](device::mojom::HidConnection::ReadCallback* cb) {
std::move(*cb).Run(true, 0, std::move(*cb).Run(true, 0,
CreateMockInitResponse( CreateMockInitResponse(
mock_connection.nonce(), mock_connection.nonce(),
mock_connection.connection_channel_id())); mock_connection.connection_channel_id()));
}))) }))
// Version response from the authenticator. // Version response from the authenticator.
.WillOnce(WithArg<0>(Invoke( .WillOnce(Invoke(
[&mock_connection](device::mojom::HidConnection::ReadCallback* cb) { [&mock_connection](device::mojom::HidConnection::ReadCallback* cb) {
std::move(*cb).Run(true, 0, std::move(*cb).Run(
CreateMockVersionResponse( true, 0,
mock_connection.connection_channel_id())); CreateMockResponse(mock_connection.connection_channel_id(),
}))); kMockVersionResponseSuffix));
}));
// Add device and set mock connection to fake hid manager. // Add device and set mock connection to fake hid manager.
fake_hid_manager_->AddDeviceAndSetConnection(std::move(hid_device), fake_hid_manager_->AddDeviceAndSetConnection(std::move(hid_device),
std::move(connection_client)); std::move(connection_client));
FidoDeviceEnumerateCallbackReceiver receiver(hid_manager_.get()); FidoDeviceEnumerateCallbackReceiver receiver(hid_manager_.get());
hid_manager_->GetDevices(receiver.callback()); hid_manager_->GetDevices(receiver.callback());
receiver.WaitForCallback(); receiver.WaitForCallback();
...@@ -301,4 +297,139 @@ TEST_F(FidoHidDeviceTest, TestRetryChannelAllocation) { ...@@ -301,4 +297,139 @@ TEST_F(FidoHidDeviceTest, TestRetryChannelAllocation) {
EXPECT_THAT(*result, testing::ElementsAreArray(GetValidU2fVersionResponse())); EXPECT_THAT(*result, testing::ElementsAreArray(GetValidU2fVersionResponse()));
} }
TEST_F(FidoHidDeviceTest, TestKeepAliveMessage) {
constexpr uint8_t kChannelId[] = {0x01, 0x02, 0x03, 0x04};
auto hid_device = TestHidDevice();
// Replace device HID connection with custom client connection bound to mock
// server-side mojo connection.
device::mojom::HidConnectionPtr connection_client;
MockHidConnection mock_connection(hid_device.Clone(),
mojo::MakeRequest(&connection_client),
u2f_parsing_utils::Materialize(kChannelId));
// Initial write for establishing channel ID.
mock_connection.ExpectWriteHidInit();
// HID_CBOR request to authenticator.
mock_connection.ExpectHidWriteWithCommand(FidoHidDeviceCommand::kCbor);
EXPECT_CALL(mock_connection, ReadPtr(_))
// Response to HID_INIT request.
.WillOnce(Invoke([&](device::mojom::HidConnection::ReadCallback* cb) {
std::move(*cb).Run(
true, 0,
CreateMockInitResponse(mock_connection.nonce(),
mock_connection.connection_channel_id()));
}))
// // Keep alive message sent from the authenticator.
.WillOnce(Invoke([&](device::mojom::HidConnection::ReadCallback* cb) {
std::move(*cb).Run(
true, 0,
GetKeepAliveHidMessage(mock_connection.connection_channel_id()));
}))
// Repeated Read() invocation due to keep alive message. Sends a dummy
// response that corresponds to U2F version response.
.WillOnce(Invoke([&](device::mojom::HidConnection::ReadCallback* cb) {
auto almost_time_out =
kDeviceTimeout - base::TimeDelta::FromMicroseconds(1);
scoped_task_environment_.FastForwardBy(almost_time_out);
std::move(*cb).Run(
true, 0,
CreateMockResponse(mock_connection.connection_channel_id(),
kMockVersionResponseSuffix));
}));
// Add device and set mock connection to fake hid manager.
fake_hid_manager_->AddDeviceAndSetConnection(std::move(hid_device),
std::move(connection_client));
FidoDeviceEnumerateCallbackReceiver receiver(hid_manager_.get());
hid_manager_->GetDevices(receiver.callback());
receiver.WaitForCallback();
std::vector<std::unique_ptr<FidoHidDevice>> u2f_devices =
receiver.TakeReturnedDevicesFiltered();
ASSERT_EQ(1u, u2f_devices.size());
auto& device = u2f_devices.front();
// Keep alive message handling is only supported for CTAP HID device.
device->set_supported_protocol(ProtocolVersion::kCtap);
TestDeviceCallbackReceiver cb;
device->DeviceTransact(U2fRequest::GetU2fVersionApduCommand(false),
cb.callback());
cb.WaitForCallback();
const auto result = std::get<0>(*cb.result());
ASSERT_TRUE(result);
EXPECT_THAT(*result, testing::ElementsAreArray(GetValidU2fVersionResponse()));
}
TEST_F(FidoHidDeviceTest, TestDeviceTimeoutAfterKeepAliveMessage) {
constexpr uint8_t kChannelId[] = {0x01, 0x02, 0x03, 0x04};
auto hid_device = TestHidDevice();
// Replace device HID connection with custom client connection bound to mock
// server-side mojo connection.
device::mojom::HidConnectionPtr connection_client;
MockHidConnection mock_connection(hid_device.Clone(),
mojo::MakeRequest(&connection_client),
u2f_parsing_utils::Materialize(kChannelId));
// Initial write for establishing channel ID.
mock_connection.ExpectWriteHidInit();
// HID_CBOR request to authenticator.
mock_connection.ExpectHidWriteWithCommand(FidoHidDeviceCommand::kCbor);
EXPECT_CALL(mock_connection, ReadPtr(_))
// Response to HID_INIT request.
.WillOnce(Invoke([&](device::mojom::HidConnection::ReadCallback* cb) {
std::move(*cb).Run(
true, 0,
CreateMockInitResponse(mock_connection.nonce(),
mock_connection.connection_channel_id()));
}))
// // Keep alive message sent from the authenticator.
.WillOnce(Invoke([&](device::mojom::HidConnection::ReadCallback* cb) {
std::move(*cb).Run(
true, 0,
GetKeepAliveHidMessage(mock_connection.connection_channel_id()));
}))
// Repeated Read() invocation due to keep alive message. The callback
// is invoked only after 3 seconds, which should cause device to timeout.
.WillOnce(Invoke([&](device::mojom::HidConnection::ReadCallback* cb) {
scoped_task_environment_.FastForwardBy(kDeviceTimeout);
std::move(*cb).Run(
true, 0,
CreateMockResponse(mock_connection.connection_channel_id(),
kMockVersionResponseSuffix));
}));
// Add device and set mock connection to fake hid manager.
fake_hid_manager_->AddDeviceAndSetConnection(std::move(hid_device),
std::move(connection_client));
FidoDeviceEnumerateCallbackReceiver receiver(hid_manager_.get());
hid_manager_->GetDevices(receiver.callback());
receiver.WaitForCallback();
std::vector<std::unique_ptr<FidoHidDevice>> u2f_devices =
receiver.TakeReturnedDevicesFiltered();
ASSERT_EQ(1u, u2f_devices.size());
auto& device = u2f_devices.front();
// Keep alive message handling is only supported for CTAP HID device.
device->set_supported_protocol(ProtocolVersion::kCtap);
TestDeviceCallbackReceiver cb;
device->DeviceTransact(U2fRequest::GetU2fVersionApduCommand(false),
cb.callback());
cb.WaitForCallback();
const auto result = std::get<0>(*cb.result());
EXPECT_FALSE(result);
EXPECT_EQ(FidoDevice::State::kDeviceError, device->state());
}
} // namespace device } // namespace device
...@@ -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