refactor: cleanup unused code, drop lib_ prefix from name

This commit is contained in:
Grimmauld 2025-02-15 16:24:40 +01:00
parent 6a88a4f3de
commit d29370d398
No known key found for this signature in database
6 changed files with 135 additions and 157 deletions

34
Cargo.lock generated
View file

@ -126,23 +126,6 @@ dependencies = [
"untrusted", "untrusted",
] ]
[[package]]
name = "lib_ykoath2"
version = "0.1.0"
dependencies = [
"apdu-core",
"base64",
"getrandom",
"hmac",
"iso7816-tlv",
"ouroboros",
"pbkdf2",
"pcsc",
"regex",
"sha1",
"sha2",
]
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.169" version = "0.2.169"
@ -431,3 +414,20 @@ name = "yansi"
version = "1.0.1" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]]
name = "ykoath2"
version = "0.1.0"
dependencies = [
"apdu-core",
"base64",
"getrandom",
"hmac",
"iso7816-tlv",
"ouroboros",
"pbkdf2",
"pcsc",
"regex",
"sha1",
"sha2",
]

View file

@ -16,7 +16,7 @@ name = "example"
path = "./src/example.rs" path = "./src/example.rs"
[package] [package]
name = "lib_ykoath2" name = "ykoath2"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
authors = ["Grimmauld <grimmauld@grimmauld.de>"] authors = ["Grimmauld <grimmauld@grimmauld.de>"]

View file

@ -1,9 +1,9 @@
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
use lib_ykoath2::constants::{HashAlgo, OathDigits, OathType, DEFAULT_PERIOD}; use ykoath2::constants::{HashAlgo, OathDigits, OathType, DEFAULT_PERIOD};
use lib_ykoath2::oath_credential::OathCredential; use ykoath2::oath_credential::OathCredential;
use lib_ykoath2::oath_credentialid::CredentialIDData; use ykoath2::oath_credentialid::CredentialIDData;
use lib_ykoath2::OathSession; use ykoath2::OathSession;
// use crate::args::Cli; // use crate::args::Cli;
// use clap::Parser; // use clap::Parser;
@ -35,11 +35,11 @@ fn main() {
println!("Found device with label {}", device_label); println!("Found device with label {}", device_label);
let mut session = OathSession::new(yubikey).unwrap(); let mut session = OathSession::new(yubikey).unwrap();
// session.set_key(&session.derive_key("1234")).unwrap(); /* session.set_key(&session.derive_key("1234")).unwrap();
// session.unlock_session(&session.derive_key("1234")).unwrap(); session.unlock_session(&session.derive_key("1234")).unwrap();
// session.unset_key().unwrap(); session.unset_key().unwrap();
/* let cred = OathCredential { let cred = OathCredential {
device_id: session.name.clone(), device_id: session.name.clone(),
id_data: CredentialIDData { id_data: CredentialIDData {
name: "test_cred".to_string(), name: "test_cred".to_string(),

View file

@ -1,38 +1,26 @@
#![allow(unused)]
pub mod constants; pub mod constants;
use constants::*;
mod transaction;
use transaction::*;
pub mod oath_credential; pub mod oath_credential;
pub mod oath_credentialid; pub mod oath_credentialid;
/// Utilities for interacting with YubiKey OATH/TOTP functionality mod refreshable_oath_credential;
use std::{ mod transaction;
fmt::Display,
ops::{Range, RangeInclusive},
time::{Duration, Instant, SystemTime},
};
use base64::{engine::general_purpose, Engine as _}; use constants::*;
use hmac::{Hmac, Mac};
use oath_credential::*; use oath_credential::*;
use oath_credentialid::*; use oath_credentialid::*;
use refreshable_oath_credential::*;
use transaction::*;
fn _get_device_id(salt: Vec<u8>) -> String { use std::time::{Duration, SystemTime};
let result = HashAlgo::Sha256.get_hash_fun()(salt.leak());
// Get the first 16 bytes of the hash use hmac::{Hmac, Mac};
let hash_16_bytes = &result[..16];
// Base64 encode the result and remove padding ('=') fn hmac_sha1(key: &[u8], message: &[u8]) -> Vec<u8> {
general_purpose::URL_SAFE_NO_PAD.encode(hash_16_bytes)
}
fn _hmac_sha1(key: &[u8], message: &[u8]) -> Vec<u8> {
let mut mac = Hmac::<sha1::Sha1>::new_from_slice(key).expect("Invalid key length"); let mut mac = Hmac::<sha1::Sha1>::new_from_slice(key).expect("Invalid key length");
mac.update(message); mac.update(message);
mac.finalize().into_bytes().to_vec() mac.finalize().into_bytes().to_vec()
} }
fn _hmac_shorten_key(key: &[u8], algo: HashAlgo) -> Vec<u8> { fn hmac_shorten_key(key: &[u8], algo: HashAlgo) -> Vec<u8> {
if key.len() > algo.digest_size() { if key.len() > algo.digest_size() {
algo.get_hash_fun()(key) algo.get_hash_fun()(key)
} else { } else {
@ -40,81 +28,14 @@ fn _hmac_shorten_key(key: &[u8], algo: HashAlgo) -> Vec<u8> {
} }
} }
pub struct RefreshableOathCredential<'a> { fn time_challenge(timestamp: Option<SystemTime>, period: Option<Duration>) -> [u8; 8] {
pub cred: OathCredential, (timestamp
pub code: Option<OathCodeDisplay>, .unwrap_or_else(SystemTime::now)
pub valid_timeframe: Range<SystemTime>, .duration_since(SystemTime::UNIX_EPOCH)
refresh_provider: &'a OathSession, .as_ref()
} .map_or(0, Duration::as_secs)
/ period.unwrap_or(DEFAULT_PERIOD).as_secs())
impl Display for RefreshableOathCredential<'_> { .to_be_bytes()
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) -> Self {
RefreshableOathCredential {
cred,
code: None,
valid_timeframe: SystemTime::UNIX_EPOCH..SystemTime::UNIX_EPOCH,
refresh_provider,
}
}
pub fn force_update(&mut self, code: Option<OathCodeDisplay>, timestamp: SystemTime) {
self.code = code;
self.valid_timeframe =
RefreshableOathCredential::format_validity_time_frame(self, timestamp);
}
pub fn refresh(&mut self) {
let timestamp = SystemTime::now();
let refresh_result = self
.refresh_provider
.calculate_code(&self.cred, Some(timestamp))
.ok();
self.force_update(refresh_result, timestamp);
}
pub fn get_or_refresh(mut self) -> RefreshableOathCredential<'a> {
if !self.is_valid() {
self.refresh();
}
self
}
pub fn is_valid(&self) -> bool {
self.valid_timeframe.contains(&SystemTime::now())
}
fn format_validity_time_frame(&self, timestamp: SystemTime) -> Range<SystemTime> {
match self.cred.id_data.oath_type {
OathType::Totp => {
let timestamp_seconds = timestamp
.duration_since(SystemTime::UNIX_EPOCH)
.as_ref()
.map_or(0, Duration::as_secs);
let time_step = timestamp_seconds / (self.cred.id_data.period.as_secs());
let valid_from = SystemTime::UNIX_EPOCH
.checked_add(self.cred.id_data.period.saturating_mul(time_step as u32))
.unwrap();
let valid_to = valid_from.checked_add(self.cred.id_data.period).unwrap();
valid_from..valid_to
}
OathType::Hotp => {
timestamp
..SystemTime::UNIX_EPOCH
.checked_add(Duration::from_secs(u64::MAX))
.unwrap()
}
}
}
} }
pub struct OathSession { pub struct OathSession {
@ -180,7 +101,7 @@ impl OathSession {
return Ok(()); return Ok(());
} }
let hmac = _hmac_sha1(key, &chal); let hmac = hmac_sha1(key, &chal);
let random_chal = getrandom::u64()?.to_be_bytes(); let random_chal = getrandom::u64()?.to_be_bytes();
let data = &[ let data = &[
to_tlv(Tag::Response, &hmac), to_tlv(Tag::Response, &hmac),
@ -190,7 +111,7 @@ impl OathSession {
let resp = let resp =
self.transaction_context self.transaction_context
.apdu(0, Instruction::Validate as u8, 0, 0, Some(data))?; .apdu(0, Instruction::Validate as u8, 0, 0, Some(data))?;
let verification = _hmac_sha1(key, &random_chal); let verification = hmac_sha1(key, &random_chal);
if tlv_to_map(resp.buf) if tlv_to_map(resp.buf)
.get(&(Tag::Response as u8)) .get(&(Tag::Response as u8))
.map(|v| *v == verification) .map(|v| *v == verification)
@ -208,7 +129,7 @@ impl OathSession {
return Err(Error::Authentication); return Err(Error::Authentication);
} }
let random_chal = getrandom::u64()?.to_be_bytes(); let random_chal = getrandom::u64()?.to_be_bytes();
let hmac = _hmac_sha1(key, &random_chal); let hmac = hmac_sha1(key, &random_chal);
let data = &[ let data = &[
to_tlv( to_tlv(
Tag::Key, Tag::Key,
@ -294,8 +215,7 @@ impl OathSession {
return Err(Error::Authentication); return Err(Error::Authentication);
} }
let cred_id = cred.id_data.format_cred_id(); let secret_short = hmac_shorten_key(secret, algo);
let secret_short = _hmac_shorten_key(secret, algo);
let mut secret_padded = [0u8; HMAC_MINIMUM_KEY_SIZE]; let mut secret_padded = [0u8; HMAC_MINIMUM_KEY_SIZE];
let len_to_copy = secret_short.len().min(HMAC_MINIMUM_KEY_SIZE); // Avoid copying more than 14 let len_to_copy = secret_short.len().min(HMAC_MINIMUM_KEY_SIZE); // Avoid copying more than 14
secret_padded[(HMAC_MINIMUM_KEY_SIZE - len_to_copy)..] secret_padded[(HMAC_MINIMUM_KEY_SIZE - len_to_copy)..]
@ -449,13 +369,3 @@ impl OathSession {
Ok(key_buffer) Ok(key_buffer)
} }
} }
fn time_challenge(timestamp: Option<SystemTime>, period: Option<Duration>) -> [u8; 8] {
(timestamp
.unwrap_or_else(SystemTime::now)
.duration_since(SystemTime::UNIX_EPOCH)
.as_ref()
.map_or(0, Duration::as_secs)
/ period.unwrap_or(DEFAULT_PERIOD).as_secs())
.to_be_bytes()
}

View file

@ -0,0 +1,84 @@
use crate::{OathCodeDisplay, OathCredential, OathSession, OathType};
use std::{
fmt::Display,
ops::Range,
time::{Duration, SystemTime},
};
pub struct RefreshableOathCredential<'a> {
pub cred: OathCredential,
pub code: Option<OathCodeDisplay>,
pub valid_timeframe: Range<SystemTime>,
refresh_provider: &'a OathSession,
}
impl Display for RefreshableOathCredential<'_> {
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) -> Self {
RefreshableOathCredential {
cred,
code: None,
valid_timeframe: SystemTime::UNIX_EPOCH..SystemTime::UNIX_EPOCH,
refresh_provider,
}
}
pub fn force_update(&mut self, code: Option<OathCodeDisplay>, timestamp: SystemTime) {
self.code = code;
self.valid_timeframe =
RefreshableOathCredential::format_validity_time_frame(self, timestamp);
}
pub fn refresh(&mut self) {
let timestamp = SystemTime::now();
let refresh_result = self
.refresh_provider
.calculate_code(&self.cred, Some(timestamp))
.ok();
self.force_update(refresh_result, timestamp);
}
pub fn get_or_refresh(mut self) -> RefreshableOathCredential<'a> {
if !self.is_valid() {
self.refresh();
}
self
}
pub fn is_valid(&self) -> bool {
self.valid_timeframe.contains(&SystemTime::now())
}
fn format_validity_time_frame(&self, timestamp: SystemTime) -> Range<SystemTime> {
match self.cred.id_data.oath_type {
OathType::Totp => {
let timestamp_seconds = timestamp
.duration_since(SystemTime::UNIX_EPOCH)
.as_ref()
.map_or(0, Duration::as_secs);
let time_step = timestamp_seconds / (self.cred.id_data.period.as_secs());
let valid_from = SystemTime::UNIX_EPOCH
.checked_add(self.cred.id_data.period.saturating_mul(time_step as u32))
.unwrap();
let valid_to = valid_from.checked_add(self.cred.id_data.period).unwrap();
valid_from..valid_to
}
OathType::Hotp => {
timestamp
..SystemTime::UNIX_EPOCH
.checked_add(Duration::from_secs(u64::MAX))
.unwrap()
}
}
}
}

View file

@ -77,6 +77,8 @@ impl std::error::Error for Error {}
pub struct ApduResponse { pub struct ApduResponse {
pub buf: Vec<u8>, pub buf: Vec<u8>,
pub sw1: u8, pub sw1: u8,
#[allow(dead_code)]
pub sw2: u8, pub sw2: u8,
} }
@ -213,15 +215,8 @@ pub struct TlvZipIter<'a> {
} }
impl<'a> TlvZipIter<'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<u8>) -> Self { pub fn from_vec(value: Vec<u8>) -> Self {
TlvZipIter { Self::from_tlv_iter(TlvIter::from_vec(value).into_iter())
iter: TlvIter::from_vec(value).into_iter(),
}
} }
pub fn from_tlv_iter(value: TlvIter<'a>) -> Self { pub fn from_tlv_iter(value: TlvIter<'a>) -> Self {
@ -246,7 +241,7 @@ impl<'a> TlvIter<'a> {
TlvIter { buf: value } TlvIter { buf: value }
} }
pub fn from_vec(value: Vec<u8>) -> Self { pub fn from_vec(value: Vec<u8>) -> Self {
TlvIter { buf: value.leak() } TlvIter::new(value.leak())
} }
} }
@ -262,14 +257,3 @@ impl Iterator for TlvIter<'_> {
r.ok() r.ok()
} }
} }
pub fn tlv_to_lists(data: Vec<u8>) -> HashMap<u8, Vec<Vec<u8>>> {
let mut parsed_manual: HashMap<u8, Vec<Vec<u8>>> = HashMap::new();
for res in TlvIter::from_vec(data) {
parsed_manual
.entry(res.tag().into())
.or_default()
.push(res.value().to_vec());
}
parsed_manual
}