1#![crate_name = "rust_otp"]
2#![crate_type = "lib"]
3
4use data_encoding::{DecodeError, BASE32_NOPAD};
5use err_derive::Error;
6use ring::hmac;
7use std::convert::TryInto;
8use std::time::{SystemTime, SystemTimeError};
9
10#[derive(Debug, Error)]
11pub enum Error {
12 #[error(display = "invalid time provided")]
13 InvalidTimeError(#[error(source)] SystemTimeError),
14 #[error(display = "invalid digest provided: {:?}", _0)]
15 InvalidDigest(Vec<u8>),
16 #[error(display = "invalid secret provided")]
17 InvalidSecret(#[error(source)] DecodeError),
18}
19
20fn decode_secret(secret: &str) -> Result<Vec<u8>, DecodeError> {
23 BASE32_NOPAD.decode(secret.as_bytes())
24}
25
26fn calc_digest(decoded_secret: &[u8], counter: u64) -> hmac::Tag {
28 let key = hmac::Key::new(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, decoded_secret);
29 hmac::sign(&key, &counter.to_be_bytes())
30}
31
32fn encode_digest(digest: &[u8]) -> Result<u32, Error> {
34 let offset = match digest.last() {
35 Some(x) => *x & 0xf,
36 None => return Err(Error::InvalidDigest(Vec::from(digest))),
37 } as usize;
38 let code_bytes: [u8; 4] = match digest[offset..offset + 4].try_into() {
39 Ok(x) => x,
40 Err(_) => return Err(Error::InvalidDigest(Vec::from(digest))),
41 };
42 let code = u32::from_be_bytes(code_bytes);
43 Ok((code & 0x7fffffff) % 1_000_000)
44}
45
46pub fn make_hotp(secret: &str, counter: u64) -> Result<u32, Error> {
49 let decoded = decode_secret(secret)?;
50 encode_digest(calc_digest(decoded.as_slice(), counter).as_ref())
51}
52
53fn make_totp_helper(secret: &str, time_step: u64, skew: i64, time: u64) -> Result<u32, Error> {
56 let counter = ((time as i64 + skew) as u64) / time_step;
57 make_hotp(secret, counter)
58}
59
60pub fn make_totp(secret: &str, time_step: u64, skew: i64) -> Result<u32, Error> {
64 let now = SystemTime::now();
65 let time_since_epoch = now.duration_since(SystemTime::UNIX_EPOCH)?;
66 match make_totp_helper(secret, time_step, skew, time_since_epoch.as_secs()) {
67 Ok(d) => Ok(d),
68 Err(err) => return Err(err),
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::{make_hotp, make_totp_helper};
75
76 #[test]
77 fn hotp() {
78 assert_eq!(make_hotp("BASE32SECRET3232", 0).unwrap(), 260182);
79 assert_eq!(make_hotp("BASE32SECRET3232", 1).unwrap(), 55283);
80 assert_eq!(make_hotp("BASE32SECRET3232", 1401).unwrap(), 316439);
81 }
82
83 #[test]
84 fn totp() {
85 assert_eq!(
86 make_totp_helper("BASE32SECRET3232", 30, 0, 0).unwrap(),
87 260182
88 );
89 assert_eq!(
90 make_totp_helper("BASE32SECRET3232", 3600, 0, 7).unwrap(),
91 260182
92 );
93 assert_eq!(
94 make_totp_helper("BASE32SECRET3232", 30, 0, 35).unwrap(),
95 55283
96 );
97 assert_eq!(
98 make_totp_helper("BASE32SECRET3232", 1, -2, 1403).unwrap(),
99 316439
100 );
101 }
102}