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 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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
10
src/main.rs
10
src/main.rs
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue