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",
]
[[package]]
name = "lib_ykoath2"
version = "0.1.0"
dependencies = [
"apdu-core",
"base64",
"getrandom",
"hmac",
"iso7816-tlv",
"ouroboros",
"pbkdf2",
"pcsc",
"regex",
"sha1",
"sha2",
]
[[package]]
name = "libc"
version = "0.2.169"
@ -431,3 +414,20 @@ name = "yansi"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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"
[package]
name = "lib_ykoath2"
name = "ykoath2"
version = "0.1.0"
edition = "2021"
authors = ["Grimmauld <grimmauld@grimmauld.de>"]

View file

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

View file

@ -1,38 +1,26 @@
#![allow(unused)]
pub mod constants;
use constants::*;
mod transaction;
use transaction::*;
pub mod oath_credential;
pub mod oath_credentialid;
/// Utilities for interacting with YubiKey OATH/TOTP functionality
use std::{
fmt::Display,
ops::{Range, RangeInclusive},
time::{Duration, Instant, SystemTime},
};
mod refreshable_oath_credential;
mod transaction;
use base64::{engine::general_purpose, Engine as _};
use hmac::{Hmac, Mac};
use constants::*;
use oath_credential::*;
use oath_credentialid::*;
use refreshable_oath_credential::*;
use transaction::*;
fn _get_device_id(salt: Vec<u8>) -> String {
let result = HashAlgo::Sha256.get_hash_fun()(salt.leak());
use std::time::{Duration, SystemTime};
// Get the first 16 bytes of the hash
let hash_16_bytes = &result[..16];
use hmac::{Hmac, Mac};
// Base64 encode the result and remove padding ('=')
general_purpose::URL_SAFE_NO_PAD.encode(hash_16_bytes)
}
fn _hmac_sha1(key: &[u8], message: &[u8]) -> Vec<u8> {
fn hmac_sha1(key: &[u8], message: &[u8]) -> Vec<u8> {
let mut mac = Hmac::<sha1::Sha1>::new_from_slice(key).expect("Invalid key length");
mac.update(message);
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() {
algo.get_hash_fun()(key)
} else {
@ -40,81 +28,14 @@ fn _hmac_shorten_key(key: &[u8], algo: HashAlgo) -> Vec<u8> {
}
}
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
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);
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()
}
}
}
.map_or(0, Duration::as_secs)
/ period.unwrap_or(DEFAULT_PERIOD).as_secs())
.to_be_bytes()
}
pub struct OathSession {
@ -180,7 +101,7 @@ impl OathSession {
return Ok(());
}
let hmac = _hmac_sha1(key, &chal);
let hmac = hmac_sha1(key, &chal);
let random_chal = getrandom::u64()?.to_be_bytes();
let data = &[
to_tlv(Tag::Response, &hmac),
@ -190,7 +111,7 @@ impl OathSession {
let resp =
self.transaction_context
.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)
.get(&(Tag::Response as u8))
.map(|v| *v == verification)
@ -208,7 +129,7 @@ impl OathSession {
return Err(Error::Authentication);
}
let random_chal = getrandom::u64()?.to_be_bytes();
let hmac = _hmac_sha1(key, &random_chal);
let hmac = hmac_sha1(key, &random_chal);
let data = &[
to_tlv(
Tag::Key,
@ -294,8 +215,7 @@ impl OathSession {
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 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)..]
@ -449,13 +369,3 @@ impl OathSession {
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 buf: Vec<u8>,
pub sw1: u8,
#[allow(dead_code)]
pub sw2: u8,
}
@ -213,15 +215,8 @@ pub struct 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 {
TlvZipIter {
iter: TlvIter::from_vec(value).into_iter(),
}
Self::from_tlv_iter(TlvIter::from_vec(value).into_iter())
}
pub fn from_tlv_iter(value: TlvIter<'a>) -> Self {
@ -246,7 +241,7 @@ impl<'a> TlvIter<'a> {
TlvIter { buf: value }
}
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()
}
}
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
}