Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "u2f_hid_device.h" | |
| 6 | |
| 7 #include "base/bind.h" | |
| 8 #include "base/bind_helpers.h" | |
| 9 #include "base/command_line.h" | |
| 10 #include "crypto/random.h" | |
| 11 #include "device/base/device_client.h" | |
| 12 #include "device/hid/hid_connection.h" | |
| 13 #include "u2f_apdu_command.h" | |
| 14 #include "u2f_message.h" | |
| 15 | |
| 16 namespace device { | |
| 17 | |
| 18 namespace switches { | |
| 19 static constexpr char kEnableU2fHidTest[] = "enable-u2f-hid-tests"; | |
| 20 } // namespace switches | |
| 21 | |
| 22 U2fHidDevice::U2fHidDevice(scoped_refptr<HidDeviceInfo> device_info) | |
| 23 : U2fDevice(), | |
| 24 state_(State::INIT), | |
| 25 device_info_(device_info), | |
| 26 weak_factory_(this) { | |
| 27 channel_id_ = kBroadcastChannel; | |
| 28 } | |
| 29 | |
| 30 U2fHidDevice::~U2fHidDevice() { | |
| 31 // Cleanup connection | |
| 32 switch (state_) { | |
| 33 case State::CONNECTED: | |
| 34 case State::IDLE: | |
| 35 connection_->Close(); | |
| 36 break; | |
| 37 default: | |
| 38 break; | |
| 39 } | |
| 40 } | |
| 41 | |
| 42 void U2fHidDevice::DeviceTransact(scoped_refptr<U2fApduCommand> command, | |
| 43 const DeviceCallback& callback) { | |
| 44 Transition(command, callback); | |
| 45 } | |
| 46 | |
| 47 void U2fHidDevice::Transition(scoped_refptr<U2fApduCommand> command, | |
| 48 const DeviceCallback& callback) { | |
| 49 switch (state_) { | |
| 50 case State::INIT: | |
| 51 state_ = State::BUSY; | |
| 52 Connect(base::Bind(&U2fHidDevice::OnConnect, weak_factory_.GetWeakPtr(), | |
| 53 command, callback)); | |
| 54 break; | |
| 55 case State::CONNECTED: | |
| 56 state_ = State::BUSY; | |
| 57 AllocateChannel(command, callback); | |
| 58 break; | |
| 59 case State::IDLE: { | |
| 60 state_ = State::BUSY; | |
| 61 scoped_refptr<U2fMessage> msg = U2fMessage::Create( | |
| 62 channel_id_, U2fMessage::Type::CMD_MSG, command->GetEncodedCommand()); | |
| 63 WriteMessage(msg, true, | |
| 64 base::Bind(&U2fHidDevice::MessageReceived, | |
| 65 weak_factory_.GetWeakPtr(), callback)); | |
| 66 break; | |
| 67 } | |
| 68 case State::BUSY: | |
| 69 pending_transactions_.push_back({std::move(command), callback}); | |
| 70 break; | |
| 71 case State::DEVICE_ERROR: | |
| 72 default: | |
| 73 callback.Run(false, nullptr); | |
| 74 break; | |
| 75 } | |
| 76 } | |
| 77 | |
| 78 void U2fHidDevice::Connect(const HidService::ConnectCallback& callback) { | |
| 79 HidService* hid_service = DeviceClient::Get()->GetHidService(); | |
| 80 | |
| 81 hid_service->Connect(device_info_->device_id(), callback); | |
| 82 } | |
| 83 | |
| 84 void U2fHidDevice::OnConnect(scoped_refptr<U2fApduCommand> command, | |
| 85 const DeviceCallback& callback, | |
| 86 scoped_refptr<HidConnection> connection) { | |
| 87 if (connection) { | |
| 88 connection_ = connection; | |
| 89 state_ = State::CONNECTED; | |
| 90 } else { | |
| 91 state_ = State::DEVICE_ERROR; | |
| 92 } | |
| 93 Transition(command, callback); | |
| 94 } | |
| 95 | |
| 96 void U2fHidDevice::AllocateChannel(scoped_refptr<U2fApduCommand> command, | |
| 97 const DeviceCallback& callback) { | |
| 98 // Send random nonce to device to verify received message | |
| 99 std::vector<uint8_t> nonce(8); | |
| 100 crypto::RandBytes(nonce.data(), nonce.size()); | |
| 101 scoped_refptr<U2fMessage> message = | |
| 102 U2fMessage::Create(channel_id_, U2fMessage::Type::CMD_INIT, nonce); | |
| 103 | |
| 104 WriteMessage( | |
| 105 message, true, | |
| 106 base::Bind(&U2fHidDevice::OnAllocateChannel, weak_factory_.GetWeakPtr(), | |
| 107 nonce, command, callback)); | |
| 108 } | |
| 109 | |
| 110 void U2fHidDevice::OnAllocateChannel(std::vector<uint8_t> nonce, | |
| 111 scoped_refptr<U2fApduCommand> command, | |
| 112 const DeviceCallback& callback, | |
| 113 bool success, | |
| 114 scoped_refptr<U2fMessage> message) { | |
| 115 // Channel allocation response is defined as: | |
| 116 // 0: 8 byte nonce | |
| 117 // 8: 4 byte channel id | |
| 118 // 12: Protocol version id | |
| 119 // 13: Major device version | |
| 120 // 14: Minor device version | |
| 121 // 15: Build device version | |
| 122 // 16: Capabilities | |
| 123 std::vector<uint8_t> payload = message->GetMessagePayload(); | |
| 124 if (payload.size() != 17) { | |
| 125 callback.Run(false, nullptr); | |
| 126 return; | |
| 127 } | |
| 128 | |
| 129 std::vector<uint8_t> received_nonce(std::begin(payload), | |
| 130 std::begin(payload) + 8); | |
| 131 if (nonce != received_nonce) { | |
| 132 callback.Run(false, nullptr); | |
| 133 return; | |
| 134 } | |
| 135 | |
| 136 size_t index = 8; | |
| 137 channel_id_ = payload[index++] << 24; | |
| 138 channel_id_ |= payload[index++] << 16; | |
| 139 channel_id_ |= payload[index++] << 8; | |
| 140 channel_id_ |= payload[index++]; | |
| 141 capabilities_ = payload[16]; | |
| 142 | |
| 143 state_ = State::IDLE; | |
| 144 Transition(command, callback); | |
| 145 } | |
| 146 | |
| 147 void U2fHidDevice::WriteMessage(scoped_refptr<U2fMessage> message, | |
| 148 bool response_expected, | |
| 149 U2fHidMessageCallback callback) { | |
| 150 if (!connection_ || !message || message->NumPackets() == 0) { | |
| 151 std::move(callback).Run(false, nullptr); | |
| 152 return; | |
| 153 } | |
| 154 | |
| 155 scoped_refptr<net::IOBufferWithSize> buffer = message->PopNextPacket(); | |
| 156 | |
| 157 connection_->Write( | |
| 158 buffer, buffer->size(), | |
| 159 base::Bind(&U2fHidDevice::PacketWritten, weak_factory_.GetWeakPtr(), | |
| 160 message, true, base::Passed(std::move(callback)))); | |
| 161 } | |
| 162 | |
| 163 void U2fHidDevice::PacketWritten(scoped_refptr<U2fMessage> message, | |
| 164 bool response_expected, | |
| 165 U2fHidMessageCallback callback, | |
| 166 bool success) { | |
| 167 if (success && message->NumPackets() > 0) { | |
| 168 WriteMessage(message, response_expected, std::move(callback)); | |
| 169 } else if (success && response_expected) { | |
| 170 ReadMessage(std::move(callback)); | |
| 171 } else { | |
| 172 std::move(callback).Run(success, nullptr); | |
| 173 } | |
| 174 } | |
| 175 | |
| 176 void U2fHidDevice::ReadMessage(U2fHidMessageCallback callback) { | |
| 177 if (!connection_) { | |
| 178 std::move(callback).Run(false, nullptr); | |
| 179 return; | |
| 180 } | |
| 181 | |
| 182 connection_->Read(base::Bind(&U2fHidDevice::OnRead, | |
| 183 weak_factory_.GetWeakPtr(), | |
| 184 base::Passed(std::move(callback)))); | |
| 185 } | |
| 186 | |
| 187 void U2fHidDevice::OnRead(U2fHidMessageCallback callback, | |
| 188 bool success, | |
| 189 scoped_refptr<net::IOBuffer> buf, | |
| 190 size_t size) { | |
| 191 if (!success) { | |
| 192 std::move(callback).Run(success, nullptr); | |
| 193 return; | |
| 194 } | |
| 195 | |
| 196 scoped_refptr<net::IOBufferWithSize> buffer(new net::IOBufferWithSize(size)); | |
| 197 memcpy(buffer->data(), buf->data(), size); | |
| 198 scoped_refptr<U2fMessage> read_message = | |
| 199 U2fMessage::CreateFromSerializedData(buffer); | |
| 200 | |
| 201 if (!read_message) { | |
| 202 std::move(callback).Run(false, nullptr); | |
| 203 return; | |
| 204 } | |
| 205 | |
| 206 // Received a message from a different channel, so try again | |
| 207 if (channel_id_ != read_message->channel_id()) { | |
| 208 connection_->Read(base::Bind(&U2fHidDevice::OnRead, | |
| 209 weak_factory_.GetWeakPtr(), | |
| 210 base::Passed(std::move(callback)))); | |
| 211 return; | |
| 212 } | |
| 213 | |
| 214 if (read_message->MessageComplete()) { | |
| 215 std::move(callback).Run(success, read_message); | |
| 216 return; | |
| 217 } | |
| 218 | |
| 219 // Continue reading additional packets | |
| 220 connection_->Read(base::Bind(&U2fHidDevice::OnReadContinuation, | |
| 221 weak_factory_.GetWeakPtr(), read_message, | |
| 222 base::Passed(std::move(callback)))); | |
| 223 } | |
| 224 | |
| 225 void U2fHidDevice::OnReadContinuation(scoped_refptr<U2fMessage> message, | |
| 226 U2fHidMessageCallback callback, | |
| 227 bool success, | |
| 228 scoped_refptr<net::IOBuffer> buf, | |
| 229 size_t size) { | |
| 230 if (!success) { | |
| 231 std::move(callback).Run(success, nullptr); | |
| 232 return; | |
| 233 } | |
| 234 | |
| 235 scoped_refptr<net::IOBufferWithSize> buffer(new net::IOBufferWithSize(size)); | |
| 236 memcpy(buffer->data(), buf->data(), size); | |
| 237 message->AddContinuationPacket(buffer); | |
| 238 if (message->MessageComplete()) { | |
| 239 std::move(callback).Run(success, message); | |
| 240 return; | |
| 241 } | |
| 242 connection_->Read(base::Bind(&U2fHidDevice::OnReadContinuation, | |
| 243 weak_factory_.GetWeakPtr(), message, | |
| 244 base::Passed(std::move(callback)))); | |
| 245 } | |
| 246 | |
| 247 void U2fHidDevice::MessageReceived(const DeviceCallback& callback, | |
| 248 bool success, | |
| 249 scoped_refptr<U2fMessage> message) { | |
| 250 scoped_refptr<U2fApduResponse> response = nullptr; | |
| 251 if (success && message) | |
| 252 response = U2fApduResponse::CreateFromMessage(message->GetMessagePayload()); | |
| 253 if (!success) | |
| 254 state_ = State::DEVICE_ERROR; | |
| 255 else | |
| 256 state_ = State::IDLE; | |
|
Reilly Grant (use Gerrit)
2017/03/06 20:49:23
state_ = success ? State::IDLE : State::DEVICE_ERR
Casey Piper
2017/03/07 00:53:28
Done.
| |
| 257 callback.Run(success, response); | |
| 258 | |
| 259 // If any transactions were queued, start processing them | |
| 260 if (!pending_transactions_.empty()) { | |
| 261 scoped_refptr<U2fApduCommand> pending_cmd = | |
| 262 std::move((*pending_transactions_.begin()).first); | |
|
Reilly Grant (use Gerrit)
2017/03/06 20:49:23
pending_transactions_.front().first
Casey Piper
2017/03/07 00:53:27
Done.
| |
| 263 DeviceCallback pending_cb = (*pending_transactions_.begin()).second; | |
|
Reilly Grant (use Gerrit)
2017/03/06 20:49:23
pending_transactions_.front().second
Casey Piper
2017/03/07 00:53:28
Done.
| |
| 264 pending_transactions_.erase(pending_transactions_.begin()); | |
|
Reilly Grant (use Gerrit)
2017/03/06 20:49:23
I would use an std::list so you can use pop_front(
Casey Piper
2017/03/07 00:53:27
Done.
| |
| 265 Transition(pending_cmd, pending_cb); | |
| 266 } | |
| 267 } | |
| 268 | |
| 269 void U2fHidDevice::TryWink(const WinkCallback& callback) { | |
| 270 // Only try to wink if device claims support | |
| 271 if (!(capabilities_ & kWinkCapability) || state_ != State::IDLE) { | |
| 272 callback.Run(); | |
| 273 return; | |
| 274 } | |
| 275 | |
| 276 scoped_refptr<U2fMessage> wink_message = device::U2fMessage::Create( | |
| 277 channel_id_, U2fMessage::Type::CMD_WINK, std::vector<uint8_t>()); | |
| 278 WriteMessage( | |
| 279 wink_message, true, | |
| 280 base::Bind(&U2fHidDevice::OnWink, weak_factory_.GetWeakPtr(), callback)); | |
| 281 } | |
| 282 | |
| 283 void U2fHidDevice::OnWink(const WinkCallback& callback, | |
| 284 bool success, | |
| 285 scoped_refptr<U2fMessage> response) { | |
| 286 callback.Run(); | |
| 287 } | |
| 288 | |
| 289 std::string U2fHidDevice::Id() { | |
| 290 std::ostringstream id("hid:"); | |
| 291 id << device_info_->device_id(); | |
| 292 return id.str(); | |
| 293 } | |
| 294 | |
| 295 // static | |
| 296 bool U2fHidDevice::IsTestEnabled() { | |
| 297 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); | |
| 298 return command_line->HasSwitch(switches::kEnableU2fHidTest); | |
| 299 } | |
| 300 | |
| 301 } // namespace device | |
| OLD | NEW |