better formatting, listing codes without calculate

This commit is contained in:
Grimmauld 2025-02-12 18:18:33 +01:00
parent 0c877a859a
commit 85b8ccea35
No known key found for this signature in database
4 changed files with 87 additions and 48 deletions

View file

@ -1,3 +1,5 @@
use std::fmt::Display;
use iso7816_tlv::simple::Tlv; use iso7816_tlv::simple::Tlv;
use sha1::{Digest, Sha1}; use sha1::{Digest, Sha1};
use sha2::{Sha256, Sha512}; use sha2::{Sha256, Sha512};
@ -153,12 +155,18 @@ pub enum OathDigits {
Eight = 8, Eight = 8,
} }
#[derive(Debug, PartialEq, Hash, Eq)] #[derive(Debug, PartialEq, Hash, Eq, Copy, Clone)]
pub struct OathCodeDisplay { pub struct OathCodeDisplay {
code: u32, code: u32,
digits: u8, 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 { impl OathCodeDisplay {
pub fn from_tlv(tlv: Tlv) -> Option<Self> { pub fn from_tlv(tlv: Tlv) -> Option<Self> {
if Into::<u8>::into(tlv.tag()) == (Tag::TruncatedResponse as u8) && tlv.value().len() == 5 { if Into::<u8>::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()), code: u32::from_be_bytes((&bytes[1..5]).try_into().unwrap()),
} }
} }
pub fn display(&self) -> String {
format!("{:01$}", self.code, usize::from(self.digits))
}
} }

View file

@ -12,6 +12,7 @@ use pbkdf2::pbkdf2_hmac_array;
use sha1::Sha1; use sha1::Sha1;
use std::{ use std::{
fmt::Display,
str::{self}, str::{self},
time::Duration, time::Duration,
}; };
@ -79,6 +80,16 @@ pub struct RefreshableOathCredential<'a> {
refresh_provider: &'a OathSession<'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> { impl<'a> RefreshableOathCredential<'a> {
pub fn new(cred: OathCredential, refresh_provider: &'a OathSession<'a>) -> Self { pub fn new(cred: OathCredential, refresh_provider: &'a OathSession<'a>) -> Self {
RefreshableOathCredential { RefreshableOathCredential {
@ -96,17 +107,6 @@ impl<'a> RefreshableOathCredential<'a> {
RefreshableOathCredential::format_validity_time_frame(&self, timestamp); 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) { pub fn refresh(&mut self) {
let timestamp = SystemTime::now(); let timestamp = SystemTime::now();
let refresh_result = self let refresh_result = self
@ -177,6 +177,19 @@ impl<'a> OathSession<'a> {
self.version self.version
} }
pub fn delete_code(
&self,
cred: OathCredential,
) -> Result<ApduResponse, FormattableErrorResponse> {
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( pub fn calculate_code(
&self, &self,
cred: OathCredential, cred: OathCredential,
@ -216,8 +229,8 @@ impl<'a> OathSession<'a> {
)) ))
} }
/// Read the OATH codes from the device /// Read the OATH codes from the device, calculate TOTP codes that don't need touch
pub fn get_oath_codes( pub fn calculate_oath_codes(
&self, &self,
) -> Result<Vec<RefreshableOathCredential>, FormattableErrorResponse> { ) -> Result<Vec<RefreshableOathCredential>, FormattableErrorResponse> {
let timestamp = SystemTime::now(); let timestamp = SystemTime::now();
@ -252,6 +265,24 @@ impl<'a> OathSession<'a> {
key_buffer.push(refreshable_cred); key_buffer.push(refreshable_cred);
} }
return Ok(key_buffer);
}
pub fn list_oath_codes(&self) -> Result<Vec<CredentialIDData>, 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); return Ok(key_buffer);
} }
} }

View file

@ -4,7 +4,10 @@ use crate::lib_ykoath2::*;
extern crate pcsc; extern crate pcsc;
use regex::Regex; use regex::Regex;
use std::str::{self}; use std::{
fmt::Write,
str::{self},
};
#[derive(Debug, Eq, PartialEq, Clone)] #[derive(Debug, Eq, PartialEq, Clone)]
pub struct CredentialIDData { pub struct CredentialIDData {
@ -14,20 +17,18 @@ pub struct CredentialIDData {
pub period: u32, 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 { impl CredentialIDData {
pub fn from_tlv(id_bytes: &[u8], oath_type_tag: iso7816_tlv::simple::Tag) -> Self { pub fn from_tlv(id_bytes: &[u8], oath_type_tag: iso7816_tlv::simple::Tag) -> Self {
let oath_type = if Into::<u8>::into(oath_type_tag) == (Tag::Hotp as u8) { return CredentialIDData::from_bytes(id_bytes, Into::<u8>::into(oath_type_tag));
OathType::Hotp
} else {
OathType::Totp
};
let (issuer, name, period) = CredentialIDData::parse_cred_id(id_bytes, oath_type);
return CredentialIDData {
issuer,
name,
period,
oath_type,
};
} }
pub fn format_cred_id(&self) -> Vec<u8> { pub fn format_cred_id(&self) -> Vec<u8> {
@ -70,20 +71,19 @@ impl CredentialIDData {
}); });
} }
} }
}
pub struct CredentialData { pub(crate) fn from_bytes(id_bytes: &[u8], tag: u8) -> CredentialIDData {
id_data: CredentialIDData, let oath_type = if tag == (Tag::Hotp as u8) {
hash_algorithm: HashAlgo, OathType::Hotp
// secret: bytes, } else {
digits: OathDigits, // = DEFAULT_DIGITS, OathType::Totp
counter: u32, // = DEFAULT_IMF, };
} let (issuer, name, period) = CredentialIDData::parse_cred_id(id_bytes, oath_type);
return CredentialIDData {
impl CredentialData { issuer,
// TODO: parse_uri name,
period,
pub fn get_id(&self) -> Vec<u8> { oath_type,
return self.id_data.format_cred_id(); };
} }
} }

View file

@ -38,7 +38,11 @@ fn main() {
println!("Found device with label {}", device_label); println!("Found device with label {}", device_label);
let session = OathSession::new(yubikey); let session = OathSession::new(yubikey);
println!("YubiKey version is {:?}", session.get_version()); 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, Ok(codes) => codes,
Err(e) => { Err(e) => {
println!("ERROR {}", e); println!("ERROR {}", e);
@ -51,12 +55,12 @@ fn main() {
println!("No credentials on device {}", device_label); 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 // Enumerate the OATH codes
for oath in codes { for oath in codes {
// let recalculated = session.calculate_code(oath.cred, None).unwrap(); // 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());
} }
} }
} }