blob: ebdc62fa1d556ef205faafce41fff23bdbf3c2fd [file] [log] [blame]
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +02001// Copyright 2017 The Chromium OS 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 <algorithm>
6#include <memory>
7#include <utility>
8
9#include <base/bind.h>
10#include <base/callback.h>
Louis Collard10ac9e92019-02-23 18:34:45 +080011#include <base/optional.h>
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +020012#include <base/strings/string_number_conversions.h>
13#include <base/sys_byteorder.h>
14#include <base/timer/timer.h>
Louis Collard10ac9e92019-02-23 18:34:45 +080015#include <openssl/bn.h>
16#include <openssl/ecdsa.h>
17#include <trunks/cr50_headers/u2f.h>
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +020018
Louis Collard10ac9e92019-02-23 18:34:45 +080019#include "u2fd/u2f_adpu.h"
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +020020#include "u2fd/u2fhid.h"
Louis Collardf59aa942019-02-25 17:50:14 +080021#include "u2fd/user_state.h"
Louis Collard10ac9e92019-02-23 18:34:45 +080022#include "u2fd/util.h"
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +020023
24namespace {
25
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +020026// Size of the payload for an INIT U2F HID report.
27constexpr size_t kInitReportPayloadSize = 57;
28// Size of the payload for a Continuation U2F HID report.
29constexpr size_t kContReportPayloadSize = 59;
30
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +020031constexpr uint8_t kInterfaceVersion = 2;
32
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +020033constexpr int kU2fHidTimeoutMs = 500;
34
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +020035// Maximum duration one can keep the channel lock as specified by the U2FHID
36// specification
37constexpr int kMaxLockDurationSeconds = 10;
38
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +020039// HID report descriptor for U2F interface.
40constexpr uint8_t kU2fReportDesc[] = {
41 0x06, 0xD0, 0xF1, /* Usage Page (FIDO Alliance), FIDO_USAGE_PAGE */
42 0x09, 0x01, /* Usage (U2F HID Auth. Device) FIDO_USAGE_U2FHID */
43 0xA1, 0x01, /* Collection (Application), HID_APPLICATION */
44 0x09, 0x20, /* Usage (Input Report Data), FIDO_USAGE_DATA_IN */
45 0x15, 0x00, /* Logical Minimum (0) */
46 0x26, 0xFF, 0x00, /* Logical Maximum (255) */
47 0x75, 0x08, /* Report Size (8) */
48 0x95, 0x40, /* Report Count (64), HID_INPUT_REPORT_BYTES */
49 0x81, 0x02, /* Input (Data, Var, Abs), Usage */
50 0x09, 0x21, /* Usage (Output Report Data), FIDO_USAGE_DATA_OUT */
51 0x15, 0x00, /* Logical Minimum (0) */
52 0x26, 0xFF, 0x00, /* Logical Maximum (255) */
53 0x75, 0x08, /* Report Size (8) */
54 0x95, 0x40, /* Report Count (64), HID_OUTPUT_REPORT_BYTES */
55 0x91, 0x02, /* Output (Data, Var, Abs), Usage */
56 0xC0 /* End Collection */
57};
58
59} // namespace
60
61namespace u2f {
62
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +020063class U2fHid::HidPacket {
64 public:
65 explicit HidPacket(const std::string& report);
66
67 bool IsValidFrame() const { return valid_; }
68
69 bool IsInitFrame() const { return (tcs_ & kFrameTypeMask) == kFrameTypeInit; }
70
71 uint32_t ChannelId() const { return cid_; }
72
73 U2fHid::U2fHidCommand Command() const {
74 return static_cast<U2fHidCommand>(tcs_ & ~kFrameTypeMask);
75 }
76
77 uint8_t SeqNumber() const { return tcs_ & ~kFrameTypeMask; }
78
79 int PayloadIndex() const { return IsInitFrame() ? 8 : 6; }
80
81 size_t MessagePayloadSize() const { return bcnt_; }
82
83 private:
84 bool valid_;
85 uint32_t cid_; // Channel Identifier
86 uint8_t tcs_; // type and command or sequence number
87 uint16_t bcnt_; // payload length as defined by U2fHID specification
88};
89
90U2fHid::HidPacket::HidPacket(const std::string& report)
91 : valid_(false), cid_(0), tcs_(0), bcnt_(0) {
92 // the report is prefixed by the report ID (we skip it below).
93 if (report.size() != kU2fReportSize + 1) /* Invalid U2FHID report */
94 return;
95
96 // U2FHID frame bytes parsing.
97 // As defined in the "FIDO U2F HID Protocol Specification":
98 // An initialization packet is defined as
99 //
100 // Offset Length Mnemonic Description
101 // 0 4 CID Channel identifier
102 // 4 1 CMD Command identifier (bit 7 always set)
103 // 5 1 BCNTH High part of payload length
104 // 6 1 BCNTL Low part of payload length
105 // 7 (s - 7) DATA Payload data (s is the fixed packet size)
106 // The command byte has always the highest bit set to distinguish it
107 // from a continuation packet, which is described below.
108 //
109 // A continuation packet is defined as
110 //
111 // Offset Length Mnemonic Description
112 // 0 4 CID Channel identifier
113 // 4 1 SEQ Packet sequence 0x00..0x7f (bit 7 always cleared)
114 // 5 (s - 5) DATA Payload data (s is the fixed packet size)
115 // With this approach, a message with a payload less or equal to (s - 7)
116 // may be sent as one packet. A larger message is then divided into one or
117 // more continuation packets, starting with sequence number 0 which then
118 // increments by one to a maximum of 127.
119
120 // the CID word is not aligned
121 memcpy(&cid_, &report[1], sizeof(cid_));
122 tcs_ = report[5];
123
124 uint16_t raw_count;
125 memcpy(&raw_count, &report[6], sizeof(raw_count));
126 bcnt_ = base::NetToHost16(raw_count);
127
128 valid_ = true;
129}
130
131class U2fHid::HidMessage {
132 public:
133 HidMessage(U2fHidCommand cmd, uint32_t cid) : cid_(cid), cmd_(cmd) {}
134
135 // Appends |bytes| to the message payload.
136 void AddPayload(const std::string& bytes);
137
138 // Appends the single |byte| to the message payload.
139 void AddByte(uint8_t byte);
140
141 // Fills an HID report with the part of the message starting at |offset|.
142 // Returns the offset of the remaining unused content in the message.
143 int BuildReport(int offset, std::string* report_out);
144
145 private:
146 uint32_t cid_;
147 U2fHidCommand cmd_;
148 std::string payload_;
149};
150
151void U2fHid::HidMessage::AddPayload(const std::string& bytes) {
152 payload_ += bytes;
153}
154
155void U2fHid::HidMessage::AddByte(uint8_t byte) {
156 payload_.push_back(byte);
157}
158
159int U2fHid::HidMessage::BuildReport(int offset, std::string* report_out) {
160 size_t data_size;
161
162 // Serialize one chunk of the message in a 64-byte HID report
163 // (see the HID report structure in HidPacket constructor)
164 report_out->assign(
165 std::string(reinterpret_cast<char*>(&cid_), sizeof(uint32_t)));
166 if (offset == 0) { // INIT message
167 uint16_t bcnt = payload_.size();
168 report_out->push_back(static_cast<uint8_t>(cmd_) | kFrameTypeInit);
169 report_out->push_back(bcnt >> 8);
170 report_out->push_back(bcnt & 0xff);
171 data_size = kInitReportPayloadSize;
172 } else { // CONT message
173 // Insert sequence number.
174 report_out->push_back((offset - kInitReportPayloadSize) /
175 kContReportPayloadSize);
176 data_size = kContReportPayloadSize;
177 }
178 data_size = std::min(data_size, payload_.size() - offset);
179 *report_out += payload_.substr(offset, data_size);
180 // Ensure the report is 64-B long
181 report_out->insert(report_out->end(), kU2fReportSize - report_out->size(), 0);
182 offset += data_size;
183
184 VLOG(2) << "TX RPT ["
185 << base::HexEncode(report_out->data(), report_out->size()) << "]";
186
187 return offset != payload_.size() ? offset : 0;
188}
189
190struct U2fHid::Transaction {
Vincent Palatin167f0fb2017-08-11 22:28:12 +0200191 uint32_t cid = 0;
192 U2fHidCommand cmd = U2fHidCommand::kError;
193 size_t total_size = 0;
194 int seq = 0;
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +0200195 std::string payload;
196 base::OneShotTimer timeout;
197};
198
199U2fHid::U2fHid(std::unique_ptr<HidInterface> hid,
Louis Collardca8d2712019-08-26 17:57:58 +0800200 U2fMessageHandler* msg_handler)
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +0200201 : hid_(std::move(hid)),
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +0200202 free_cid_(1),
Vincent Palatin828bfe92018-04-05 15:14:44 +0200203 locked_cid_(0),
Louis Collardca8d2712019-08-26 17:57:58 +0800204 msg_handler_(msg_handler) {
Ben Chan921e9752017-09-29 00:27:10 -0700205 transaction_ = std::make_unique<Transaction>();
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +0200206 hid_->SetOutputReportHandler(
207 base::Bind(&U2fHid::ProcessReport, base::Unretained(this)));
208}
209
210U2fHid::~U2fHid() = default;
211
212bool U2fHid::Init() {
213 return hid_->Init(kInterfaceVersion,
214 std::string(reinterpret_cast<const char*>(kU2fReportDesc),
215 sizeof(kU2fReportDesc)));
216}
217
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +0200218void U2fHid::ReturnError(U2fHidError errcode, uint32_t cid, bool clear) {
219 HidMessage msg(U2fHidCommand::kError, cid);
220
221 msg.AddByte(static_cast<uint8_t>(errcode));
222 VLOG(1) << "ERROR/" << std::hex << static_cast<int>(errcode)
223 << " CID:" << std::hex << cid;
224 if (clear)
Ben Chan921e9752017-09-29 00:27:10 -0700225 transaction_ = std::make_unique<Transaction>();
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +0200226
227 std::string report;
228 msg.BuildReport(0, &report);
229 hid_->SendReport(report);
230}
231
232void U2fHid::TransactionTimeout() {
233 ReturnError(U2fHidError::kMsgTimeout, transaction_->cid, true);
234}
235
236void U2fHid::LockTimeout() {
237 if (locked_cid_)
238 LOG(WARNING) << "Cancelled lock CID:" << std::hex << locked_cid_;
239 locked_cid_ = 0;
240}
241
242void U2fHid::ReturnResponse(const std::string& resp) {
243 HidMessage msg(transaction_->cmd, transaction_->cid);
244 int offset = 0;
245
246 msg.AddPayload(resp);
247 // Send all the chunks of the message (split in 64-B HID reports)
248 do {
249 std::string report;
250 offset = msg.BuildReport(offset, &report);
251 hid_->SendReport(report);
252 } while (offset);
253}
254
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +0200255void U2fHid::CmdInit(uint32_t cid, const std::string& payload) {
256 HidMessage msg(U2fHidCommand::kInit, cid);
257
258 if (payload.size() != kInitNonceSize) {
259 VLOG(1) << "Payload size " << payload.size();
260 ReturnError(U2fHidError::kInvalidLen, cid, false);
261 return;
262 }
263
264 VLOG(1) << "INIT CID:" << std::hex << cid << " NONCE "
265 << base::HexEncode(payload.data(), payload.size());
266
267 if (cid == kCidBroadcast) { // Allocate Channel ID
268 cid = free_cid_++;
269 // Roll-over if needed
270 if (free_cid_ == kCidBroadcast)
271 free_cid_ = 1;
272 }
273
274 // Keep the nonce in the first 8 bytes
275 msg.AddPayload(payload);
276
277 std::string serial_cid(reinterpret_cast<char*>(&cid), sizeof(uint32_t));
278 msg.AddPayload(serial_cid);
279
280 // Append the versions : interface / major / minor / build
281 msg.AddByte(kInterfaceVersion);
282 msg.AddByte(0);
283 msg.AddByte(0);
284 msg.AddByte(0);
285 // Append Capability flags
Louis Collarde41caa52020-02-12 13:54:04 +0800286 msg.AddByte(kCapFlagLock);
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +0200287
288 std::string report;
289 msg.BuildReport(0, &report);
290 hid_->SendReport(report);
291}
292
293int U2fHid::CmdPing(std::string* resp) {
294 VLOG(1) << "PING len " << transaction_->total_size;
295
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +0200296 // send back the same content
297 *resp = transaction_->payload.substr(0, transaction_->total_size);
298 return transaction_->total_size;
299}
300
301int U2fHid::CmdLock(std::string* resp) {
302 int duration = transaction_->payload[0];
303
304 VLOG(1) << "LOCK " << duration << "s CID:" << std::hex << transaction_->cid;
305
306 if (duration > kMaxLockDurationSeconds) {
307 ReturnError(U2fHidError::kInvalidPar, transaction_->cid, true);
308 return -EINVAL;
309 }
310
311 if (!duration) {
312 lock_timeout_.Stop();
313 locked_cid_ = 0;
314 } else {
315 locked_cid_ = transaction_->cid;
316 lock_timeout_.Start(
Tom Hughes0f7203b2020-08-24 18:29:15 -0700317 FROM_HERE, base::TimeDelta::FromSeconds(duration),
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +0200318 base::Bind(&U2fHid::LockTimeout, base::Unretained(this)));
319 }
320 return 0;
321}
322
Vincent Palatin828bfe92018-04-05 15:14:44 +0200323int U2fHid::CmdSysInfo(std::string* resp) {
Louis Collardb4514772019-08-15 16:13:30 +0800324 LOG(WARNING) << "Received unsupported SysInfo command";
325 ReturnError(U2fHidError::kInvalidCmd, transaction_->cid, true);
326 return -EINVAL;
Vincent Palatin828bfe92018-04-05 15:14:44 +0200327}
328
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +0200329int U2fHid::CmdMsg(std::string* resp) {
Louis Collardca8d2712019-08-26 17:57:58 +0800330 U2fResponseAdpu r = msg_handler_->ProcessMsg(transaction_->payload);
Louis Collard10ac9e92019-02-23 18:34:45 +0800331
Louis Collardca8d2712019-08-26 17:57:58 +0800332 r.ToString(resp);
Louis Collard10ac9e92019-02-23 18:34:45 +0800333
334 return 0;
335}
336
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +0200337void U2fHid::ExecuteCmd() {
338 int rc;
339 std::string resp;
340
341 transaction_->timeout.Stop();
342 switch (transaction_->cmd) {
343 case U2fHidCommand::kMsg:
344 rc = CmdMsg(&resp);
345 break;
346 case U2fHidCommand::kPing:
347 rc = CmdPing(&resp);
348 break;
349 case U2fHidCommand::kLock:
350 rc = CmdLock(&resp);
351 break;
Vincent Palatin828bfe92018-04-05 15:14:44 +0200352 case U2fHidCommand::kVendorSysInfo:
353 rc = CmdSysInfo(&resp);
354 break;
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +0200355 default:
356 LOG(WARNING) << "Unknown command " << std::hex
357 << static_cast<int>(transaction_->cmd);
358 ReturnError(U2fHidError::kInvalidCmd, transaction_->cid, true);
359 return;
360 }
361
362 if (rc >= 0)
363 ReturnResponse(resp);
364
365 // we are done with this transaction
Ben Chan921e9752017-09-29 00:27:10 -0700366 transaction_ = std::make_unique<Transaction>();
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +0200367}
368
369void U2fHid::ProcessReport(const std::string& report) {
370 HidPacket pkt(report);
371
372 VLOG(2) << "RX RPT/" << report.size() << " ["
373 << base::HexEncode(report.data(), report.size()) << "]";
374 if (!pkt.IsValidFrame())
375 return; // Invalid report
376
377 // Check frame validity
378 if (pkt.ChannelId() == 0) {
379 VLOG(1) << "No frame should use channel 0";
Tom Hughes0f7203b2020-08-24 18:29:15 -0700380 ReturnError(U2fHidError::kInvalidCid, pkt.ChannelId(),
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +0200381 pkt.ChannelId() == transaction_->cid);
382 return;
383 }
384
385 if (pkt.IsInitFrame() && pkt.Command() == U2fHidCommand::kInit) {
386 if (pkt.ChannelId() == transaction_->cid) {
387 // Abort an ongoing multi-packet transaction
388 VLOG(1) << "Transaction cancelled on CID:" << std::hex << pkt.ChannelId();
Ben Chan921e9752017-09-29 00:27:10 -0700389 transaction_ = std::make_unique<Transaction>();
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +0200390 }
391 // special case: INIT should not interrupt other commands
392 CmdInit(pkt.ChannelId(), report.substr(pkt.PayloadIndex(), kInitNonceSize));
393 return;
394 }
395 // not an INIT command from here
396
397 if (pkt.IsInitFrame()) { // INIT frame type (not the INIT command)
398 if (pkt.ChannelId() == kCidBroadcast) {
399 VLOG(1) << "INIT command not on broadcast CID:" << std::hex
400 << pkt.ChannelId();
401 ReturnError(U2fHidError::kInvalidCid, pkt.ChannelId(), false);
402 return;
403 }
404 if (locked_cid_ && pkt.ChannelId() != locked_cid_) {
405 // somebody else has the lock
406 VLOG(1) << "channel locked by CID:" << std::hex << locked_cid_;
407 ReturnError(U2fHidError::kChannelBusy, pkt.ChannelId(), false);
408 return;
409 }
410 if (transaction_->cid && (pkt.ChannelId() != transaction_->cid)) {
411 VLOG(1) << "channel used by CID:" << std::hex << transaction_->cid;
412 ReturnError(U2fHidError::kChannelBusy, pkt.ChannelId(), false);
413 return;
414 }
415 if (transaction_->cid) {
416 VLOG(1) << "CONT frame expected";
417 ReturnError(U2fHidError::kInvalidSeq, pkt.ChannelId(), true);
418 return;
419 }
420 if (pkt.MessagePayloadSize() > kMaxPayloadSize) {
421 VLOG(1) << "Invalid length " << pkt.MessagePayloadSize();
422 ReturnError(U2fHidError::kInvalidLen, pkt.ChannelId(), true);
423 return;
424 }
425
426 transaction_->timeout.Start(
Tom Hughes0f7203b2020-08-24 18:29:15 -0700427 FROM_HERE, base::TimeDelta::FromMilliseconds(kU2fHidTimeoutMs),
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +0200428 base::Bind(&U2fHid::TransactionTimeout, base::Unretained(this)));
429
430 // record transaction parameters
431 transaction_->cid = pkt.ChannelId();
432 transaction_->total_size = pkt.MessagePayloadSize();
433 transaction_->cmd = pkt.Command();
434 transaction_->seq = 0;
435 transaction_->payload =
436 report.substr(pkt.PayloadIndex(), transaction_->total_size);
437 } else { // CONT Frame
Vincent Palatinc03a6eb2017-12-01 15:05:59 +0100438 if (transaction_->cid == 0 || transaction_->cid != pkt.ChannelId()) {
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +0200439 VLOG(1) << "invalid CONT";
440 return; // just ignore
441 }
442 if (transaction_->seq != pkt.SeqNumber()) {
443 VLOG(1) << "invalid sequence " << static_cast<int>(pkt.SeqNumber())
444 << " != " << transaction_->seq;
Tom Hughes0f7203b2020-08-24 18:29:15 -0700445 ReturnError(U2fHidError::kInvalidSeq, pkt.ChannelId(),
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +0200446 pkt.ChannelId() == transaction_->cid);
447 return;
448 }
449 // reload timeout
450 transaction_->timeout.Start(
Tom Hughes0f7203b2020-08-24 18:29:15 -0700451 FROM_HERE, base::TimeDelta::FromMilliseconds(kU2fHidTimeoutMs),
Vincent Palatinc6c7e4e2017-06-15 15:45:05 +0200452 base::Bind(&U2fHid::TransactionTimeout, base::Unretained(this)));
453 // record the payload
454 transaction_->payload += report.substr(pkt.PayloadIndex());
455 transaction_->seq++;
456 }
457 // Are we done with this transaction ?
458 if (transaction_->payload.size() >= transaction_->total_size)
459 ExecuteCmd();
460}
461
462} // namespace u2f