otp_rs/
totp.rs

1use crate::HOTP;
2use anyhow::Result;
3
4#[derive(Debug, Eq, PartialEq, Clone)]
5pub struct TOTP {
6  hotp: HOTP,
7}
8
9impl TOTP {
10  pub fn new(secret: &str) -> TOTP {
11    TOTP {
12      hotp: HOTP::new(secret),
13    }
14  }
15
16  pub fn from_base32(secret: &str) -> Result<TOTP> {
17    HOTP::from_base32(secret).map(|hotp| TOTP { hotp })
18  }
19
20  pub fn from_bytes(secret: &[u8]) -> TOTP {
21    TOTP {
22      hotp: HOTP::from_bytes(secret),
23    }
24  }
25
26  pub fn generate(&self, period: u64, timestamp: u64) -> Result<u32> {
27    let counter = timestamp / period;
28
29    self.hotp.generate(counter)
30  }
31
32  pub fn verify(&self, code: u32, period: u64, timestamp: u64) -> bool {
33    let code_str = code.to_string();
34    let code_bytes = code_str.as_bytes();
35    if code_bytes.len() > 6 {
36      return false;
37    }
38    let valid_code = self
39      .generate(period, timestamp)
40      .expect("Fail to generate code")
41      .to_string();
42    let valid_bytes = valid_code.as_bytes();
43    if code_bytes.len() != valid_code.len() {
44      return false;
45    }
46    let mut rv = 0;
47    for (a, b) in code_bytes.iter().zip(valid_bytes.iter()) {
48      rv |= a ^ b;
49    }
50    rv == 0
51  }
52
53  pub fn to_uri(&self, label: &str, issuer: &str) -> String {
54    format!(
55      "otpauth://totp/{}?secret={}&issuer={}",
56      label,
57      self.hotp.base32_secret(),
58      issuer
59    )
60  }
61}