mirror of
https://github.com/LordGrimmauld/yubi-oath-rs.git
synced 2025-03-03 21:34:40 +01:00
better formatting, listing codes without calculate
This commit is contained in:
parent
0c877a859a
commit
85b8ccea35
4 changed files with 87 additions and 48 deletions
|
@ -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<Self> {
|
||||
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()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display(&self) -> String {
|
||||
format!("{:01$}", self.code, usize::from(self.digits))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<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(
|
||||
&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<Vec<RefreshableOathCredential>, 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<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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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::<u8>::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::<u8>::into(oath_type_tag));
|
||||
}
|
||||
|
||||
pub fn format_cred_id(&self) -> Vec<u8> {
|
||||
|
@ -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<u8> {
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
10
src/main.rs
10
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue