Skip to content

Commit 66c54bf

Browse files
committed
Add NFC support
1 parent f567991 commit 66c54bf

File tree

7 files changed

+341
-1
lines changed

7 files changed

+341
-1
lines changed

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ maintenance = { status = "actively-developed" }
1717
binding-recompile = ["bindgen"]
1818
webdriver = ["base64", "bytes", "warp", "tokio", "serde", "serde_json"]
1919

20+
nfc = ["pcsc"]
21+
2022
[target.'cfg(target_os = "linux")'.dependencies]
2123
libudev = "^0.2"
2224

@@ -51,6 +53,7 @@ serde = { version = "1.0", optional = true, features = ["derive"] }
5153
serde_json = { version = "1.0", optional = true }
5254
bytes = { version = "0.5", optional = true, features = ["serde"] }
5355
base64 = { version = "^0.10", optional = true }
56+
pcsc = { version = "2", optional = true }
5457

5558
[dev-dependencies]
5659
sha2 = "^0.8.2"

examples/main.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ fn main() {
4242
opts.optflag("x", "no-u2f-usb-hid", "do not enable u2f-usb-hid platforms");
4343
#[cfg(feature = "webdriver")]
4444
opts.optflag("w", "webdriver", "enable WebDriver virtual bus");
45+
#[cfg(feature = "nfc")]
46+
opts.optflag("n", "no-nfc", "do not enable u2f-nfc platform");
4547

4648
opts.optflag("h", "help", "print this help menu").optopt(
4749
"t",
@@ -74,6 +76,13 @@ fn main() {
7476
}
7577
}
7678

79+
#[cfg(feature = "nfc")]
80+
{
81+
if !matches.opt_present("no-nfc") {
82+
manager.add_u2f_nfc_transports();
83+
}
84+
}
85+
7786
let timeout_ms = match matches.opt_get_default::<u64>("timeout", 15) {
7887
Ok(timeout_s) => {
7988
println!("Using {}s as the timeout", &timeout_s);

src/authenticatorservice.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ impl AuthenticatorService {
7171
/// Add any detected platform transports
7272
pub fn add_detected_transports(&mut self) {
7373
self.add_u2f_usb_hid_platform_transports();
74+
75+
#[cfg(feature = "nfc")]
76+
self.add_u2f_nfc_transports();
7477
}
7578

7679
fn add_transport(&mut self, boxed_token: Box<dyn AuthenticatorTransport + Send>) {
@@ -95,6 +98,11 @@ impl AuthenticatorService {
9598
}
9699
}
97100

101+
#[cfg(feature = "nfc")]
102+
pub fn add_u2f_nfc_transports(&mut self) {
103+
self.add_transport(Box::new(crate::NFCManager::new()));
104+
}
105+
98106
pub fn register(
99107
&mut self,
100108
flags: crate::RegisterFlags,

src/consts.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,12 @@ pub const SW_NO_ERROR: [u8; 2] = [0x90, 0x00];
7878
pub const SW_CONDITIONS_NOT_SATISFIED: [u8; 2] = [0x69, 0x85];
7979
pub const SW_WRONG_DATA: [u8; 2] = [0x6A, 0x80];
8080
pub const SW_WRONG_LENGTH: [u8; 2] = [0x67, 0x00];
81+
82+
// U2F-over-NFC constants
83+
// TODO: naming, some are also ISO 7816-4 ??
84+
// The're mostly APDU-specific?
85+
pub const U2F_AID: [u8; 8] = [0xA0, 0x00, 0x00, 0x06, 0x47, 0x2F, 0x00, 0x01];
86+
pub const U2F_SELECT_FILE: u8 = 0xA4;
87+
pub const U2F_SELECT_DIRECT: u8 = 0x04;
88+
pub const U2F_GET_RESPONSE: u8 = 0xC0;
89+
pub const U2F_MORE_DATA: u8 = 0x61;

src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ pub mod platform;
5252
#[path = "stub/mod.rs"]
5353
pub mod platform;
5454

55+
#[cfg(feature = "nfc")]
56+
extern crate pcsc;
57+
#[cfg(feature = "nfc")]
58+
mod nfc;
59+
#[cfg(feature = "nfc")]
60+
pub use crate::nfc::NFCManager;
61+
5562
extern crate libc;
5663
#[macro_use]
5764
extern crate log;

src/nfc.rs

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
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 pcsc::*;
6+
use runloop::RunLoop;
7+
use std::collections::HashMap;
8+
use std::ffi::CString;
9+
use std::io;
10+
use std::option::Option;
11+
use std::sync::mpsc::Sender;
12+
use std::sync::{Arc, Mutex};
13+
use std::thread;
14+
use std::time::Duration;
15+
16+
use crate::apdu::*;
17+
use crate::consts::*;
18+
use crate::errors;
19+
use crate::util::{io_err, trace_hex};
20+
21+
use crate::authenticatorservice::AuthenticatorTransport;
22+
use crate::statecallback::StateCallback;
23+
use crate::statemachine::StateMachine;
24+
use crate::u2ftypes::{U2FDeviceInfo, U2FInfoQueryable};
25+
26+
fn sendrecv(card: &mut Card, send: &[u8]) -> io::Result<Vec<u8>> {
27+
trace_hex("NFC send", send);
28+
let mut rapdu_buf = [0; MAX_BUFFER_SIZE];
29+
match card.transmit(send, &mut rapdu_buf) {
30+
Ok(rapdu) => {
31+
trace_hex("NFC recv", rapdu);
32+
Ok(rapdu.to_vec())
33+
}
34+
Err(err) => {
35+
trace!("NFC error: {}", err);
36+
let s = format!("{}", err);
37+
Err(io_err(&s))
38+
}
39+
}
40+
}
41+
42+
impl APDUDevice for Card {
43+
fn init_apdu(&mut self) -> io::Result<()> {
44+
let out = APDU::serialize_short(U2F_SELECT_FILE, U2F_SELECT_DIRECT, &U2F_AID)?;
45+
let ret = sendrecv(self, &out)?;
46+
let (_, status) = APDU::deserialize(ret)?;
47+
apdu_status_to_result(status, ())
48+
}
49+
50+
fn send_apdu(&mut self, cmd: u8, p1: u8, send: &[u8]) -> io::Result<(Vec<u8>, [u8; 2])> {
51+
// Some devices, such as the Yubikey 4, freak out if an APDU which _would_ fit a short
52+
// command is sent as an extended command. This means we must use short, even though
53+
// that means chaining the responses together.
54+
// The whole response would have fit in an extended reply, but it seems we can't have nice
55+
// things.
56+
let mut data: Vec<u8> = Vec::new();
57+
58+
let out = APDU::serialize_short(cmd, p1, send)?;
59+
let ret = sendrecv(self, &out)?;
60+
let (mut more, [s1, s2]) = APDU::deserialize(ret)?;
61+
data.append(&mut more);
62+
63+
if s1 != U2F_MORE_DATA {
64+
return Ok((data, [s1, s2]));
65+
}
66+
67+
loop {
68+
let out = APDU::serialize_short(U2F_GET_RESPONSE, 0x00, &[])?;
69+
let ret = sendrecv(self, &out)?;
70+
let (mut more, [s1, s2]) = APDU::deserialize(ret)?;
71+
data.append(&mut more);
72+
if s1 != U2F_MORE_DATA {
73+
return Ok((data, [s1, s2]));
74+
}
75+
}
76+
}
77+
}
78+
79+
impl U2FInfoQueryable for Card {
80+
fn get_device_info(&self) -> U2FDeviceInfo {
81+
// TODO: actuall return something sane here!
82+
let vendor = String::from("Unknown Vendor");
83+
let product = String::from("Unknown Device");
84+
85+
U2FDeviceInfo {
86+
vendor_name: vendor.as_bytes().to_vec(),
87+
device_name: product.as_bytes().to_vec(),
88+
version_interface: 0,
89+
version_major: 0,
90+
version_minor: 0,
91+
version_build: 0,
92+
cap_flags: 0,
93+
}
94+
}
95+
}
96+
97+
pub struct NFCManager {
98+
run_loop: Option<RunLoop>,
99+
}
100+
101+
impl NFCManager {
102+
fn run<E, F>(&mut self, timeout: u64, fatal_error: E, f: F) -> crate::Result<()>
103+
where
104+
E: Fn() + Sync + Send + 'static,
105+
F: Fn(&mut Card, &dyn Fn() -> bool) + Sync + Send + Clone + 'static,
106+
{
107+
if self.run_loop.is_some() {
108+
return Err(errors::AuthenticatorError::InternalError(String::from(
109+
"nfc run loop is already in use",
110+
)));
111+
}
112+
113+
let ctx =
114+
Context::establish(Scope::User).map_err(|_| errors::AuthenticatorError::Platform)?;
115+
116+
let rl = RunLoop::new_with_timeout(
117+
move |alive| {
118+
let mut child_loops: HashMap<CString, RunLoop> = HashMap::new();
119+
120+
let mut readers_buf = [0; 2048];
121+
// We _could_ insert `ReaderState::new(PNP_NOTIFICATION(), State::UNAWARE)`
122+
// to be reminded of reader insertion/removal,
123+
// but this is not guaranteed to be supported
124+
// and we need to poll anyways.
125+
let mut reader_states: Vec<ReaderState> = Vec::new();
126+
127+
while alive() {
128+
// Add new readers.
129+
let names = match ctx.list_readers(&mut readers_buf) {
130+
Ok(n) => n,
131+
Err(_) => {
132+
fatal_error();
133+
break;
134+
}
135+
};
136+
for name in names {
137+
if !reader_states.iter().any(|reader| reader.name() == name) {
138+
debug!("Adding reader {:?}", name);
139+
reader_states.push(ReaderState::new(name, State::UNAWARE));
140+
}
141+
}
142+
143+
// Remove dead readers.
144+
fn is_dead(reader: &ReaderState) -> bool {
145+
reader
146+
.event_state()
147+
.intersects(State::UNKNOWN | State::IGNORE)
148+
}
149+
for reader in &reader_states {
150+
if is_dead(reader) {
151+
debug!("Removing reader {:?}", reader.name());
152+
}
153+
}
154+
reader_states.retain(|reader| !is_dead(reader));
155+
156+
// Let backend know that we know about the reader state.
157+
// Otherwise it will keep trying to update us.
158+
for rs in &mut reader_states {
159+
rs.sync_current_state();
160+
}
161+
162+
if reader_states.len() == 0 {
163+
// No readers available. This means that `get_status_change` will return
164+
// immediately without any work, causing a busy-loop.
165+
// Let's wait for a bit and look for a new reader.
166+
thread::sleep(Duration::from_millis(500));
167+
continue;
168+
}
169+
170+
// This call is blocking, so we must give it _some_ timeout in order for
171+
// the `alive()` check to work.
172+
let timeout = Duration::from_millis(100);
173+
if let Err(e) = ctx.get_status_change(timeout, &mut reader_states) {
174+
if e == Error::Timeout {
175+
continue;
176+
}
177+
fatal_error();
178+
break;
179+
}
180+
181+
for reader in &mut reader_states {
182+
let state = reader.event_state();
183+
let name = CString::from(reader.name());
184+
185+
trace!("Reader {:?}: state {:?}", name, state);
186+
187+
// TODO: this will keep spamming yubikeys with usb auth attempts???
188+
// probably not harmful, but is there a way to avoid it?
189+
if state.contains(State::PRESENT) && !state.contains(State::EXCLUSIVE) {
190+
let mut card =
191+
match ctx.connect(&name, ShareMode::Shared, Protocols::ANY) {
192+
Ok(card) => card,
193+
_ => continue,
194+
};
195+
196+
let my_f = f.clone();
197+
let cl = RunLoop::new(move |alive| {
198+
if alive() {
199+
my_f(&mut card, alive);
200+
}
201+
});
202+
203+
if let Ok(x) = cl {
204+
child_loops.insert(name, x);
205+
}
206+
} else {
207+
if let Some(cl) = child_loops.remove(&name) {
208+
cl.cancel();
209+
}
210+
}
211+
}
212+
}
213+
214+
for (_, child) in child_loops {
215+
child.cancel();
216+
}
217+
},
218+
timeout,
219+
)
220+
.map_err(|_| errors::AuthenticatorError::Platform)?;
221+
self.run_loop = Some(rl);
222+
Ok(())
223+
}
224+
225+
pub fn new() -> Self {
226+
Self { run_loop: None }
227+
}
228+
229+
fn stop(&mut self) {
230+
if let Some(rl) = &self.run_loop {
231+
rl.cancel();
232+
self.run_loop = None;
233+
}
234+
}
235+
}
236+
237+
impl Drop for NFCManager {
238+
fn drop(&mut self) {
239+
self.stop();
240+
}
241+
}
242+
243+
impl AuthenticatorTransport for NFCManager {
244+
fn register(
245+
&mut self,
246+
flags: crate::RegisterFlags,
247+
timeout: u64,
248+
challenge: Vec<u8>,
249+
application: crate::AppId,
250+
key_handles: Vec<crate::KeyHandle>,
251+
status: Sender<crate::StatusUpdate>,
252+
callback: StateCallback<crate::Result<crate::RegisterResult>>,
253+
) -> crate::Result<()> {
254+
let status_mutex = Arc::new(Mutex::new(status));
255+
let cbc = callback.clone();
256+
let err = move || cbc.call(Err(errors::AuthenticatorError::Platform));
257+
self.run(timeout, err, move |card, alive| {
258+
StateMachine::register(
259+
card,
260+
flags,
261+
&challenge,
262+
&application,
263+
&key_handles,
264+
&status_mutex,
265+
&callback,
266+
alive,
267+
);
268+
})
269+
}
270+
271+
fn sign(
272+
&mut self,
273+
flags: crate::SignFlags,
274+
timeout: u64,
275+
challenge: Vec<u8>,
276+
app_ids: Vec<crate::AppId>,
277+
key_handles: Vec<crate::KeyHandle>,
278+
status: Sender<crate::StatusUpdate>,
279+
callback: StateCallback<crate::Result<crate::SignResult>>,
280+
) -> crate::Result<()> {
281+
let status_mutex = Arc::new(Mutex::new(status));
282+
let cbc = callback.clone();
283+
let err = move || cbc.call(Err(errors::AuthenticatorError::Platform));
284+
self.run(timeout, err, move |card, alive| {
285+
StateMachine::sign(
286+
card,
287+
flags,
288+
&challenge,
289+
&app_ids,
290+
&key_handles,
291+
&status_mutex,
292+
&callback,
293+
alive,
294+
);
295+
})
296+
}
297+
298+
fn cancel(&mut self) -> crate::Result<()> {
299+
self.stop();
300+
Ok(())
301+
}
302+
}

0 commit comments

Comments
 (0)