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