1#[macro_use]
2extern crate log;
3extern crate byteorder;
4extern crate nfc;
5
6use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
7use nfc::context;
8use nfc::device;
9use nfc::ffi;
10use nfc::initiator;
11use nfc::misc;
12use std::collections::HashMap;
13use std::fmt;
14use std::io::{Cursor, Read, Write};
15use std::mem;
16use std::ptr;
17use std::time::{Duration, Instant, SystemTime};
18
19#[cfg(test)]
20mod tests {
21 use std::time::{Duration, SystemTime};
22
23 #[test]
24 fn it_works() {}
25
26 #[test]
27 fn code_formatter_works() {
28 let code = ::OathCode {
29 digits: ::OathDigits::Six,
30 value: 595641143,
31 expiration: 0,
32 steam: false,
33 };
34 assert_eq!(format!("{}", code), "641143");
35 }
36
37 #[test]
38 fn parse_tlv_works() {
39 let resp = ::parse_tlv(&vec![0x76, 0x05, 0x06, 0x23, 0x80, 0xc3, 0x37, 0x90, 0x00]);
40 println!("{:?}", resp);
41 }
42
43 #[test]
44 fn parse_list_works() {
45 let result = ::parse_list(&vec![
46 0x72, 0x1A, 0x21, 0x44, 0x6F, 0x6F, 0x72, 0x79, 0x3A, 0x64, 0x6F, 0x6F, 0x72, 0x79,
47 0x40, 0x63, 0x75, 0x74, 0x65, 0x6C, 0x61, 0x62, 0x2E, 0x68, 0x6F, 0x75, 0x73, 0x65,
48 0x90, 0x00,
49 ]);
50 let answer = vec![::OathCredential::new(
51 "Doory:doory@cutelab.house",
52 ::OathType::Totp,
53 false,
54 ::OathAlgo::Sha1,
55 )];
56 assert_eq!(answer, result.unwrap());
57
58 let result = ::parse_list(&vec![
59 0x72, 0x16, 0x21, 0x44, 0x6F, 0x6D, 0x61, 0x69, 0x6E, 0x3A, 0x79, 0x6F, 0x75, 0x72,
60 0x40, 0x65, 0x6D, 0x61, 0x69, 0x6C, 0x2E, 0x63, 0x6F, 0x6D, 0x72, 0x17, 0x21, 0x44,
61 0x6F, 0x6F, 0x72, 0x79, 0x3A, 0x65, 0x76, 0x40, 0x63, 0x75, 0x74, 0x65, 0x6C, 0x61,
62 0x62, 0x2E, 0x68, 0x6F, 0x75, 0x73, 0x65, 0x90, 0x00,
63 ]);
64 let answer = vec![
65 ::OathCredential::new(
66 "Domain:your@email.com",
67 ::OathType::Totp,
68 false,
69 ::OathAlgo::Sha1,
70 ),
71 ::OathCredential::new(
72 "Doory:ev@cutelab.house",
73 ::OathType::Totp,
74 false,
75 ::OathAlgo::Sha1,
76 ),
77 ];
78 assert_eq!(answer, result.unwrap());
79 }
80
81 #[test]
82 fn time_challenge_works() {
83 let vectors: &[(Duration, Vec<u8>)] = &[
84 (
85 Duration::new(0, 0),
86 vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
87 ),
88 (
89 Duration::new(12345678, 0),
90 vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x47, 0x82],
91 ),
92 (
93 Duration::new(1484223461, 264495800),
94 vec![0x00, 0x00, 0x00, 0x00, 0x02, 0xf2, 0xea, 0x43],
95 ),
96 ];
97
98 for i in 0..vectors.len() {
99 let (dur, ref answer) = vectors[i];
100
101 let time = SystemTime::UNIX_EPOCH + dur;
102 let result = ::time_challenge(Some(time));
103
104 assert_eq!(*answer, result);
105 }
106 }
107
108}
109
110#[repr(u8)]
111pub enum Tag {
112 Name = 0x71,
113 NameList = 0x72,
114 Key = 0x73,
115 Challenge = 0x74,
116 Response = 0x75,
117 TruncatedResponse = 0x76,
118 Hotp = 0x77,
119 Property = 0x78,
120 Version = 0x79,
121 Imf = 0x7a,
122 Algorithm = 0x7b,
123 Touch = 0x7c,
124}
125
126#[derive(Debug, PartialEq)]
127#[repr(u8)]
128pub enum OathAlgo {
129 Sha1 = 0x01,
130 Sha256 = 0x02,
131}
132
133impl OathAlgo {
134 fn from_u8(n: u8) -> Option<OathAlgo> {
135 match n {
136 0x01 => Some(OathAlgo::Sha1),
137 0x02 => Some(OathAlgo::Sha256),
138 _ => None,
139 }
140 }
141}
142
143#[derive(Debug, PartialEq)]
144#[repr(u8)]
145pub enum OathType {
146 Hotp = 0x10,
147 Totp = 0x20,
148}
149
150impl OathType {
151 fn from_u8(n: u8) -> Option<OathType> {
152 match n {
153 0x10 => Some(OathType::Hotp),
154 0x20 => Some(OathType::Totp),
155 _ => None,
156 }
157 }
158}
159
160#[repr(u8)]
161pub enum Properties {
162 RequireTouch = 0x02,
163}
164
165#[repr(u8)]
166pub enum Ins {
167 Put = 0x01,
168 Delete = 0x02,
169 SetCode = 0x03,
170 Reset = 0x04,
171 List = 0xa1,
172 Calculate = 0xa2,
173 Validate = 0xa3,
174 CalculateAll = 0xa4,
175 SendRemaining = 0xa5,
176}
177
178#[repr(u8)]
179pub enum Mask {
180 Algo = 0x0f,
181 Type = 0xf0,
182}
183
184pub enum Sw {
185 NoSpace = 0x6a84,
186 CommandAborted = 0x6f00,
187 MoreData = 0x61,
188 InvalidInstruction = 0x6d00,
189}
190
191#[derive(Clone, Copy, Debug, PartialEq)]
192pub enum OathDigits {
193 Six = 6,
194 Eight = 8,
195}
196
197#[derive(Debug, PartialEq)]
198pub struct OathCode {
199 pub digits: OathDigits,
200 pub value: u32,
201 pub expiration: u32, pub steam: bool,
203}
204#[derive(Debug, PartialEq)]
205pub struct OathCredential {
206 pub name: String,
207 pub code: Result<OathCode, String>,
208 pub oath_type: OathType,
209 pub touch: bool,
210 pub algo: OathAlgo,
211 pub hidden: bool,
212 pub steam: bool,
213}
214pub struct OathController {
215 pub context: *mut ffi::nfc_context,
216 pub device: *mut ffi::nfc_device,
217}
218
219pub const INS_SELECT: u8 = 0xa4;
220pub const OATH_AID: [u8; 8] = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x21, 0x01, 0x01];
221
222pub fn tlv(tag: Tag, value: &[u8]) -> Vec<u8> {
223 let mut buf = vec![tag as u8];
224 let len = value.len();
225 if len < 0x80 {
226 buf.push(len as u8);
227 } else if len < 0xff {
228 buf.push(0x81);
229 buf.push(len as u8);
230 } else {
231 buf.push(0x82);
232 buf.write_u16::<BigEndian>(len as u16).unwrap();
233 }
234 buf.write(value).unwrap();
235 buf
236}
237
238pub fn parse_tlv(data: &[u8]) -> HashMap<u8, Vec<u8>> {
239 let mut rdr = Cursor::new(data);
240 let mut map = HashMap::new();
241 loop {
242 let tag = match rdr.read_u8() {
243 Ok(tag) => tag,
244 Err(_) => break,
245 };
246 let mut len: u16 = match rdr.read_u8() {
247 Ok(len) => len as u16,
248 Err(_) => break,
249 };
250 if len > 0x80 {
251 let n_bytes = len - 0x80;
252 if n_bytes == 1 {
253 len = match rdr.read_u8() {
254 Ok(len) => len as u16,
255 Err(_) => break,
256 };
257 } else if n_bytes == 2 {
258 len = match rdr.read_u16::<BigEndian>() {
259 Ok(len) => len,
260 Err(_) => break,
261 };
262 }
263 }
264 let mut dst = Vec::with_capacity(len as usize);
265 unsafe {
266 dst.set_len(len as usize);
267 }
268 match rdr.read_exact(dst.as_mut_slice()) {
269 Ok(_) => (),
270 Err(_) => break,
271 };
272 map.insert(tag, dst);
273 }
274 map
275}
276
277fn parse_list(resp: &[u8]) -> Result<Vec<OathCredential>, String> {
278 let mut rdr = Cursor::new(resp);
279
280 let mut result: Vec<OathCredential> = Vec::new();
281
282 while let Ok(tag) = rdr.read_u8() {
283 if tag != (Tag::NameList as u8) {
284 break;
285 }
286 let length = rdr.read_u8().or(Err("Missing length after tag"))?;
287
288 let type_code = rdr
289 .read_u8()
290 .or(Err("Malformed response, missing type code"))?;
291
292 let mut buf = vec![0; (length - 1) as usize];
293 rdr.read_exact(&mut buf)
294 .or(Err("Malformed response, incorrect key name length"))?;
295 result.push(OathCredential::new(
296 &String::from_utf8(buf).or(Err("Invalid key name"))?,
297 OathType::from_u8(type_code & (Mask::Type as u8)).ok_or("Invalid type")?,
298 false,
299 OathAlgo::from_u8(type_code & (Mask::Algo as u8)).ok_or("Invalid algorithm")?,
300 ));
301 }
302
303 return Ok(result);
304}
305
306pub fn time_challenge(datetime: Option<SystemTime>) -> Vec<u8> {
307 let ts = match datetime {
308 Some(datetime) => {
309 datetime
310 .duration_since(SystemTime::UNIX_EPOCH)
311 .unwrap()
312 .as_secs()
313 / 30
314 }
315 None => {
316 SystemTime::now()
317 .duration_since(SystemTime::UNIX_EPOCH)
318 .unwrap()
319 .as_secs()
320 / 30
321 }
322 };
323 let mut buf = vec![];
324 buf.write_u64::<BigEndian>(ts).unwrap();
325 buf
326}
327
328impl fmt::Display for OathCode {
329 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
330 const STEAM_CHAR_TABLE_LEN: u32 = 26;
331 const STEAM_CHAR_TABLE: [char; STEAM_CHAR_TABLE_LEN as usize] = [
332 '2', '3', '4', '5', '6', '7', '8', '9', 'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'M',
333 'N', 'P', 'Q', 'R', 'T', 'V', 'W', 'X', 'Y',
334 ];
335 let mut code = self.value;
336 if self.steam {
337 let mut str = String::new();
338 for _i in 0..5 {
339 str.push(
340 *STEAM_CHAR_TABLE
341 .get((code % STEAM_CHAR_TABLE_LEN) as usize)
342 .unwrap(),
343 );
344 code /= STEAM_CHAR_TABLE_LEN;
345 }
346 try!(fmt.write_str(&str));
347 } else {
348 let code = self.value % (10 as u32).pow(self.digits as u32);
349 match self.digits {
350 OathDigits::Six => write!(fmt, "{:06}", code),
351 OathDigits::Eight => write!(fmt, "{:08}", code),
352 }
353 .unwrap();
354 }
355 Ok(())
356 }
357}
358
359impl OathCredential {
360 pub fn new(name: &str, oath_type: OathType, touch: bool, algo: OathAlgo) -> OathCredential {
361 OathCredential {
362 name: name.to_string(),
363 code: Err("No code calculated yet".to_string()),
364 oath_type: oath_type,
365 touch: touch,
366 algo: algo,
367 hidden: name.starts_with("_hidden:"),
368 steam: name.starts_with("Steam:"),
369 }
370 }
371}
372
373impl OathController {
374 pub fn new() -> Result<OathController, String> {
375 let mut context = context::new();
376
377 if context.is_null() {
378 return Err("Unable to initialize new NFC context!".to_string());
379 }
380
381 nfc::init(&mut context);
382
383 debug!("libnfc version: {}", ::misc::version());
384
385 let device = nfc::open(context, ptr::null());
386 if device.is_null() {
387 return Err("Unable to initialize new NFC device!".to_string());
388 }
389
390 initiator::init(Box::new(device));
391
392 debug!("NFC reader: {} opened", device::get_name(device));
393
394 device::set_property_bool(device, ffi::Enum_Unnamed1::NP_AUTO_ISO14443_4, 1);
395
396 Ok(OathController {
397 context: context,
398 device: device,
399 })
400 }
401
402 pub fn close(&self) {
403 nfc::close(self.device);
404 }
405
406 pub fn poll(&self, duration: Option<Duration>) -> bool {
407 let start = Instant::now();
408
409 debug!("Polling for target...");
410 let modu = ffi::nfc_modulation {
411 nmt: ffi::nfc_modulation_type::NMT_ISO14443A,
412 nbr: ffi::nfc_baud_rate::NBR_106,
413 };
414 unsafe {
415 let mut target: ffi::nfc_target = mem::uninitialized();
416 while initiator::poll_target(self.device, &modu, 1, 1, 1, &mut target) <= 0 {
417 if let Some(duration) = duration {
418 if Instant::now() > (start + duration) {
419 debug!("Poll timed out");
420 return false;
421 }
422 }
423 }
424 while initiator::select_passive_target(
425 self.device,
426 modu,
427 (*target.nti.nai()).abtUid.as_mut_ptr(),
428 (*target.nti.nai()).szUidLen,
429 &mut target,
430 ) <= 0
431 {}
432 }
433 debug!("Target detected!");
434 return true;
435 }
436
437 pub fn send_apdu(
455 &self,
456 class: u8,
457 instruction: u8,
458 parameter1: u8,
459 parameter2: u8,
460 data: Option<&[u8]>,
461 ) -> Result<Vec<u8>, String> {
462 let mut tx_buf = vec![];
463 let nc = match data {
464 Some(ref data) => data.len(),
465 None => 0,
466 };
467 tx_buf.push(class);
469 tx_buf.push(instruction);
470 tx_buf.push(parameter1);
471 tx_buf.push(parameter2);
472
473 if nc > 255 {
475 tx_buf.push(0);
476 tx_buf.write_u16::<BigEndian>(nc as u16).unwrap();
477 } else if nc > 0 {
478 tx_buf.push(nc as u8);
479 }
480 if let Some(data) = data {
481 tx_buf.write(data).unwrap();
482 }
483
484 let mut s = String::new();
485 for byte in &tx_buf {
486 s += &format!("{:02X} ", byte);
487 }
488 debug!(">> {}", s);
489
490 let mut rx_buf = Vec::with_capacity(256);
491 let bytes_read = initiator::transceive_bytes(
492 self.device,
493 tx_buf.as_ptr(),
494 tx_buf.len(),
495 rx_buf.as_mut_ptr(),
496 256,
497 0,
498 );
499 if bytes_read < 0 {
500 return Err("Error no bytes were returned".to_string());
501 }
502
503 unsafe {
504 rx_buf.set_len(bytes_read as usize);
505 }
506
507 let mut s = String::new();
508 for byte in &rx_buf {
509 s += &format!("{:02X} ", byte);
510 }
511 debug!("<< {}", s);
512
513 {
514 let sw1 = match rx_buf.get((bytes_read - 2) as usize) {
515 Some(sw1) => sw1,
516 None => return Err("Error invalid bytes were returned".to_string()),
517 };
518 let sw2 = match rx_buf.get((bytes_read - 1) as usize) {
519 Some(sw2) => sw2,
520 None => return Err("Error invalid bytes were returned".to_string()),
521 };
522 if *sw1 != 0x90 || *sw2 != 0x00 {
523 return Err(format!("Error {:x} {:x}", sw1, sw2));
524 }
525 }
526 Ok(rx_buf)
527 }
528
529 pub fn list(&self) -> Result<Vec<OathCredential>, String> {
530 self.send_apdu(0, INS_SELECT, 0x04, 0, Some(&OATH_AID))?;
532
533 let resp = self.send_apdu(0, Ins::List as u8, 0, 0, None)?;
534 return parse_list(&resp);
535 }
536
537 pub fn calculate(&self, mut credential: OathCredential) -> OathCredential {
538 if let Err(err) = self.send_apdu(0, INS_SELECT, 0x04, 0, Some(&OATH_AID)) {
540 credential.code = Err(err);
541 return credential;
542 }
543
544 let mut data = tlv(Tag::Name, credential.name.as_bytes());
545 let datetime = SystemTime::now();
546 let challenge = time_challenge(Some(datetime));
547 data.write(&tlv(Tag::Challenge, &challenge)).unwrap();
548 let resp = match self.send_apdu(0, Ins::Calculate as u8, 0, 0x01, Some(&data)) {
549 Ok(resp) => resp,
550 Err(err) => {
551 credential.code = Err(err);
552 return credential;
553 }
554 };
555
556 let resp = parse_tlv(&resp[0..resp.len() - 2]);
557 let resp = match resp.get(&(Tag::TruncatedResponse as u8)) {
558 Some(resp) => resp,
559 None => {
560 credential.code = Err("Response tlv was invalid".to_string());
561 return credential;
562 }
563 };
564 let mut rdr = Cursor::new(resp);
565
566 let digits = match rdr.read_u8() {
567 Ok(digits) => {
568 let digits = match digits {
569 6 => OathDigits::Six,
570 8 => OathDigits::Eight,
571 _ => {
572 credential.code = Err("Digits can only be 6 or 8".to_owned());
573 return credential;
574 }
575 };
576 digits
577 }
578 Err(err) => {
579 credential.code = Err(err.to_string());
580 return credential;
581 }
582 };
583
584 let val = match rdr.read_u32::<BigEndian>() {
585 Ok(val) => val,
586 Err(err) => {
587 credential.code = Err(err.to_string());
588 return credential;
589 }
590 };
591 let expiration = ((datetime
592 .duration_since(SystemTime::UNIX_EPOCH)
593 .unwrap()
594 .as_secs()
595 + 30)
596 / 30)
597 * 30;
598
599 credential.code = Ok(OathCode {
600 digits: digits,
601 value: val,
602 expiration: expiration as u32,
603 steam: credential.steam,
604 });
605 credential
606 }
607}