| Index: device/u2f/u2f_hid_device.cc
|
| diff --git a/device/u2f/u2f_hid_device.cc b/device/u2f/u2f_hid_device.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..4a4380da276e94f04ac64c094c66bd4186d034a1
|
| --- /dev/null
|
| +++ b/device/u2f/u2f_hid_device.cc
|
| @@ -0,0 +1,314 @@
|
| +// Copyright 2017 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 "u2f_hid_device.h"
|
| +
|
| +#include "base/bind.h"
|
| +#include "base/bind_helpers.h"
|
| +#include "base/command_line.h"
|
| +#include "crypto/random.h"
|
| +#include "device/base/device_client.h"
|
| +#include "device/hid/hid_connection.h"
|
| +#include "u2f_apdu_command.h"
|
| +#include "u2f_message.h"
|
| +
|
| +namespace device {
|
| +
|
| +namespace switches {
|
| +static constexpr char kEnableU2fHidTest[] = "enable-u2f-hid-tests";
|
| +} // namespace switches
|
| +
|
| +U2fHidDevice::U2fHidDevice(scoped_refptr<HidDeviceInfo> device_info)
|
| + : U2fDevice(),
|
| + state_(State::INIT),
|
| + device_info_(device_info),
|
| + weak_factory_(this) {
|
| + channel_id_ = kBroadcastChannel;
|
| +}
|
| +
|
| +U2fHidDevice::~U2fHidDevice() {
|
| + // Cleanup connection
|
| + if (connection_)
|
| + connection_->Close();
|
| +}
|
| +
|
| +void U2fHidDevice::DeviceTransact(scoped_refptr<U2fApduCommand> command,
|
| + const DeviceCallback& callback) {
|
| + Transition(command, callback);
|
| +}
|
| +
|
| +void U2fHidDevice::Transition(scoped_refptr<U2fApduCommand> command,
|
| + const DeviceCallback& callback) {
|
| + switch (state_) {
|
| + case State::INIT:
|
| + state_ = State::BUSY;
|
| + Connect(base::Bind(&U2fHidDevice::OnConnect, weak_factory_.GetWeakPtr(),
|
| + command, callback));
|
| + break;
|
| + case State::CONNECTED:
|
| + state_ = State::BUSY;
|
| + AllocateChannel(command, callback);
|
| + break;
|
| + case State::IDLE: {
|
| + state_ = State::BUSY;
|
| + scoped_refptr<U2fMessage> msg = U2fMessage::Create(
|
| + channel_id_, U2fMessage::Type::CMD_MSG, command->GetEncodedCommand());
|
| + WriteMessage(msg, true,
|
| + base::Bind(&U2fHidDevice::MessageReceived,
|
| + weak_factory_.GetWeakPtr(), callback));
|
| + break;
|
| + }
|
| + case State::BUSY:
|
| + pending_transactions_.push_back({std::move(command), callback});
|
| + break;
|
| + case State::DEVICE_ERROR:
|
| + default:
|
| + base::WeakPtr<U2fHidDevice> self = weak_factory_.GetWeakPtr();
|
| + callback.Run(false, nullptr);
|
| + // Executing callbacks may free |this|. Check |self| first.
|
| + while (self && !pending_transactions_.empty()) {
|
| + // Respond to any pending requests
|
| + DeviceCallback pending_cb = pending_transactions_.front().second;
|
| + pending_transactions_.pop_front();
|
| + pending_cb.Run(false, nullptr);
|
| + }
|
| + break;
|
| + }
|
| +}
|
| +
|
| +void U2fHidDevice::Connect(const HidService::ConnectCallback& callback) {
|
| + HidService* hid_service = DeviceClient::Get()->GetHidService();
|
| +
|
| + hid_service->Connect(device_info_->device_id(), callback);
|
| +}
|
| +
|
| +void U2fHidDevice::OnConnect(scoped_refptr<U2fApduCommand> command,
|
| + const DeviceCallback& callback,
|
| + scoped_refptr<HidConnection> connection) {
|
| + if (connection) {
|
| + connection_ = connection;
|
| + state_ = State::CONNECTED;
|
| + } else {
|
| + state_ = State::DEVICE_ERROR;
|
| + }
|
| + Transition(command, callback);
|
| +}
|
| +
|
| +void U2fHidDevice::AllocateChannel(scoped_refptr<U2fApduCommand> command,
|
| + const DeviceCallback& callback) {
|
| + // Send random nonce to device to verify received message
|
| + std::vector<uint8_t> nonce(8);
|
| + crypto::RandBytes(nonce.data(), nonce.size());
|
| + scoped_refptr<U2fMessage> message =
|
| + U2fMessage::Create(channel_id_, U2fMessage::Type::CMD_INIT, nonce);
|
| +
|
| + WriteMessage(
|
| + message, true,
|
| + base::Bind(&U2fHidDevice::OnAllocateChannel, weak_factory_.GetWeakPtr(),
|
| + nonce, command, callback));
|
| +}
|
| +
|
| +void U2fHidDevice::OnAllocateChannel(std::vector<uint8_t> nonce,
|
| + scoped_refptr<U2fApduCommand> command,
|
| + const DeviceCallback& callback,
|
| + bool success,
|
| + scoped_refptr<U2fMessage> message) {
|
| + if (!success || !message) {
|
| + state_ = State::DEVICE_ERROR;
|
| + Transition(nullptr, callback);
|
| + return;
|
| + }
|
| + // Channel allocation response is defined as:
|
| + // 0: 8 byte nonce
|
| + // 8: 4 byte channel id
|
| + // 12: Protocol version id
|
| + // 13: Major device version
|
| + // 14: Minor device version
|
| + // 15: Build device version
|
| + // 16: Capabilities
|
| + std::vector<uint8_t> payload = message->GetMessagePayload();
|
| + if (payload.size() != 17) {
|
| + state_ = State::DEVICE_ERROR;
|
| + Transition(nullptr, callback);
|
| + return;
|
| + }
|
| +
|
| + std::vector<uint8_t> received_nonce(std::begin(payload),
|
| + std::begin(payload) + 8);
|
| + if (nonce != received_nonce) {
|
| + state_ = State::DEVICE_ERROR;
|
| + Transition(nullptr, callback);
|
| + return;
|
| + }
|
| +
|
| + size_t index = 8;
|
| + channel_id_ = payload[index++] << 24;
|
| + channel_id_ |= payload[index++] << 16;
|
| + channel_id_ |= payload[index++] << 8;
|
| + channel_id_ |= payload[index++];
|
| + capabilities_ = payload[16];
|
| +
|
| + state_ = State::IDLE;
|
| + Transition(command, callback);
|
| +}
|
| +
|
| +void U2fHidDevice::WriteMessage(scoped_refptr<U2fMessage> message,
|
| + bool response_expected,
|
| + U2fHidMessageCallback callback) {
|
| + if (!connection_ || !message || message->NumPackets() == 0) {
|
| + std::move(callback).Run(false, nullptr);
|
| + return;
|
| + }
|
| +
|
| + scoped_refptr<net::IOBufferWithSize> buffer = message->PopNextPacket();
|
| +
|
| + connection_->Write(
|
| + buffer, buffer->size(),
|
| + base::Bind(&U2fHidDevice::PacketWritten, weak_factory_.GetWeakPtr(),
|
| + message, true, base::Passed(std::move(callback))));
|
| +}
|
| +
|
| +void U2fHidDevice::PacketWritten(scoped_refptr<U2fMessage> message,
|
| + bool response_expected,
|
| + U2fHidMessageCallback callback,
|
| + bool success) {
|
| + if (success && message->NumPackets() > 0) {
|
| + WriteMessage(message, response_expected, std::move(callback));
|
| + } else if (success && response_expected) {
|
| + ReadMessage(std::move(callback));
|
| + } else {
|
| + std::move(callback).Run(success, nullptr);
|
| + }
|
| +}
|
| +
|
| +void U2fHidDevice::ReadMessage(U2fHidMessageCallback callback) {
|
| + if (!connection_) {
|
| + std::move(callback).Run(false, nullptr);
|
| + return;
|
| + }
|
| +
|
| + connection_->Read(base::Bind(&U2fHidDevice::OnRead,
|
| + weak_factory_.GetWeakPtr(),
|
| + base::Passed(std::move(callback))));
|
| +}
|
| +
|
| +void U2fHidDevice::OnRead(U2fHidMessageCallback callback,
|
| + bool success,
|
| + scoped_refptr<net::IOBuffer> buf,
|
| + size_t size) {
|
| + if (!success) {
|
| + std::move(callback).Run(success, nullptr);
|
| + return;
|
| + }
|
| +
|
| + scoped_refptr<net::IOBufferWithSize> buffer(new net::IOBufferWithSize(size));
|
| + memcpy(buffer->data(), buf->data(), size);
|
| + scoped_refptr<U2fMessage> read_message =
|
| + U2fMessage::CreateFromSerializedData(buffer);
|
| +
|
| + if (!read_message) {
|
| + std::move(callback).Run(false, nullptr);
|
| + return;
|
| + }
|
| +
|
| + // Received a message from a different channel, so try again
|
| + if (channel_id_ != read_message->channel_id()) {
|
| + connection_->Read(base::Bind(&U2fHidDevice::OnRead,
|
| + weak_factory_.GetWeakPtr(),
|
| + base::Passed(std::move(callback))));
|
| + return;
|
| + }
|
| +
|
| + if (read_message->MessageComplete()) {
|
| + std::move(callback).Run(success, read_message);
|
| + return;
|
| + }
|
| +
|
| + // Continue reading additional packets
|
| + connection_->Read(base::Bind(&U2fHidDevice::OnReadContinuation,
|
| + weak_factory_.GetWeakPtr(), read_message,
|
| + base::Passed(std::move(callback))));
|
| +}
|
| +
|
| +void U2fHidDevice::OnReadContinuation(scoped_refptr<U2fMessage> message,
|
| + U2fHidMessageCallback callback,
|
| + bool success,
|
| + scoped_refptr<net::IOBuffer> buf,
|
| + size_t size) {
|
| + if (!success) {
|
| + std::move(callback).Run(success, nullptr);
|
| + return;
|
| + }
|
| +
|
| + scoped_refptr<net::IOBufferWithSize> buffer(new net::IOBufferWithSize(size));
|
| + memcpy(buffer->data(), buf->data(), size);
|
| + message->AddContinuationPacket(buffer);
|
| + if (message->MessageComplete()) {
|
| + std::move(callback).Run(success, message);
|
| + return;
|
| + }
|
| + connection_->Read(base::Bind(&U2fHidDevice::OnReadContinuation,
|
| + weak_factory_.GetWeakPtr(), message,
|
| + base::Passed(std::move(callback))));
|
| +}
|
| +
|
| +void U2fHidDevice::MessageReceived(const DeviceCallback& callback,
|
| + bool success,
|
| + scoped_refptr<U2fMessage> message) {
|
| + if (!success) {
|
| + state_ = State::DEVICE_ERROR;
|
| + Transition(nullptr, callback);
|
| + return;
|
| + }
|
| + scoped_refptr<U2fApduResponse> response = nullptr;
|
| + if (message)
|
| + response = U2fApduResponse::CreateFromMessage(message->GetMessagePayload());
|
| + state_ = State::IDLE;
|
| + base::WeakPtr<U2fHidDevice> self = weak_factory_.GetWeakPtr();
|
| + callback.Run(success, response);
|
| +
|
| + // Executing |callback| may have freed |this|. Check |self| first.
|
| + if (self && !pending_transactions_.empty()) {
|
| + // If any transactions were queued, process the first one
|
| + scoped_refptr<U2fApduCommand> pending_cmd =
|
| + std::move(pending_transactions_.front().first);
|
| + DeviceCallback pending_cb = pending_transactions_.front().second;
|
| + pending_transactions_.pop_front();
|
| + Transition(pending_cmd, pending_cb);
|
| + }
|
| +}
|
| +
|
| +void U2fHidDevice::TryWink(const WinkCallback& callback) {
|
| + // Only try to wink if device claims support
|
| + if (!(capabilities_ & kWinkCapability) || state_ != State::IDLE) {
|
| + callback.Run();
|
| + return;
|
| + }
|
| +
|
| + scoped_refptr<U2fMessage> wink_message = device::U2fMessage::Create(
|
| + channel_id_, U2fMessage::Type::CMD_WINK, std::vector<uint8_t>());
|
| + WriteMessage(
|
| + wink_message, true,
|
| + base::Bind(&U2fHidDevice::OnWink, weak_factory_.GetWeakPtr(), callback));
|
| +}
|
| +
|
| +void U2fHidDevice::OnWink(const WinkCallback& callback,
|
| + bool success,
|
| + scoped_refptr<U2fMessage> response) {
|
| + callback.Run();
|
| +}
|
| +
|
| +std::string U2fHidDevice::GetId() {
|
| + std::ostringstream id("hid:");
|
| + id << device_info_->device_id();
|
| + return id.str();
|
| +}
|
| +
|
| +// static
|
| +bool U2fHidDevice::IsTestEnabled() {
|
| + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
|
| + return command_line->HasSwitch(switches::kEnableU2fHidTest);
|
| +}
|
| +
|
| +} // namespace device
|
|
|