1use anyhow::Result;
2use data_encoding::BASE32_NOPAD;
3use hmac::Hmac;
4use hmac::Mac;
5use hmac::NewMac;
6use sha1::Sha1;
7
8#[derive(Debug, Eq, PartialEq, Clone)]
9pub struct HOTP {
10 secret: Vec<u8>,
11}
12
13impl HOTP {
14 pub fn new(secret: &str) -> HOTP {
15 HOTP {
16 secret: secret.as_bytes().to_vec(),
17 }
18 }
19
20 pub fn from_base32(secret: &str) -> Result<HOTP> {
21 let secret = BASE32_NOPAD
22 .decode(secret.as_bytes())
23 .expect("Invalid base32 value");
24 Ok(HOTP { secret })
25 }
26
27 pub fn from_bytes(secret: &[u8]) -> HOTP {
28 HOTP {
29 secret: secret.to_vec(),
30 }
31 }
32
33 pub fn generate(&self, counter: u64) -> Result<u32> {
34 let mut hmac = Hmac::<Sha1>::new_from_slice(self.secret.as_slice()).expect("Invalid secret");
35
36 hmac.update(&counter.to_be_bytes());
37 let result = hmac.finalize();
38 let digest = result.into_bytes();
39 let offset = (digest.last().expect("Invalid Digest") & 0xf) as usize;
40 let code: [u8; 4] = digest[offset..offset + 4]
41 .try_into()
42 .expect("Invalid digest");
43 let code = u32::from_be_bytes(code);
44 Ok((code & 0x7fffffff) % 1000000)
45 }
46
47 pub fn verify(&self, code: u32, last: u64, trials: u64) -> bool {
48 let code_str = code.to_string();
49 let code_bytes = code_str.as_bytes();
50 if code_bytes.len() > 6 {
51 return false;
52 }
53 for i in last + 1..last + trials + 1 {
54 println!("{}", i);
55 let valid_code = self.generate(i).expect("Fail to generate code").to_string();
56 let valid_bytes = valid_code.as_bytes();
57 if code_bytes.len() != valid_code.len() {
58 continue;
59 }
60 let mut rv = 0;
61 for (a, b) in code_bytes.iter().zip(valid_bytes.iter()) {
62 rv |= a ^ b;
63 }
64 if rv == 0 {
65 return true;
66 }
67 }
68 false
69 }
70
71 pub fn base32_secret(&self) -> String {
72 BASE32_NOPAD.encode(&self.secret)
73 }
74 pub fn to_uri(&self, label: &str, issuer: &str, counter: u64) -> String {
75 format!(
76 "otpauth://hotp/{}?secret={}&issuer={}&counter={}",
77 label,
78 self.base32_secret(),
79 issuer,
80 counter
81 )
82 }
83}