Chromium Code Reviews| 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..67a74559a922194744d82c9562d337fb71753311 |
| --- /dev/null |
| +++ b/device/u2f/u2f_hid_device.cc |
| @@ -0,0 +1,286 @@ |
| +// 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 "base/bind.h" |
| +#include "base/command_line.h" |
| +#include "base/rand_util.h" |
| +#include "device/base/device_client.h" |
| +#include "device/hid/hid_connection.h" |
| + |
| +#include "u2f_apdu_command.h" |
| +#include "u2f_hid_device.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 |
| + switch (state_) { |
| + case State::CONNECTED: |
| + case State::IDLE: |
| + connection_->Close(); |
| + break; |
| + default: |
| + break; |
| + } |
| +} |
| + |
| +void U2fHidDevice::DeviceTransact(scoped_refptr<U2fApduCommand> command, |
| + const DeviceCallback& callback) { |
| + Transition(command, callback); |
| +} |
| + |
| +void U2fHidDevice::Transition(scoped_refptr<U2fApduCommand> command, |
| + const U2fDevice::DeviceCallback& callback) { |
| + switch (state_) { |
|
Reilly Grant (use Gerrit)
2017/03/01 22:48:33
Since connection and channel allocation are asynch
Casey Piper
2017/03/04 02:06:28
Done, any requests that arrive while the device is
|
| + case State::INIT: |
| + Connect(base::Bind(&U2fHidDevice::OnConnect, weak_factory_.GetWeakPtr(), |
| + command, callback)); |
| + break; |
| + case State::CONNECTED: |
| + AllocateChannel(command, callback); |
| + break; |
| + case State::IDLE: { |
| + 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::DEVICE_ERROR: |
| + default: |
| + callback.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 != nullptr) { |
|
Reilly Grant (use Gerrit)
2017/03/01 22:48:33
if (connection)
Casey Piper
2017/03/04 02:06:28
Done.
|
| + connection_ = connection; |
| + state_ = State::CONNECTED; |
| + } else { |
| + state_ = State::DEVICE_ERROR; |
| + } |
| + Transition(command, callback); |
| +} |
| + |
| +void U2fHidDevice::AllocateChannel(scoped_refptr<U2fApduCommand> command, |
| + const DeviceCallback& callback) { |
| + uint8_t rand[8]; |
| + // Send random nonce to device to verify received message |
|
Reilly Grant (use Gerrit)
2017/03/01 22:48:33
Please add a comment documenting that this nonce d
Casey Piper
2017/03/04 02:06:28
Done.
|
| + base::RandBytes(rand, 8); |
| + std::vector<uint8_t> nonce(rand, std::end(rand)); |
|
Reilly Grant (use Gerrit)
2017/03/01 22:48:33
You can do this without copying:
std::vector<uint
Casey Piper
2017/03/04 02:06:28
Acknowledged.
|
| + 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) { |
| + // 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) { |
| + callback.Run(false, nullptr); |
| + return; |
| + } |
| + |
| + std::vector<uint8_t> received_nonce(std::begin(payload), |
| + std::begin(payload) + 8); |
| + if (nonce != received_nonce) { |
| + callback.Run(false, nullptr); |
| + 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, |
| + const U2fHidMessageCallback& callback) { |
| + if (connection_ == nullptr || message == nullptr || |
|
Reilly Grant (use Gerrit)
2017/03/01 22:48:32
if (!connection_ || !message ||
Casey Piper
2017/03/04 02:06:28
Done.
|
| + message->NumPackets() == 0) { |
| + 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, callback)); |
| +} |
| + |
| +void U2fHidDevice::PacketWritten(scoped_refptr<U2fMessage> message, |
| + bool response_expected, |
| + const U2fHidMessageCallback& callback, |
| + bool success) { |
| + if (success && message->NumPackets() > 0) { |
| + scoped_refptr<net::IOBufferWithSize> buffer = message->PopNextPacket(); |
|
Reilly Grant (use Gerrit)
2017/03/01 22:48:33
Can you replace these statements with:
WriteMessa
Casey Piper
2017/03/04 02:06:28
Done.
|
| + connection_->Write( |
| + buffer, buffer->size(), |
| + base::Bind(&U2fHidDevice::PacketWritten, weak_factory_.GetWeakPtr(), |
| + message, response_expected, callback)); |
| + } else if (success && response_expected) { |
| + ReadMessage(callback); |
| + } else { |
| + callback.Run(success, nullptr); |
| + } |
| +} |
| + |
| +void U2fHidDevice::ReadMessage(const U2fHidMessageCallback& callback) { |
| + if (connection_ == nullptr) { |
|
Reilly Grant (use Gerrit)
2017/03/01 22:48:32
if (!connection_)
Casey Piper
2017/03/04 02:06:28
Done.
|
| + callback.Run(false, nullptr); |
| + return; |
| + } |
| + |
| + connection_->Read( |
| + base::Bind(&U2fHidDevice::OnRead, weak_factory_.GetWeakPtr(), callback)); |
| +} |
| + |
| +void U2fHidDevice::OnRead(const U2fHidMessageCallback& callback, |
| + bool success, |
| + scoped_refptr<net::IOBuffer> buf, |
| + size_t size) { |
| + if (!success) { |
| + 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 == nullptr) { |
|
Reilly Grant (use Gerrit)
2017/03/01 22:48:32
if (!read_message)
Casey Piper
2017/03/04 02:06:28
Done.
|
| + 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(), callback)); |
| + return; |
| + } |
| + |
| + if (read_message->MessageComplete()) { |
| + callback.Run(success, read_message); |
| + return; |
| + } |
| + |
| + // Continue reading additional packets |
| + connection_->Read(base::Bind(&U2fHidDevice::OnReadContinuation, |
| + weak_factory_.GetWeakPtr(), read_message, |
| + callback)); |
| +} |
| + |
| +void U2fHidDevice::OnReadContinuation(scoped_refptr<U2fMessage> message, |
| + const U2fHidMessageCallback& callback, |
| + bool success, |
| + scoped_refptr<net::IOBuffer> buf, |
| + size_t size) { |
| + if (!success) { |
| + 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()) { |
| + callback.Run(success, message); |
| + return; |
| + } |
| + connection_->Read(base::Bind(&U2fHidDevice::OnReadContinuation, |
| + weak_factory_.GetWeakPtr(), message, callback)); |
| +} |
| + |
| +void U2fHidDevice::MessageReceived(const DeviceCallback& callback, |
| + bool success, |
| + scoped_refptr<U2fMessage> message) { |
| + scoped_refptr<U2fApduResponse> response = nullptr; |
| + if (success && message != nullptr) |
|
Reilly Grant (use Gerrit)
2017/03/01 22:48:32
if (success && message)
Casey Piper
2017/03/04 02:06:28
Done.
|
| + response = U2fApduResponse::CreateFromMessage(message->GetMessagePayload()); |
| + if (!success) |
| + state_ = State::DEVICE_ERROR; |
| + callback.Run(success, response); |
| +} |
| + |
| +void U2fHidDevice::TryWink(const WinkCallback& callback) { |
| + // Only try to wink if device claims support |
| + if (!(capabilities_ | kWinkCapability) || state_ != State::IDLE) { |
|
Reilly Grant (use Gerrit)
2017/03/01 22:48:32
capabilities_ & kWinkCapability?
Casey Piper
2017/03/04 02:06:28
Ah, yeah. Done.
|
| + 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::Id() { |
| + std::ostringstream id("hid:"); |
| + id << device_info_->device_id(); |
| + return id.str(); |
|
Reilly Grant (use Gerrit)
2017/03/01 22:48:32
return base::StringPrintf("hid:%d", device_info_->
Casey Piper
2017/03/04 02:06:28
StringPrintf only seems to work when device_id is
Reilly Grant (use Gerrit)
2017/03/06 20:49:23
Curses, I suppose I can only blame myself for that
|
| +} |
| + |
| +// static |
| +bool U2fHidDevice::IsTestEnabled() { |
| + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| + return command_line->HasSwitch(switches::kEnableU2fHidTest); |
| +} |
| + |
| +} // namespace device |