use std::time::{SystemTime, SystemTimeError};
use std::convert::TryInto;
use data_encoding::{BASE32_NOPAD, DecodeError};
use err_derive::Error;
use ring::hmac;
#[derive(Debug, Error)]
pub enum Error {
#[error(display="invalid time provided")]
InvalidTimeError(#[error(source)] SystemTimeError),
#[error(display="invalid digest provided: {:?}", _0)]
InvalidDigest(Vec<u8>),
#[error(display="invalid secret provided")]
InvalidSecret(#[error(source)] DecodeError)
}
fn decode_secret(secret: &str) -> Result<Vec<u8>, DecodeError> {
BASE32_NOPAD.decode(secret.as_bytes())
}
fn calc_digest(decoded_secret: &[u8], counter: u64) -> hmac::Tag {
let key = hmac::Key::new(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, decoded_secret);
hmac::sign(&key, &counter.to_be_bytes())
}
fn encode_digest(digest: &[u8]) -> Result<u32, Error> {
let offset = match digest.last() {
Some(x) => *x & 0xf,
None => return Err(Error::InvalidDigest(Vec::from(digest)))
} as usize;
let code_bytes: [u8; 4] = match digest[offset..offset+4].try_into() {
Ok(x) => x,
Err(_) => return Err(Error::InvalidDigest(Vec::from(digest)))
};
let code = u32::from_be_bytes(code_bytes);
Ok((code & 0x7fffffff) % 1_000_000)
}
pub fn make_hotp(secret: &str, counter: u64) -> Result<u32, Error> {
let decoded = decode_secret(secret)?;
encode_digest(calc_digest(decoded.as_slice(), counter).as_ref())
}
fn make_totp_helper(secret: &str, time_step: u64, skew: i64, time: u64) -> Result<u32, Error> {
let counter = ((time as i64 + skew) as u64) / time_step;
make_hotp(secret, counter)
}
pub fn make_totp(secret: &str, time_step: u64, skew: i64) -> Result<u32, Error> {
let now = SystemTime::now();
let time_since_epoch = now.duration_since(SystemTime::UNIX_EPOCH)?;
match make_totp_helper(secret, time_step, skew, time_since_epoch.as_secs() ) {
Ok(d) => Ok(d),
Err(err) => return Err(err)
}
}
#[cfg(test)]
mod tests {
use super::{make_hotp, make_totp_helper};
#[test]
fn hotp() {
assert_eq!(make_hotp("BASE32SECRET3232", 0).unwrap(), 260182);
assert_eq!(make_hotp("BASE32SECRET3232", 1).unwrap(), 55283);
assert_eq!(make_hotp("BASE32SECRET3232", 1401).unwrap(), 316439);
}
#[test]
fn totp() {
assert_eq!(make_totp_helper("BASE32SECRET3232", 30, 0, 0).unwrap(), 260182);
assert_eq!(make_totp_helper("BASE32SECRET3232", 3600, 0, 7).unwrap(), 260182);
assert_eq!(make_totp_helper("BASE32SECRET3232", 30, 0, 35).unwrap(), 55283);
assert_eq!(make_totp_helper("BASE32SECRET3232", 1, -2, 1403).unwrap(), 316439);
}
}