diff --git a/flake.nix b/flake.nix index 63b115b..0fc4311 100644 --- a/flake.nix +++ b/flake.nix @@ -55,7 +55,6 @@ openssl.dev rust ]; - shellHook = "ln -s ${rust} ./.direnv/rust"; }; }); diff --git a/src/lib_ykoath2/constants.rs b/src/lib_ykoath2/constants.rs index 2e23121..40ec86e 100644 --- a/src/lib_ykoath2/constants.rs +++ b/src/lib_ykoath2/constants.rs @@ -83,7 +83,7 @@ impl HashAlgo { } } -#[derive(Debug, PartialEq, Copy, Clone, Eq)] +#[derive(Debug, PartialEq, Copy, Clone, Eq, Hash)] #[repr(u8)] pub enum OathType { Totp = 0x10, @@ -96,7 +96,7 @@ pub enum OathDigits { Eight = 8, } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Hash, Eq)] pub struct OathCodeDisplay { code: u32, digits: u8, diff --git a/src/lib_ykoath2/legacy.rs b/src/lib_ykoath2/legacy.rs deleted file mode 100644 index ae887a9..0000000 --- a/src/lib_ykoath2/legacy.rs +++ /dev/null @@ -1,76 +0,0 @@ -#[crate_type = "lib"] -extern crate byteorder; -use crate::lib_ykoath2::*; -/// Utilities for interacting with YubiKey OATH/TOTP functionality -extern crate pcsc; -use base32::Alphabet; -use iso7816_tlv::simple::{Tag as TlvTag, Tlv}; -use openssl::hash::MessageDigest; -use sha1::Sha1; - -use ouroboros::self_referencing; -use regex::Regex; -use std::collections::HashMap; -use std::iter::zip; -use std::str::{self}; - -use apdu_core::{Command, Response}; - -use base64::{engine::general_purpose, Engine as _}; -use hmac::{Hmac, Mac}; -use openssl::pkcs5::pbkdf2_hmac; -use pcsc::{Card, Transaction}; - -use std::cmp::Ordering; -use std::hash::{Hash, Hasher}; - -use byteorder::{BigEndian, ByteOrder, WriteBytesExt}; -use std::ffi::CString; -use std::time::SystemTime; - -#[derive(Debug, PartialEq)] -pub struct LegacyOathCredential { - pub name: String, - pub code: OathCode, - // TODO: Support this stuff - // pub oath_type: OathType, - // pub touch: bool, - // pub algo: OathAlgo, - // pub hidden: bool, - // pub steam: bool, -} - -impl LegacyOathCredential { - pub fn new(name: &str, code: OathCode) -> LegacyOathCredential { - LegacyOathCredential { - name: name.to_string(), - code: code, - // oath_type: oath_type, - // touch: touch, - // algo: algo, - // hidden: name.starts_with("_hidden:"), - // steam: name.starts_with("Steam:"), - } - } -} - -pub fn legacy_format_code(code: u32, digits: OathDigits) -> String { - let mut code_string = code.to_string(); - - match digits { - OathDigits::Six => { - if code_string.len() <= 6 { - format!("{:0>6}", code_string) - } else { - code_string.split_off(code_string.len() - 6) - } - } - OathDigits::Eight => { - if code_string.len() <= 8 { - format!("{:0>8}", code_string) - } else { - code_string.split_off(code_string.len() - 8) - } - } - } -} diff --git a/src/lib_ykoath2/mod.rs b/src/lib_ykoath2/mod.rs index 93a1e1f..2ef9b7f 100644 --- a/src/lib_ykoath2/mod.rs +++ b/src/lib_ykoath2/mod.rs @@ -3,15 +3,12 @@ mod constants; use constants::*; mod transaction; use transaction::*; -mod legacy; -pub use legacy::*; /// Utilities for interacting with YubiKey OATH/TOTP functionality extern crate pcsc; use base32::Alphabet; use openssl::hash::MessageDigest; use sha1::Sha1; -use std::iter::zip; use std::str::{self}; use base64::{engine::general_purpose, Engine as _}; @@ -21,7 +18,7 @@ use openssl::pkcs5::pbkdf2_hmac; use std::cmp::Ordering; use std::hash::{Hash, Hasher}; -use byteorder::{BigEndian, ByteOrder, WriteBytesExt}; +use byteorder::{BigEndian, WriteBytesExt}; use std::time::SystemTime; pub struct YubiKey<'a> { @@ -71,31 +68,25 @@ pub struct OathCode { pub struct OathCredential<'a> { device_id: &'a str, id: Vec, - issuer: Option<&'a str>, - name: &'a str, + issuer: Option, + name: String, oath_type: OathType, period: u64, - touch_required: Option, - // TODO: Support this stuff - // pub oath_type: OathType, - // pub touch: bool, - // pub algo: OathAlgo, - // pub hidden: bool, - // pub steam: bool, + touch_required: bool, + pub code: Option, } impl<'a> OathCredential<'a> { - /* pub fn new(name: &str, code: OathCode) -> OathCredential { - OathCredential { - name, - code, - // oath_type: oath_type, - // touch: touch, - // algo: algo, - // hidden: name.starts_with("_hidden:"), - // steam: name.starts_with("Steam:"), - } - } */ + pub fn display(&self) -> String { + format!( + "{}: {}", + self.name, + self.code + .as_ref() + .map(OathCodeDisplay::display) + .unwrap_or("".to_string()) + ) + } } impl<'a> PartialOrd for OathCredential<'a> { @@ -103,7 +94,7 @@ impl<'a> PartialOrd for OathCredential<'a> { let a = ( self.issuer .clone() - .unwrap_or_else(|| self.name) + .unwrap_or_else(|| self.name.clone()) .to_lowercase(), self.name.to_lowercase(), ); @@ -111,7 +102,7 @@ impl<'a> PartialOrd for OathCredential<'a> { other .issuer .clone() - .unwrap_or_else(|| other.name) + .unwrap_or_else(|| other.name.clone()) .to_lowercase(), other.name.to_lowercase(), ); @@ -148,7 +139,7 @@ fn _format_cred_id(issuer: Option<&str>, name: &str, oath_type: OathType, period } // Function to parse the credential ID -fn _parse_cred_id(cred_id: &[u8], oath_type: OathType) -> (Option, String, u32) { +fn _parse_cred_id(cred_id: &[u8], oath_type: OathType) -> (Option, String, u64) { let data = match str::from_utf8(cred_id) { Ok(d) => d.to_string(), Err(_) => return (None, String::new(), 0), // Handle invalid UTF-8 @@ -163,9 +154,13 @@ fn _parse_cred_id(cred_id: &[u8], oath_type: OathType) -> (Option, Strin DEFAULT_PERIOD }; - return (Some(caps[4].to_string()), caps[5].to_string(), period); + return ( + Some(caps[4].to_string()), + caps[5].to_string(), + period.into(), + ); } else { - return (None, data, DEFAULT_PERIOD); + return (None, data, DEFAULT_PERIOD.into()); } } else { let (issuer, rest) = if let Some(pos) = data.find(':') { @@ -302,7 +297,7 @@ impl<'a> OathSession<'a> { } /// Read the OATH codes from the device - pub fn get_oath_codes(&self) -> Result, String> { + pub fn get_oath_codes(&self) -> Result, String> { // Request OATH codes from device let response = self.transaction_context.apdu_read_all( 0, @@ -317,23 +312,34 @@ impl<'a> OathSession<'a> { let mut key_buffer = Vec::new(); - let info_map = tlv_to_lists(response?); - for (name_bytes, metadata) in zip( - info_map.get(&(Tag::Name as u8)).unwrap(), // todo: error handling - info_map.get(&(Tag::TruncatedResponse as u8)).unwrap(), - ) { - assert!(metadata.len() == 5); - let display = OathCodeDisplay::new(metadata[..].try_into().unwrap()); - let name = str::from_utf8(&name_bytes).unwrap(); - key_buffer.push(LegacyOathCredential::new( + for (cred_id, meta) in TlvZipIter::from_vec(response?) { + // let name = str::from_utf8(&cred_id.value()).unwrap(); + let oath_type = if Into::::into(meta.tag()) == (Tag::Hotp as u8) { + OathType::Hotp + } else { + OathType::Totp + }; + let touch = Into::::into(meta.tag()) == (Tag::Touch as u8); // touch only works with totp, this is intended + let (issuer, name, period) = _parse_cred_id(cred_id.value(), oath_type); + let cred = OathCredential { + device_id: &self.name, + id: meta.value().to_vec(), + issuer, name, - OathCode { - display, - valid_from: 0, - valid_to: 0x7FFFFFFFFFFFFFFF, + period, + touch_required: touch, + oath_type, + code: if Into::::into(meta.tag()) == (Tag::TruncatedResponse as u8) { + assert!(meta.value().len() == 5); + let display = OathCodeDisplay::new(meta.value()[..].try_into().unwrap()); + Some(display) + } else { + None }, - )); + }; + key_buffer.push(cred); } + return Ok(key_buffer); } } diff --git a/src/lib_ykoath2/transaction.rs b/src/lib_ykoath2/transaction.rs index 3ff7c14..cf4a216 100644 --- a/src/lib_ykoath2/transaction.rs +++ b/src/lib_ykoath2/transaction.rs @@ -3,30 +3,16 @@ extern crate byteorder; use crate::lib_ykoath2::*; /// Utilities for interacting with YubiKey OATH/TOTP functionality extern crate pcsc; -use base32::Alphabet; use iso7816_tlv::simple::{Tag as TlvTag, Tlv}; -use openssl::hash::MessageDigest; -use sha1::Sha1; - use ouroboros::self_referencing; -use regex::Regex; use std::collections::HashMap; -use std::iter::zip; use std::str::{self}; use apdu_core::{Command, Response}; -use base64::{engine::general_purpose, Engine as _}; -use hmac::{Hmac, Mac}; -use openssl::pkcs5::pbkdf2_hmac; use pcsc::{Card, Transaction}; -use std::cmp::Ordering; -use std::hash::{Hash, Hasher}; - -use byteorder::{BigEndian, ByteOrder, WriteBytesExt}; use std::ffi::CString; -use std::time::SystemTime; pub struct ApduResponse { pub buf: Vec, @@ -192,36 +178,75 @@ pub fn to_tlv(tag: Tag, value: &[u8]) -> Vec { } pub fn tlv_to_map(data: Vec) -> HashMap> { - let mut buf: &[u8] = data.leak(); let mut parsed_manual = HashMap::new(); - while !buf.is_empty() { - let (r, remaining) = Tlv::parse(buf); - buf = remaining; - if let Ok(res) = r { - parsed_manual.insert(res.tag().into(), res.value().to_vec()); - } else { - println!("tlv parsing error"); - break; // Exit if parsing fails - } + for res in TlvIter::from_vec(data) { + parsed_manual.insert(res.tag().into(), res.value().to_vec()); } return parsed_manual; } -pub fn tlv_to_lists(data: Vec) -> HashMap>> { - let mut buf: &[u8] = data.leak(); - let mut parsed_manual: HashMap>> = HashMap::new(); - while !buf.is_empty() { - let (r, remaining) = Tlv::parse(buf); - buf = remaining; - if let Ok(res) = r { - parsed_manual - .entry(res.tag().into()) - .or_insert_with(Vec::new) - .push(res.value().to_vec()); - } else { - println!("tlv parsing error"); - break; // Exit if parsing fails +pub struct TlvZipIter<'a> { + iter: TlvIter<'a>, +} + +impl<'a> TlvZipIter<'a> { + pub fn new(value: &'a [u8]) -> Self { + TlvZipIter { + iter: TlvIter::new(value).into_iter(), } } + pub fn from_vec(value: Vec) -> Self { + TlvZipIter { + iter: TlvIter::from_vec(value).into_iter(), + } + } + + pub fn from_tlv_iter(value: TlvIter<'a>) -> Self { + TlvZipIter { iter: value } + } +} + +impl<'a> Iterator for TlvZipIter<'a> { + type Item = (Tlv, Tlv); + fn next(&mut self) -> Option { + return Some((self.iter.next()?, self.iter.next()?)); + } +} + +#[derive(Copy, Clone)] +pub struct TlvIter<'a> { + buf: &'a [u8], +} + +impl<'a> TlvIter<'a> { + pub fn new(value: &'a [u8]) -> Self { + TlvIter { buf: value } + } + pub fn from_vec(value: Vec) -> Self { + TlvIter { buf: value.leak() } + } +} + +impl<'a> Iterator for TlvIter<'a> { + type Item = Tlv; + + fn next(&mut self) -> Option { + if self.buf.is_empty() { + return None; + } + let (r, remaining) = Tlv::parse(self.buf); + self.buf = remaining; + return r.ok(); + } +} + +pub fn tlv_to_lists(data: Vec) -> HashMap>> { + let mut parsed_manual: HashMap>> = HashMap::new(); + for res in TlvIter::from_vec(data) { + parsed_manual + .entry(res.tag().into()) + .or_insert_with(Vec::new) + .push(res.value().to_vec()); + } return parsed_manual; } diff --git a/src/main.rs b/src/main.rs index c643450..17d0136 100644 --- a/src/main.rs +++ b/src/main.rs @@ -52,20 +52,7 @@ fn main() { // Enumerate the OATH codes for oath in codes { - let code = oath.code.display.display(); - let name_clone = oath.name.clone(); - let mut label_vec: Vec<&str> = name_clone.split(":").collect(); - let mut code_entry_label: String = String::from(label_vec.remove(0)); - - if label_vec.len() > 0 { - code_entry_label.push_str(" ("); - code_entry_label.push_str(&label_vec.join("")); - code_entry_label.push_str(") "); - } - - code_entry_label.push_str(&code.clone().to_owned()); - - println!("Found OATH label: {}", code_entry_label); + println!("Found OATH label: {}", oath.display()); } } }