From 85b8ccea35004dee1ec24c7bf1059da0676ef488 Mon Sep 17 00:00:00 2001 From: Grimmauld Date: Wed, 12 Feb 2025 18:18:33 +0100 Subject: [PATCH] better formatting, listing codes without calculate --- src/lib_ykoath2/constants.rs | 14 ++++--- src/lib_ykoath2/mod.rs | 57 +++++++++++++++++++++------- src/lib_ykoath2/oath_credentialid.rs | 54 +++++++++++++------------- src/main.rs | 10 +++-- 4 files changed, 87 insertions(+), 48 deletions(-) diff --git a/src/lib_ykoath2/constants.rs b/src/lib_ykoath2/constants.rs index a18957b..27d8709 100644 --- a/src/lib_ykoath2/constants.rs +++ b/src/lib_ykoath2/constants.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use iso7816_tlv::simple::Tlv; use sha1::{Digest, Sha1}; use sha2::{Sha256, Sha512}; @@ -153,12 +155,18 @@ pub enum OathDigits { Eight = 8, } -#[derive(Debug, PartialEq, Hash, Eq)] +#[derive(Debug, PartialEq, Hash, Eq, Copy, Clone)] pub struct OathCodeDisplay { code: u32, digits: u8, } +impl Display for OathCodeDisplay { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("{:01$}", self.code, usize::from(self.digits))) + } +} + impl OathCodeDisplay { pub fn from_tlv(tlv: Tlv) -> Option { if Into::::into(tlv.tag()) == (Tag::TruncatedResponse as u8) && tlv.value().len() == 5 { @@ -183,8 +191,4 @@ impl OathCodeDisplay { code: u32::from_be_bytes((&bytes[1..5]).try_into().unwrap()), } } - - pub fn display(&self) -> String { - format!("{:01$}", self.code, usize::from(self.digits)) - } } diff --git a/src/lib_ykoath2/mod.rs b/src/lib_ykoath2/mod.rs index 008209e..4433066 100644 --- a/src/lib_ykoath2/mod.rs +++ b/src/lib_ykoath2/mod.rs @@ -12,6 +12,7 @@ use pbkdf2::pbkdf2_hmac_array; use sha1::Sha1; use std::{ + fmt::Display, str::{self}, time::Duration, }; @@ -79,6 +80,16 @@ pub struct RefreshableOathCredential<'a> { refresh_provider: &'a OathSession<'a>, } +impl<'a> Display for RefreshableOathCredential<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(c) = self.code { + f.write_fmt(format_args!("{}: {}", self.cred.id_data, c)) + } else { + f.write_fmt(format_args!("{}", self.cred.id_data)) + } + } +} + impl<'a> RefreshableOathCredential<'a> { pub fn new(cred: OathCredential, refresh_provider: &'a OathSession<'a>) -> Self { RefreshableOathCredential { @@ -96,17 +107,6 @@ impl<'a> RefreshableOathCredential<'a> { RefreshableOathCredential::format_validity_time_frame(&self, timestamp); } - pub fn display(&self) -> String { - format!( - "{}: {}", - self.cred.id_data.name, - self.code - .as_ref() - .map(OathCodeDisplay::display) - .unwrap_or("".to_string()) - ) - } - pub fn refresh(&mut self) { let timestamp = SystemTime::now(); let refresh_result = self @@ -177,6 +177,19 @@ impl<'a> OathSession<'a> { self.version } + pub fn delete_code( + &self, + cred: OathCredential, + ) -> Result { + self.transaction_context.apdu( + 0, + Instruction::Delete as u8, + 0, + 0, + Some(&to_tlv(Tag::Name, &cred.id_data.format_cred_id())), + ) + } + pub fn calculate_code( &self, cred: OathCredential, @@ -216,8 +229,8 @@ impl<'a> OathSession<'a> { )) } - /// Read the OATH codes from the device - pub fn get_oath_codes( + /// Read the OATH codes from the device, calculate TOTP codes that don't need touch + pub fn calculate_oath_codes( &self, ) -> Result, FormattableErrorResponse> { let timestamp = SystemTime::now(); @@ -252,6 +265,24 @@ impl<'a> OathSession<'a> { key_buffer.push(refreshable_cred); } + return Ok(key_buffer); + } + pub fn list_oath_codes(&self) -> Result, FormattableErrorResponse> { + // Request OATH codes from device + let response = + self.transaction_context + .apdu_read_all(0, Instruction::List as u8, 0, 0, None); + + let mut key_buffer = Vec::new(); + + for cred_id in TlvIter::from_vec(response?) { + let id_data = CredentialIDData::from_bytes( + &cred_id.value()[1..], + *cred_id.value().get(0).unwrap_or(&0u8) & 0xf0, + ); + key_buffer.push(id_data); + } + return Ok(key_buffer); } } diff --git a/src/lib_ykoath2/oath_credentialid.rs b/src/lib_ykoath2/oath_credentialid.rs index 4ef4dc7..df7ff2b 100644 --- a/src/lib_ykoath2/oath_credentialid.rs +++ b/src/lib_ykoath2/oath_credentialid.rs @@ -4,7 +4,10 @@ use crate::lib_ykoath2::*; extern crate pcsc; use regex::Regex; -use std::str::{self}; +use std::{ + fmt::Write, + str::{self}, +}; #[derive(Debug, Eq, PartialEq, Clone)] pub struct CredentialIDData { @@ -14,20 +17,18 @@ pub struct CredentialIDData { pub period: u32, } +impl Display for CredentialIDData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(i) = self.issuer.clone() { + f.write_fmt(format_args!("{}: ", i))?; + } + f.write_str(&self.name) + } +} + impl CredentialIDData { pub fn from_tlv(id_bytes: &[u8], oath_type_tag: iso7816_tlv::simple::Tag) -> Self { - let oath_type = if Into::::into(oath_type_tag) == (Tag::Hotp as u8) { - OathType::Hotp - } else { - OathType::Totp - }; - let (issuer, name, period) = CredentialIDData::parse_cred_id(id_bytes, oath_type); - return CredentialIDData { - issuer, - name, - period, - oath_type, - }; + return CredentialIDData::from_bytes(id_bytes, Into::::into(oath_type_tag)); } pub fn format_cred_id(&self) -> Vec { @@ -70,20 +71,19 @@ impl CredentialIDData { }); } } -} -pub struct CredentialData { - id_data: CredentialIDData, - hash_algorithm: HashAlgo, - // secret: bytes, - digits: OathDigits, // = DEFAULT_DIGITS, - counter: u32, // = DEFAULT_IMF, -} - -impl CredentialData { - // TODO: parse_uri - - pub fn get_id(&self) -> Vec { - return self.id_data.format_cred_id(); + pub(crate) fn from_bytes(id_bytes: &[u8], tag: u8) -> CredentialIDData { + let oath_type = if tag == (Tag::Hotp as u8) { + OathType::Hotp + } else { + OathType::Totp + }; + let (issuer, name, period) = CredentialIDData::parse_cred_id(id_bytes, oath_type); + return CredentialIDData { + issuer, + name, + period, + oath_type, + }; } } diff --git a/src/main.rs b/src/main.rs index e22abcf..c6f19b9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,7 +38,11 @@ fn main() { println!("Found device with label {}", device_label); let session = OathSession::new(yubikey); println!("YubiKey version is {:?}", session.get_version()); - let codes = match session.get_oath_codes() { + for c in session.list_oath_codes().unwrap() { + println!("{}", c); + } + + let codes = match session.calculate_oath_codes() { Ok(codes) => codes, Err(e) => { println!("ERROR {}", e); @@ -51,12 +55,12 @@ fn main() { println!("No credentials on device {}", device_label); } - thread::sleep(time::Duration::from_secs(45)); // show refresh is working + thread::sleep(time::Duration::from_secs(5)); // show refresh is working // Enumerate the OATH codes for oath in codes { // let recalculated = session.calculate_code(oath.cred, None).unwrap(); - println!("Found OATH label: {}", oath.get_or_refresh().display()); + println!("Found OATH label: {}", oath.get_or_refresh()); } } }