1use hmac::{crypto_mac, Hmac, Mac, NewMac};
2use rand;
3use sha1::Sha1;
4use sha2::{Sha256, Sha512};
5use thiserror::Error;
6use url::form_urlencoded::byte_serialize;
7
8type HmacSha1 = Hmac<Sha1>;
9type HmacSha256 = Hmac<Sha256>;
10type HmacSha512 = Hmac<Sha512>;
11
12mod hotp;
13mod totp;
14
15pub use totp::Totp;
16pub use hotp::Hotp;
17
18#[derive(Error, Debug)]
20pub enum GenerationError {
21 #[error("Invalid Key Length")]
22 InvalidKeyLength(#[from] crypto_mac::InvalidKeyLength),
23 #[error("Failed to generate One-Time Password")]
24 FailedToGenerateOTP(),
25}
26
27enum HmacFunction<A, B, C> {
28 Sha1(A),
29 Sha256(B),
30 Sha512(C),
31}
32
33pub enum Algorithm {
34 Sha1,
35 Sha256,
36 Sha512,
37}
38
39static CHAR_SET: [char; 62] = [
40 '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
41 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b',
42 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u',
43 'v', 'w', 'x', 'y', 'z',
44];
45static SYMBOL_SET: [char; 22] = [
46 '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '<', '>', '?', '/', '[', ']', '{', '}', ',',
47 '.', ':', ';',
48];
49
50pub fn digest(
65 secret: String,
66 counter: u128,
67 algorithm: Algorithm,
68) -> std::result::Result<Vec<u8>, GenerationError> {
69 let mac = get_hmac(secret, algorithm)?;
70
71 let mut buf = vec![0; 8];
73 let mut tmp = counter;
74 for i in 0..8 {
75 buf[7 - i] = (tmp & 0xff) as u8;
76 tmp = tmp >> 8;
77 }
78
79 Ok(match mac {
81 HmacFunction::Sha1(mut _mac) => {
82 _mac.update(&buf);
83 _mac.finalize().into_bytes().to_vec()
84 }
85 HmacFunction::Sha256(mut _mac) => {
86 _mac.update(&buf);
87 _mac.finalize().into_bytes().to_vec()
88 }
89 HmacFunction::Sha512(mut _mac) => {
90 _mac.update(&buf);
91 _mac.finalize().into_bytes().to_vec()
92 }
93 })
94}
95
96pub fn generate_secret() -> String {
105 generate_secret_default(None, None)
106}
107
108pub fn generate_sized_secret(length: u32) -> String {
117 generate_secret_default(Some(length), None)
118}
119
120pub fn generate_secret_without_symbols() -> String {
129 generate_secret_default(None, Some(false))
130}
131
132pub fn generate_sized_secret_without_symbols(length: u32) -> String {
141 generate_secret_default(Some(length), Some(true))
142}
143
144pub fn get_otp_auth_url() {}
145
146#[doc(hidden)]
155fn generate_otp(
156 digits: u32,
157 digest_hash: Vec<u8>,
158) -> std::result::Result<String, GenerationError> {
159 let offset = if let Some(o) = digest_hash.last() {
160 o & 0xf
161 } else {
162 0
163 };
164
165 let no_offset = if let Some(o) = digest_hash.get(offset as usize) {
166 u32::from(o.clone() & 0x7f) << 24
167 } else {
168 0
169 };
170 let one_offset = if let Some(o) = digest_hash.get((offset + 1) as usize) {
171 u32::from(o.clone() & 0xff) << 16
172 } else {
173 0
174 };
175 let two_offset = if let Some(o) = digest_hash.get((offset + 2) as usize) {
176 u32::from(o.clone() & 0xff) << 8
177 } else {
178 0
179 };
180 let three_offset = if let Some(o) = digest_hash.get((offset + 3) as usize) {
181 u32::from(o.clone() & 0xff)
182 } else {
183 0
184 };
185 let code = no_offset | one_offset | two_offset | three_offset;
186
187 if code == 0 {
188 Err(GenerationError::FailedToGenerateOTP())
190 } else {
191 let padded_string = format!("{:0>width$}", code.to_string(), width = digits as usize);
192 Ok(
193 (&padded_string[(padded_string.len() - digits as usize)..padded_string.len()])
194 .to_string(),
195 )
196 }
197}
198
199#[doc(hidden)]
200fn verify_delta(
201 token: String,
202 counter: u128,
203 digits: u32,
204 window: u64,
205 digest_hash: Vec<u8>,
206) -> std::result::Result<bool, GenerationError> {
207 if token.len() as u32 != digits {
208 return Ok(false);
209 }
210
211 for _ in counter..=counter + window as u128 {
212 let test_otp = generate_otp(digits, digest_hash.clone())?;
213 if test_otp == token {
214 return Ok(true);
215 }
216 }
217
218 Ok(false)
220}
221
222#[doc(hidden)]
223fn generate_secret_default(length: Option<u32>, symbols: Option<bool>) -> String {
224 let defined_symbols = if let Some(s) = symbols { s } else { true };
225 let defined_length = if let Some(l) = length { l } else { 32 };
226 generate_secret_ascii(defined_length, defined_symbols)
227}
228
229#[doc(hidden)]
230fn get_hmac(
231 secret: String,
232 algorithm: Algorithm,
233) -> std::result::Result<HmacFunction<HmacSha1, HmacSha256, HmacSha512>, GenerationError> {
234 Ok(match algorithm {
235 Algorithm::Sha1 => HmacFunction::Sha1(HmacSha1::new_varkey(secret.as_bytes())?),
236 Algorithm::Sha256 => HmacFunction::Sha256(HmacSha256::new_varkey(secret.as_bytes())?),
237 Algorithm::Sha512 => HmacFunction::Sha512(HmacSha512::new_varkey(secret.as_bytes())?),
238 })
239}
240
241#[doc(hidden)]
242fn generate_secret_ascii(length: u32, symbols: bool) -> String {
243 let byte_array: Vec<u8> = (0..length).map(|_| rand::random::<u8>()).collect();
244
245 let mut secret: String = String::from("");
246 for (_, value) in byte_array.iter().enumerate() {
247 if symbols {
249 secret.push(match value % 2 {
250 0 => CHAR_SET[((usize::from(value / 1)) * (CHAR_SET.len() - 1)) / 255],
251 1 => SYMBOL_SET[((usize::from(value / 1)) * (SYMBOL_SET.len() - 1)) / 255],
252 _ => unreachable!("Error: Reached the unreachable match arm of `u8` modulo 2"),
253 })
254 } else {
255 secret.push(CHAR_SET[((usize::from(value / 1)) * (CHAR_SET.len() - 1)) / 255])
256 }
257 }
258 secret
259}
260
261#[doc(hidden)]
262fn encode_uri_component(string: String) -> String {
263 byte_serialize(string.as_bytes()).collect()
264}
265
266#[doc(hidden)]
267fn generate_otpauth_url() {}
268
269#[cfg(test)]
270mod digest_tests {
271 use crate::digest;
272 use crate::Algorithm::Sha1;
273
274 #[test]
275 fn it_works() {
276 let test = digest("My secret".to_string(), 5000, Sha1);
277 match test {
278 Ok(result) => println!("Testing {:02x?}", result),
279 Err(_) => panic!("There was an error in the test"),
280 }
281 }
282}
283
284#[cfg(test)]
285mod generate_secret_tests {
286 use crate::{
287 generate_secret_ascii, generate_secret_without_symbols, generate_sized_secret, SYMBOL_SET,
288 };
289
290 #[test]
291 fn test_generate_secret_ascii_no_symbols() {
292 let secret = generate_secret_ascii(2000, false);
293 assert_eq!(secret.len(), 2000);
294 }
295
296 #[test]
297 fn test_generate_secret_ascii_symbols() {
298 let secret = generate_secret_ascii(2000, true);
299 assert_eq!(secret.len(), 2000);
300 assert_eq!(secret.contains("!"), true);
301 }
302
303 #[test]
318 fn test_generate_secret_non_default_length() {
319 assert_eq!(generate_sized_secret(2000).len(), 2000);
320 }
321
322 #[test]
323 fn test_generate_secret_non_default_symbols() {
324 assert_eq!(
325 generate_secret_without_symbols()
326 .chars()
327 .any(|c| match SYMBOL_SET.binary_search(&c) {
328 Ok(_) => true,
329 _ => false,
330 }),
331 false
332 )
333 }
334}