mirror of
https://github.com/LordGrimmauld/yubi-oath-rs.git
synced 2025-03-04 05:44:40 +01:00
refactor: cleanup unused code, drop lib_ prefix from name
This commit is contained in:
parent
6a88a4f3de
commit
d29370d398
6 changed files with 135 additions and 157 deletions
34
Cargo.lock
generated
34
Cargo.lock
generated
|
@ -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",
|
||||||
|
]
|
||||||
|
|
|
@ -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>"]
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
132
src/lib.rs
132
src/lib.rs
|
@ -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()
|
|
||||||
}
|
|
||||||
|
|
84
src/refreshable_oath_credential.rs
Normal file
84
src/refreshable_oath_credential.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue