use std::rc::Rc;
use serde::{Deserialize, Serialize};
use crate::{error, HMACType, Key, OtpAuthKey};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct HOTPKey {
pub name: String,
pub key: String,
pub digits: u8,
pub counter: u64,
pub recovery_codes: Vec<String>,
pub hmac_type: HMACType,
pub issuer: Option<String>,
}
impl Default for HOTPKey {
fn default() -> Self {
Self {
name: Default::default(),
key: Default::default(),
digits: 6,
counter: Default::default(),
recovery_codes: Default::default(),
hmac_type: Default::default(),
issuer: Default::default(),
}
}
}
impl HOTPKey {
fn decode_key(&self) -> Result<Rc<[u8]>, error::Error> {
let key = data_encoding::BASE32.decode(self.get_key().as_bytes());
if key.is_err() {
return Err(error::Error::InvalidKey);
}
Ok(Rc::from(key.unwrap().as_slice()))
}
fn get_key(&self) -> &str {
&self.key
}
}
impl OtpAuthKey for HOTPKey {
fn to_uri_struct(&self) -> crate::URI {
crate::URI {
name: self.name.clone(),
secret: self.key.clone(),
issuer: self.issuer.clone(),
algorithm: Some(self.hmac_type),
digits: Some(self.digits),
period: None,
counter: Some(self.counter),
key_type: crate::KeyType::TOTP,
}
}
fn from_uri_struct(uri: &crate::URI) -> Result<Box<dyn Key>, crate::Error> {
let counter = if let Some(counter) = uri.counter {
counter
} else {
30
};
let digits = if let Some(digits) = uri.digits {
digits
} else {
6
};
let algorithm = if let Some(algorithm) = uri.algorithm {
algorithm
} else {
HMACType::SHA1
};
Ok(Box::from(HOTPKey {
name: uri.name.clone(),
key: uri.secret.clone(),
digits,
counter,
recovery_codes: Vec::default(),
hmac_type: algorithm,
issuer: uri.issuer.clone(),
}))
}
fn get_issuer(&self) -> Option<&str> {
self.issuer.as_deref()
}
}
impl Key for HOTPKey {
fn get_type(&self) -> crate::KeyType {
crate::KeyType::HOTP
}
fn get_name(&self) -> &str {
&self.name
}
fn get_recovery_codes(&self) -> Vec<String> {
self.recovery_codes.clone()
}
fn get_code(&mut self) -> Result<String, error::Error> {
let raw = self.decode_key()?;
self.counter += 1;
let res = self
.hmac_type
.get_hash(raw.as_ref(), &self.counter.to_be_bytes())?;
let offset: usize = (res[res.len() - 1] & 0x0f) as usize;
let code: u32 = (((res[offset] & 0x7f) as u32) << 24)
| ((res[offset + 1] as u32) << 16)
| ((res[offset + 2] as u32) << 8)
| (res[offset + 3] as u32);
let code = code % 10u32.pow(self.digits as u32);
let mut code = code.to_string();
while code.len() < self.digits as usize {
code.insert(0, '0');
}
Ok(code)
}
fn set_name(&mut self, name: &str) {
self.name = name.to_string();
}
fn set_recovery_codes(&mut self, recovery_codes: Vec<String>) {
self.recovery_codes = recovery_codes;
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}