use hmac::{crypto_mac, Hmac, Mac, NewMac};
use rand;
use sha1::Sha1;
use sha2::{Sha256, Sha512};
use thiserror::Error;
use url::form_urlencoded::byte_serialize;
type HmacSha1 = Hmac<Sha1>;
type HmacSha256 = Hmac<Sha256>;
type HmacSha512 = Hmac<Sha512>;
mod hotp;
mod totp;
pub use totp::Totp;
pub use hotp::Hotp;
#[derive(Error, Debug)]
pub enum GenerationError {
#[error("Invalid Key Length")]
InvalidKeyLength(#[from] crypto_mac::InvalidKeyLength),
#[error("Failed to generate One-Time Password")]
FailedToGenerateOTP(),
}
enum HmacFunction<A, B, C> {
Sha1(A),
Sha256(B),
Sha512(C),
}
pub enum Algorithm {
Sha1,
Sha256,
Sha512,
}
static CHAR_SET: [char; 62] = [
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b',
'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u',
'v', 'w', 'x', 'y', 'z',
];
static SYMBOL_SET: [char; 22] = [
'!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '<', '>', '?', '/', '[', ']', '{', '}', ',',
'.', ':', ';',
];
pub fn digest(
secret: String,
counter: u128,
algorithm: Algorithm,
) -> std::result::Result<Vec<u8>, GenerationError> {
let mac = get_hmac(secret, algorithm)?;
let mut buf = vec![0; 8];
let mut tmp = counter;
for i in 0..8 {
buf[7 - i] = (tmp & 0xff) as u8;
tmp = tmp >> 8;
}
Ok(match mac {
HmacFunction::Sha1(mut _mac) => {
_mac.update(&buf);
_mac.finalize().into_bytes().to_vec()
}
HmacFunction::Sha256(mut _mac) => {
_mac.update(&buf);
_mac.finalize().into_bytes().to_vec()
}
HmacFunction::Sha512(mut _mac) => {
_mac.update(&buf);
_mac.finalize().into_bytes().to_vec()
}
})
}
pub fn generate_secret() -> String {
generate_secret_default(None, None)
}
pub fn generate_sized_secret(length: u32) -> String {
generate_secret_default(Some(length), None)
}
pub fn generate_secret_without_symbols() -> String {
generate_secret_default(None, Some(false))
}
pub fn generate_sized_secret_without_symbols(length: u32) -> String {
generate_secret_default(Some(length), Some(true))
}
pub fn get_otp_auth_url() {}
#[doc(hidden)]
fn generate_otp(
digits: u32,
digest_hash: Vec<u8>,
) -> std::result::Result<String, GenerationError> {
let offset = if let Some(o) = digest_hash.last() {
o & 0xf
} else {
0
};
let no_offset = if let Some(o) = digest_hash.get(offset as usize) {
u32::from(o.clone() & 0x7f) << 24
} else {
0
};
let one_offset = if let Some(o) = digest_hash.get((offset + 1) as usize) {
u32::from(o.clone() & 0xff) << 16
} else {
0
};
let two_offset = if let Some(o) = digest_hash.get((offset + 2) as usize) {
u32::from(o.clone() & 0xff) << 8
} else {
0
};
let three_offset = if let Some(o) = digest_hash.get((offset + 3) as usize) {
u32::from(o.clone() & 0xff)
} else {
0
};
let code = no_offset | one_offset | two_offset | three_offset;
if code == 0 {
Err(GenerationError::FailedToGenerateOTP())
} else {
let padded_string = format!("{:0>width$}", code.to_string(), width = digits as usize);
Ok(
(&padded_string[(padded_string.len() - digits as usize)..padded_string.len()])
.to_string(),
)
}
}
#[doc(hidden)]
fn verify_delta(
token: String,
counter: u128,
digits: u32,
window: u64,
digest_hash: Vec<u8>,
) -> std::result::Result<bool, GenerationError> {
if token.len() as u32 != digits {
return Ok(false);
}
for _ in counter..=counter + window as u128 {
let test_otp = generate_otp(digits, digest_hash.clone())?;
if test_otp == token {
return Ok(true);
}
}
Ok(false)
}
#[doc(hidden)]
fn generate_secret_default(length: Option<u32>, symbols: Option<bool>) -> String {
let defined_symbols = if let Some(s) = symbols { s } else { true };
let defined_length = if let Some(l) = length { l } else { 32 };
generate_secret_ascii(defined_length, defined_symbols)
}
#[doc(hidden)]
fn get_hmac(
secret: String,
algorithm: Algorithm,
) -> std::result::Result<HmacFunction<HmacSha1, HmacSha256, HmacSha512>, GenerationError> {
Ok(match algorithm {
Algorithm::Sha1 => HmacFunction::Sha1(HmacSha1::new_varkey(secret.as_bytes())?),
Algorithm::Sha256 => HmacFunction::Sha256(HmacSha256::new_varkey(secret.as_bytes())?),
Algorithm::Sha512 => HmacFunction::Sha512(HmacSha512::new_varkey(secret.as_bytes())?),
})
}
#[doc(hidden)]
fn generate_secret_ascii(length: u32, symbols: bool) -> String {
let byte_array: Vec<u8> = (0..length).map(|_| rand::random::<u8>()).collect();
let mut secret: String = String::from("");
for (_, value) in byte_array.iter().enumerate() {
if symbols {
secret.push(match value % 2 {
0 => CHAR_SET[((usize::from(value / 1)) * (CHAR_SET.len() - 1)) / 255],
1 => SYMBOL_SET[((usize::from(value / 1)) * (SYMBOL_SET.len() - 1)) / 255],
_ => unreachable!("Error: Reached the unreachable match arm of `u8` modulo 2"),
})
} else {
secret.push(CHAR_SET[((usize::from(value / 1)) * (CHAR_SET.len() - 1)) / 255])
}
}
secret
}
#[doc(hidden)]
fn encode_uri_component(string: String) -> String {
byte_serialize(string.as_bytes()).collect()
}
#[doc(hidden)]
fn generate_otpauth_url() {}
#[cfg(test)]
mod digest_tests {
use crate::digest;
use crate::Algorithm::Sha1;
#[test]
fn it_works() {
let test = digest("My secret".to_string(), 5000, Sha1);
match test {
Ok(result) => println!("Testing {:02x?}", result),
Err(_) => panic!("There was an error in the test"),
}
}
}
#[cfg(test)]
mod generate_secret_tests {
use crate::{
generate_secret_ascii, generate_secret_without_symbols, generate_sized_secret, SYMBOL_SET,
};
#[test]
fn test_generate_secret_ascii_no_symbols() {
let secret = generate_secret_ascii(2000, false);
assert_eq!(secret.len(), 2000);
}
#[test]
fn test_generate_secret_ascii_symbols() {
let secret = generate_secret_ascii(2000, true);
assert_eq!(secret.len(), 2000);
assert_eq!(secret.contains("!"), true);
}
#[test]
fn test_generate_secret_non_default_length() {
assert_eq!(generate_sized_secret(2000).len(), 2000);
}
#[test]
fn test_generate_secret_non_default_symbols() {
assert_eq!(
generate_secret_without_symbols()
.chars()
.any(|c| match SYMBOL_SET.binary_search(&c) {
Ok(_) => true,
_ => false,
}),
false
)
}
}