Skip to content

Commit 76b1369

Browse files
committed
Extract in/out HID report sizes from descriptors
The U2F/CTAP specifications for the HID transport allow arbitrary sizes for the HID input/output reports, not just the standard 64 bytes. This commit adds the general logic needed to deal with varying report sizes and implements the relevant descriptor parsing for Linux.
1 parent 6495aa2 commit 76b1369

File tree

11 files changed

+209
-27
lines changed

11 files changed

+209
-27
lines changed

src/consts.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,17 @@
55
// Allow dead code in this module, since it's all packet consts anyways.
66
#![allow(dead_code)]
77

8-
pub const HID_RPT_SIZE: usize = 64;
8+
pub const MAX_HID_RPT_SIZE: usize = 64;
99
pub const U2FAPDUHEADER_SIZE: usize = 7;
1010
pub const CID_BROADCAST: [u8; 4] = [0xff, 0xff, 0xff, 0xff];
1111
pub const TYPE_MASK: u8 = 0x80;
1212
pub const TYPE_INIT: u8 = 0x80;
1313
pub const TYPE_CONT: u8 = 0x80;
1414

15-
// Size of data chunk expected in U2F Init USB HID Packets
16-
pub const INIT_DATA_SIZE: usize = HID_RPT_SIZE - 7;
17-
// Size of data chunk expected in U2F Cont USB HID Packets
18-
pub const CONT_DATA_SIZE: usize = HID_RPT_SIZE - 5;
15+
// Size of header in U2F Init USB HID Packets
16+
pub const INIT_HEADER_SIZE: usize = 7;
17+
// Size of header in U2F Cont USB HID Packets
18+
pub const CONT_HEADER_SIZE: usize = 5;
1919

2020
pub const PARAMETER_SIZE: usize = 32;
2121

src/freebsd/device.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::io;
99
use std::io::{Read, Write};
1010
use std::os::unix::prelude::*;
1111

12-
use consts::CID_BROADCAST;
12+
use consts::{CID_BROADCAST, MAX_HID_RPT_SIZE};
1313
use platform::uhid;
1414
use u2ftypes::U2FDevice;
1515
use util::from_unix_result;
@@ -85,4 +85,12 @@ impl U2FDevice for Device {
8585
fn set_cid(&mut self, cid: [u8; 4]) {
8686
self.cid = cid;
8787
}
88+
89+
fn in_rpt_size(&self) -> usize {
90+
MAX_HID_RPT_SIZE
91+
}
92+
93+
fn out_rpt_size(&self) -> usize {
94+
MAX_HID_RPT_SIZE
95+
}
8896
}

src/hidproto.rs

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66

77
#![cfg_attr(feature = "cargo-clippy", allow(cast_lossless, needless_lifetimes))]
88

9+
use std::io;
910
use std::mem;
1011

11-
use consts::{FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID};
12+
use consts::{FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID, INIT_HEADER_SIZE, MAX_HID_RPT_SIZE};
1213

1314
// The 4 MSBs (the tag) are set when it's a long item.
1415
const HID_MASK_LONG_ITEM_TAG: u8 = 0b1111_0000;
@@ -20,6 +21,12 @@ const HID_MASK_ITEM_TAGTYPE: u8 = 0b1111_1100;
2021
const HID_ITEM_TAGTYPE_USAGE: u8 = 0b0000_1000;
2122
// tag=0000, type=01 (global)
2223
const HID_ITEM_TAGTYPE_USAGE_PAGE: u8 = 0b0000_0100;
24+
// tag=1000, type=00 (main)
25+
const HID_ITEM_TAGTYPE_INPUT: u8 = 0b1000_0000;
26+
// tag=1001, type=00 (main)
27+
const HID_ITEM_TAGTYPE_OUTPUT: u8 = 0b1001_0000;
28+
// tag=1001, type=01 (global)
29+
const HID_ITEM_TAGTYPE_REPORT_COUNT: u8 = 0b1001_0100;
2330

2431
pub struct ReportDescriptor {
2532
pub value: Vec<u8>,
@@ -35,6 +42,9 @@ impl ReportDescriptor {
3542
pub enum Data {
3643
UsagePage { data: u32 },
3744
Usage { data: u32 },
45+
Input,
46+
Output,
47+
ReportCount { data: u32 },
3848
}
3949

4050
pub struct ReportDescriptorIterator {
@@ -69,10 +79,12 @@ impl ReportDescriptorIterator {
6979

7080
// Convert data bytes to a uint.
7181
let data = read_uint_le(data);
72-
7382
match tag_type {
7483
HID_ITEM_TAGTYPE_USAGE_PAGE => Some(Data::UsagePage { data }),
7584
HID_ITEM_TAGTYPE_USAGE => Some(Data::Usage { data }),
85+
HID_ITEM_TAGTYPE_INPUT => Some(Data::Input),
86+
HID_ITEM_TAGTYPE_OUTPUT => Some(Data::Output),
87+
HID_ITEM_TAGTYPE_REPORT_COUNT => Some(Data::ReportCount { data }),
7688
_ => None,
7789
}
7890
}
@@ -150,6 +162,7 @@ pub fn has_fido_usage(desc: ReportDescriptor) -> bool {
150162
match data {
151163
Data::UsagePage { data } => usage_page = Some(data),
152164
Data::Usage { data } => usage = Some(data),
165+
_ => {}
153166
}
154167

155168
// Check the values we found.
@@ -161,3 +174,77 @@ pub fn has_fido_usage(desc: ReportDescriptor) -> bool {
161174

162175
false
163176
}
177+
178+
pub fn read_hid_rpt_sizes(desc: ReportDescriptor) -> io::Result<(usize, usize)> {
179+
let mut in_rpt_count = None;
180+
let mut out_rpt_count = None;
181+
let mut last_rpt_count = None;
182+
183+
for data in desc.iter() {
184+
match data {
185+
Data::ReportCount { data } => {
186+
if last_rpt_count != None {
187+
return Err(io::Error::new(
188+
io::ErrorKind::InvalidInput,
189+
"Duplicate HID_ReportCount",
190+
));
191+
}
192+
last_rpt_count = Some(data as usize);
193+
}
194+
Data::Input => {
195+
if last_rpt_count.is_none() {
196+
return Err(io::Error::new(
197+
io::ErrorKind::InvalidInput,
198+
"HID_Input should be preceded by HID_ReportCount",
199+
));
200+
}
201+
if in_rpt_count.is_some() {
202+
return Err(io::Error::new(
203+
io::ErrorKind::InvalidInput,
204+
"Duplicate HID_ReportCount",
205+
));
206+
}
207+
in_rpt_count = last_rpt_count;
208+
last_rpt_count = None
209+
}
210+
Data::Output => {
211+
if last_rpt_count.is_none() {
212+
return Err(io::Error::new(
213+
io::ErrorKind::InvalidInput,
214+
"HID_Output should be preceded by HID_ReportCount",
215+
));
216+
}
217+
if out_rpt_count.is_some() {
218+
return Err(io::Error::new(
219+
io::ErrorKind::InvalidInput,
220+
"Duplicate HID_ReportCount",
221+
));
222+
}
223+
out_rpt_count = last_rpt_count;
224+
last_rpt_count = None;
225+
}
226+
_ => {}
227+
}
228+
}
229+
230+
match (in_rpt_count, out_rpt_count) {
231+
(Some(in_count), Some(out_count)) => {
232+
if in_count > INIT_HEADER_SIZE
233+
&& in_count <= MAX_HID_RPT_SIZE
234+
&& out_count > INIT_HEADER_SIZE
235+
&& out_count <= MAX_HID_RPT_SIZE
236+
{
237+
Ok((in_count, out_count))
238+
} else {
239+
Err(io::Error::new(
240+
io::ErrorKind::InvalidData,
241+
"Report size is too small or too large",
242+
))
243+
}
244+
}
245+
_ => Err(io::Error::new(
246+
io::ErrorKind::InvalidData,
247+
"Failed to extract report sizes from report descriptor",
248+
)),
249+
}
250+
}

src/linux/device.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ use util::from_unix_result;
1818
pub struct Device {
1919
path: OsString,
2020
fd: libc::c_int,
21+
in_rpt_size: usize,
22+
out_rpt_size: usize,
2123
cid: [u8; 4],
2224
}
2325

@@ -26,9 +28,12 @@ impl Device {
2628
let cstr = CString::new(path.as_bytes())?;
2729
let fd = unsafe { libc::open(cstr.as_ptr(), libc::O_RDWR) };
2830
let fd = from_unix_result(fd)?;
31+
let (in_rpt_size, out_rpt_size) = hidraw::read_hid_rpt_sizes_or_defaults(fd);
2932
Ok(Self {
3033
path,
3134
fd,
35+
in_rpt_size,
36+
out_rpt_size,
3237
cid: CID_BROADCAST,
3338
})
3439
}
@@ -80,4 +85,12 @@ impl U2FDevice for Device {
8085
fn set_cid(&mut self, cid: [u8; 4]) {
8186
self.cid = cid;
8287
}
88+
89+
fn in_rpt_size(&self) -> usize {
90+
self.in_rpt_size
91+
}
92+
93+
fn out_rpt_size(&self) -> usize {
94+
self.out_rpt_size
95+
}
8396
}

src/linux/hidraw.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::io;
99
use std::os::unix::io::RawFd;
1010

1111
use super::hidwrapper::{_HIDIOCGRDESC, _HIDIOCGRDESCSIZE};
12+
use consts::MAX_HID_RPT_SIZE;
1213
use hidproto::*;
1314
use util::{from_unix_result, io_err};
1415

@@ -47,6 +48,20 @@ pub fn is_u2f_device(fd: RawFd) -> bool {
4748
}
4849
}
4950

51+
pub fn read_hid_rpt_sizes_or_defaults(fd: RawFd) -> (usize, usize) {
52+
let default_rpt_sizes = (MAX_HID_RPT_SIZE, MAX_HID_RPT_SIZE);
53+
let desc = read_report_descriptor(fd);
54+
if let Ok(desc) = desc {
55+
if let Ok(rpt_sizes) = read_hid_rpt_sizes(desc) {
56+
rpt_sizes
57+
} else {
58+
default_rpt_sizes
59+
}
60+
} else {
61+
default_rpt_sizes
62+
}
63+
}
64+
5065
fn read_report_descriptor(fd: RawFd) -> io::Result<ReportDescriptor> {
5166
let mut desc = LinuxReportDescriptor {
5267
size: 0,

src/macos/device.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
extern crate log;
66

7-
use consts::{CID_BROADCAST, HID_RPT_SIZE};
7+
use consts::{CID_BROADCAST, MAX_HID_RPT_SIZE};
88
use core_foundation::base::*;
99
use platform::iokit::*;
1010
use std::convert::TryInto;
@@ -100,4 +100,12 @@ impl U2FDevice for Device {
100100
fn set_cid(&mut self, cid: [u8; 4]) {
101101
self.cid = cid;
102102
}
103+
104+
fn in_rpt_size(&self) -> usize {
105+
MAX_HID_RPT_SIZE
106+
}
107+
108+
fn out_rpt_size(&self) -> usize {
109+
MAX_HID_RPT_SIZE
110+
}
103111
}

src/openbsd/device.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::ffi::OsString;
88
use std::io::{Read, Result, Write};
99
use std::mem;
1010

11-
use consts::CID_BROADCAST;
11+
use consts::{CID_BROADCAST, MAX_HID_RPT_SIZE};
1212
use platform::monitor::FidoDev;
1313
use u2ftypes::U2FDevice;
1414
use util::{from_unix_result, io_err};
@@ -120,4 +120,12 @@ impl U2FDevice for Device {
120120
fn set_cid(&mut self, cid: [u8; 4]) {
121121
self.cid = cid;
122122
}
123+
124+
fn in_rpt_size(&self) -> usize {
125+
MAX_HID_RPT_SIZE
126+
}
127+
128+
fn out_rpt_size(&self) -> usize {
129+
MAX_HID_RPT_SIZE
130+
}
123131
}

src/stub/device.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,12 @@ impl U2FDevice for Device {
4242
fn set_cid(&mut self, cid: [u8; 4]) {
4343
panic!("not implemented");
4444
}
45+
46+
fn in_rpt_size(&self) -> usize {
47+
panic!("not implemented");
48+
}
49+
50+
fn out_rpt_size(&self) -> usize {
51+
panic!("not implemented");
52+
}
4553
}

src/u2fprotocol.rs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -223,13 +223,16 @@ mod tests {
223223
use std::io;
224224
use std::io::{Read, Write};
225225

226-
use consts::{CID_BROADCAST, HID_RPT_SIZE};
226+
use consts::CID_BROADCAST;
227227
use u2ftypes::U2FDevice;
228228

229+
const IN_HID_RPT_SIZE: usize = 64;
230+
const OUT_HID_RPT_SIZE: usize = 64;
231+
229232
pub struct TestDevice {
230233
cid: [u8; 4],
231-
reads: Vec<[u8; HID_RPT_SIZE]>,
232-
writes: Vec<[u8; HID_RPT_SIZE + 1]>,
234+
reads: Vec<[u8; IN_HID_RPT_SIZE]>,
235+
writes: Vec<[u8; OUT_HID_RPT_SIZE + 1]>,
233236
}
234237

235238
impl TestDevice {
@@ -243,7 +246,7 @@ mod tests {
243246

244247
pub fn add_write(&mut self, packet: &[u8], fill_value: u8) {
245248
// Add one to deal with record index check
246-
let mut write = [fill_value; HID_RPT_SIZE + 1];
249+
let mut write = [fill_value; OUT_HID_RPT_SIZE + 1];
247250
// Make sure we start with a 0, for HID record index
248251
write[0] = 0;
249252
// Clone packet data in at 1, since front is padded with HID record index
@@ -252,7 +255,7 @@ mod tests {
252255
}
253256

254257
pub fn add_read(&mut self, packet: &[u8], fill_value: u8) {
255-
let mut read = [fill_value; HID_RPT_SIZE];
258+
let mut read = [fill_value; IN_HID_RPT_SIZE];
256259
read[..packet.len()].clone_from_slice(packet);
257260
self.reads.push(read);
258261
}
@@ -300,6 +303,14 @@ mod tests {
300303
fn set_cid(&mut self, cid: [u8; 4]) {
301304
self.cid = cid;
302305
}
306+
307+
fn in_rpt_size(&self) -> usize {
308+
IN_HID_RPT_SIZE
309+
}
310+
311+
fn out_rpt_size(&self) -> usize {
312+
OUT_HID_RPT_SIZE
313+
}
303314
}
304315
}
305316

0 commit comments

Comments
 (0)