Skip to content

Commit 9caad1b

Browse files
committed
Add basic NFC support
1 parent fb6bb17 commit 9caad1b

File tree

15 files changed

+553
-291
lines changed

15 files changed

+553
-291
lines changed

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ description = "Library for interacting with CTAP1/2 security keys for Web Authen
99
[features]
1010
binding-recompile = ["bindgen"]
1111

12+
nfc = ["pcsc"]
13+
1214
[target.'cfg(target_os = "linux")'.dependencies]
1315
libudev = "^0.2"
1416

@@ -38,6 +40,7 @@ libc = "0.2"
3840
boxfnonce = "0.0.3"
3941
runloop = "0.1.0"
4042
bitflags = "1.0"
43+
pcsc = { version = "2", optional = true }
4144

4245
[dev-dependencies]
4346
sha2 = "^0.8"

src/consts.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,10 @@ pub const SW_NO_ERROR: [u8; 2] = [0x90, 0x00];
7676
pub const SW_CONDITIONS_NOT_SATISFIED: [u8; 2] = [0x69, 0x85];
7777
pub const SW_WRONG_DATA: [u8; 2] = [0x6A, 0x80];
7878
pub const SW_WRONG_LENGTH: [u8; 2] = [0x67, 0x00];
79+
80+
// U2F-over-NFC constants
81+
pub const U2F_AID: [u8; 8] = [0xA0, 0x00, 0x00, 0x06, 0x47, 0x2F, 0x00, 0x01];
82+
pub const U2F_SELECT_FILE: u8 = 0xA4;
83+
pub const U2F_SELECT_DIRECT: u8 = 0x04;
84+
pub const U2F_CONTINUE: u8 = 0xC0;
85+
pub const U2F_MORE_DATA: u8 = 0x61;

src/hid.rs

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
use std::io;
6+
use std::io::{Read, Write};
7+
use std::cmp;
8+
use rand::{thread_rng, RngCore};
9+
10+
use platform::device::USBDevice;
11+
use u2ftypes::*;
12+
use consts::*;
13+
use util::{io_err, trace_hex};
14+
15+
// Represents U2F HID Devices. Requires getters/setters for the
16+
// channel ID, created during device initialization.
17+
pub struct HIDDevice<'a> {
18+
base: &'a mut USBDevice,
19+
cid: [u8; 4],
20+
}
21+
22+
impl<'a> HIDDevice<'a> {
23+
pub fn new(base: &'a mut USBDevice) -> HIDDevice<'a> {
24+
Self {
25+
base,
26+
cid: CID_BROADCAST,
27+
}
28+
}
29+
30+
pub fn get_cid(&self) -> &[u8; 4] {
31+
&self.cid
32+
}
33+
34+
pub fn set_cid(&mut self, cid: [u8; 4]) {
35+
self.cid = cid;
36+
}
37+
38+
fn sendrecv(&mut self, cmd: u8, send: &[u8]) -> io::Result<Vec<u8>>
39+
{
40+
// Send initialization packet.
41+
let mut count = U2FHIDInit::write(self, cmd, send)?;
42+
43+
// Send continuation packets.
44+
let mut sequence = 0u8;
45+
while count < send.len() {
46+
count += U2FHIDCont::write(self, sequence, &send[count..])?;
47+
sequence += 1;
48+
}
49+
50+
// Now we read. This happens in 2 chunks: The initial packet, which has the
51+
// size we expect overall, then continuation packets, which will fill in
52+
// data until we have everything.
53+
let mut data = U2FHIDInit::read(self)?;
54+
55+
let mut sequence = 0u8;
56+
while data.len() < data.capacity() {
57+
let max = data.capacity() - data.len();
58+
data.extend_from_slice(&U2FHIDCont::read(self, sequence, max)?);
59+
sequence += 1;
60+
}
61+
62+
Ok(data)
63+
}
64+
}
65+
66+
impl Read for HIDDevice<'_> {
67+
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
68+
self.base.read(buf)
69+
}
70+
}
71+
72+
impl Write for HIDDevice<'_> {
73+
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
74+
self.base.write(buf)
75+
}
76+
77+
// USB HID writes don't buffer, so this will be a nop.
78+
fn flush(&mut self) -> io::Result<()> {
79+
Ok(())
80+
}
81+
}
82+
83+
84+
impl APDUDevice for HIDDevice<'_> {
85+
fn init_apdu(&mut self) -> io::Result<()> {
86+
self.base.initialize()?;
87+
88+
let mut nonce = [0u8; 8];
89+
thread_rng().fill_bytes(&mut nonce);
90+
assert_eq!(nonce.len(), INIT_NONCE_SIZE);
91+
let raw = self.sendrecv(U2FHID_INIT, &nonce)?;
92+
self.set_cid(U2FHIDInitResp::read(&raw, &nonce)?);
93+
94+
Ok(())
95+
}
96+
97+
fn send_apdu(&mut self, cmd: u8, p1: u8, send: &[u8]) -> io::Result<(Vec<u8>, [u8; 2])> {
98+
let out = APDU::serialize_long(cmd, p1, send)?;
99+
trace_hex("USB send", &out);
100+
let ret = self.sendrecv(U2FHID_MSG, &out)?;
101+
trace_hex("USB recv", &ret);
102+
APDU::deserialize(ret)
103+
}
104+
}
105+
106+
// Init structure for U2F Communications. Tells the receiver what channel
107+
// communication is happening on, what command is running, and how much data to
108+
// expect to receive over all.
109+
//
110+
// Spec at https://fidoalliance.org/specs/fido-u2f-v1.
111+
// 0-nfc-bt-amendment-20150514/fido-u2f-hid-protocol.html#message--and-packet-structure
112+
pub struct U2FHIDInit {}
113+
114+
impl U2FHIDInit {
115+
pub fn read(dev: &mut HIDDevice) -> io::Result<Vec<u8>>
116+
{
117+
let mut frame = [0u8; HID_RPT_SIZE];
118+
let mut count = dev.read(&mut frame)?;
119+
120+
while dev.get_cid() != &frame[..4] {
121+
count = dev.read(&mut frame)?;
122+
}
123+
124+
if count != HID_RPT_SIZE {
125+
return Err(io_err("invalid init packet"));
126+
}
127+
128+
let cap = (frame[5] as usize) << 8 | (frame[6] as usize);
129+
let mut data = Vec::with_capacity(cap);
130+
131+
let len = cmp::min(cap, INIT_DATA_SIZE);
132+
data.extend_from_slice(&frame[7..7 + len]);
133+
134+
Ok(data)
135+
}
136+
137+
pub fn write(dev: &mut HIDDevice, cmd: u8, data: &[u8]) -> io::Result<usize>
138+
{
139+
if data.len() > 0xffff {
140+
return Err(io_err("payload length > 2^16"));
141+
}
142+
143+
let mut frame = [0; HID_RPT_SIZE + 1];
144+
frame[1..5].copy_from_slice(dev.get_cid());
145+
frame[5] = cmd;
146+
frame[6] = (data.len() >> 8) as u8;
147+
frame[7] = data.len() as u8;
148+
149+
let count = cmp::min(data.len(), INIT_DATA_SIZE);
150+
frame[8..8 + count].copy_from_slice(&data[..count]);
151+
152+
if dev.write(&frame)? != frame.len() {
153+
return Err(io_err("device write failed"));
154+
}
155+
156+
Ok(count)
157+
}
158+
}
159+
160+
// Continuation structure for U2F Communications. After an Init structure is
161+
// sent, continuation structures are used to transmit all extra data that
162+
// wouldn't fit in the initial packet. The sequence number increases with every
163+
// packet, until all data is received.
164+
//
165+
// https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-hid-protocol.
166+
// html#message--and-packet-structure
167+
pub struct U2FHIDCont {}
168+
169+
impl U2FHIDCont {
170+
pub fn read(dev: &mut HIDDevice, seq: u8, max: usize) -> io::Result<Vec<u8>>
171+
{
172+
let mut frame = [0u8; HID_RPT_SIZE];
173+
let mut count = dev.read(&mut frame)?;
174+
175+
while dev.get_cid() != &frame[..4] {
176+
count = dev.read(&mut frame)?;
177+
}
178+
179+
if count != HID_RPT_SIZE {
180+
return Err(io_err("invalid cont packet"));
181+
}
182+
183+
if seq != frame[4] {
184+
return Err(io_err("invalid sequence number"));
185+
}
186+
187+
let max = cmp::min(max, CONT_DATA_SIZE);
188+
Ok(frame[5..5 + max].to_vec())
189+
}
190+
191+
pub fn write(dev: &mut HIDDevice, seq: u8, data: &[u8]) -> io::Result<usize>
192+
{
193+
let mut frame = [0; HID_RPT_SIZE + 1];
194+
frame[1..5].copy_from_slice(dev.get_cid());
195+
frame[5] = seq;
196+
197+
let count = cmp::min(data.len(), CONT_DATA_SIZE);
198+
frame[6..6 + count].copy_from_slice(&data[..count]);
199+
200+
if dev.write(&frame)? != frame.len() {
201+
return Err(io_err("device write failed"));
202+
}
203+
204+
Ok(count)
205+
}
206+
}
207+
208+
// Reply sent after initialization command. Contains information about U2F USB
209+
// Key versioning, as well as the communication channel to be used for all
210+
// further requests.
211+
//
212+
// https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-hid-protocol.
213+
// html#u2fhid_init
214+
pub struct U2FHIDInitResp {}
215+
216+
impl U2FHIDInitResp {
217+
pub fn read(data: &[u8], nonce: &[u8]) -> io::Result<[u8; 4]> {
218+
assert_eq!(nonce.len(), INIT_NONCE_SIZE);
219+
220+
if data.len() != INIT_NONCE_SIZE + 9 {
221+
return Err(io_err("invalid init response"));
222+
}
223+
224+
if nonce != &data[..INIT_NONCE_SIZE] {
225+
return Err(io_err("invalid nonce"));
226+
}
227+
228+
let mut cid = [0u8; 4];
229+
cid.copy_from_slice(&data[INIT_NONCE_SIZE..INIT_NONCE_SIZE + 4]);
230+
Ok(cid)
231+
}
232+
}

src/lib.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,17 @@ pub mod hidproto;
1111
#[cfg(any(target_os = "linux"))]
1212
extern crate libudev;
1313

14-
#[cfg(any(target_os = "linux"))]
14+
#[cfg(all(any(target_os = "linux"), not(feature = "nfc")))]
1515
#[path = "linux/mod.rs"]
1616
pub mod platform;
1717

18+
#[cfg(feature = "nfc")]
19+
#[path = "nfc/mod.rs"]
20+
pub mod platform;
21+
22+
#[cfg(feature = "nfc")]
23+
extern crate pcsc;
24+
1825
#[cfg(any(target_os = "freebsd"))]
1926
extern crate devd_rs;
2027

@@ -61,6 +68,8 @@ mod consts;
6168
mod statemachine;
6269
mod u2fprotocol;
6370
mod u2ftypes;
71+
#[cfg(not(feature = "nfc"))]
72+
mod hid;
6473

6574
mod manager;
6675
pub use manager::U2FManager;

src/linux/device.rs

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,57 +9,58 @@ use std::io;
99
use std::io::{Read, Write};
1010
use std::os::unix::prelude::*;
1111

12-
use consts::CID_BROADCAST;
1312
use platform::hidraw;
14-
use u2ftypes::U2FDevice;
1513
use util::from_unix_result;
14+
use util::io_err;
1615

1716
#[derive(Debug)]
18-
pub struct Device {
17+
pub struct USBDevice {
1918
path: OsString,
2019
fd: libc::c_int,
21-
cid: [u8; 4],
2220
}
2321

24-
impl Device {
22+
impl USBDevice {
2523
pub fn new(path: OsString) -> io::Result<Self> {
2624
let cstr = CString::new(path.as_bytes())?;
2725
let fd = unsafe { libc::open(cstr.as_ptr(), libc::O_RDWR) };
2826
let fd = from_unix_result(fd)?;
2927
Ok(Self {
3028
path,
3129
fd,
32-
cid: CID_BROADCAST,
3330
})
3431
}
3532

36-
pub fn is_u2f(&self) -> bool {
37-
hidraw::is_u2f_device(self.fd)
33+
pub fn initialize(&self) -> io::Result<()> {
34+
if hidraw::is_u2f_device(self.fd) {
35+
Ok(())
36+
} else {
37+
Err(io_err("not a u2f device"))
38+
}
3839
}
3940
}
4041

41-
impl Drop for Device {
42+
impl Drop for USBDevice {
4243
fn drop(&mut self) {
4344
// Close the fd, ignore any errors.
4445
let _ = unsafe { libc::close(self.fd) };
4546
}
4647
}
4748

48-
impl PartialEq for Device {
49-
fn eq(&self, other: &Device) -> bool {
49+
impl PartialEq for USBDevice {
50+
fn eq(&self, other: &USBDevice) -> bool {
5051
self.path == other.path
5152
}
5253
}
5354

54-
impl Read for Device {
55+
impl Read for USBDevice {
5556
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
5657
let bufp = buf.as_mut_ptr() as *mut libc::c_void;
5758
let rv = unsafe { libc::read(self.fd, bufp, buf.len()) };
5859
from_unix_result(rv as usize)
5960
}
6061
}
6162

62-
impl Write for Device {
63+
impl Write for USBDevice {
6364
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
6465
let bufp = buf.as_ptr() as *const libc::c_void;
6566
let rv = unsafe { libc::write(self.fd, bufp, buf.len()) };
@@ -71,13 +72,3 @@ impl Write for Device {
7172
Ok(())
7273
}
7374
}
74-
75-
impl U2FDevice for Device {
76-
fn get_cid(&self) -> &[u8; 4] {
77-
&self.cid
78-
}
79-
80-
fn set_cid(&mut self, cid: [u8; 4]) {
81-
self.cid = cid;
82-
}
83-
}

0 commit comments

Comments
 (0)