1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
mod algorithm;
pub mod backup;
mod hotp;
mod otp_method;
mod otp_uri;
mod steam;
mod totp;
use anyhow::{anyhow, Result};
use ring::hmac;
use std::convert::TryInto;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
pub use algorithm::Algorithm;
pub use backup::Account;
pub use hotp::{HOTP, HOTP_DEFAULT_COUNTER};
pub use otp_method::OTPMethod;
pub use otp_uri::OTPUri;
pub use steam::{Steam, STEAM_DEFAULT_DIGITS, STEAM_DEFAULT_PERIOD};
pub use totp::{TOTP, TOTP_DEFAULT_PERIOD};
pub const DEFAULT_DIGITS: u32 = 6;
pub trait OTP {
fn compute(&self, counter: u64) -> Result<String>;
}
fn decode_secret(secret: &str) -> Result<Vec<u8>> {
let size = secret.len();
let mut output_buffer = std::iter::repeat(0).take(size).collect::<Vec<u8>>();
let ret = binascii::b32decode(secret.as_bytes(), &mut output_buffer)
.map_err(|_| anyhow!("Invalid Input"))?;
Ok(ret.to_vec())
}
pub fn is_valid(secret: &str) -> bool {
decode_secret(secret).is_ok()
}
pub(crate) fn calc_digest(decoded_secret: &[u8], counter: u64, algorithm: Algorithm) -> hmac::Tag {
let key = hmac::Key::new(algorithm.into(), decoded_secret);
hmac::sign(&key, &counter.to_be_bytes())
}
pub(crate) fn encode_digest(digest: &[u8]) -> Result<u32> {
let offset = match digest.last() {
Some(x) => *x & 0xf,
None => anyhow::bail!("Invalid digest"),
} as usize;
let code_bytes: [u8; 4] = match digest[offset..offset + 4].try_into() {
Ok(x) => x,
Err(_) => anyhow::bail!("Invalid digest"),
};
let code = u32::from_be_bytes(code_bytes);
Ok(code & 0x7fffffff)
}
pub fn format(code: u32, digits: usize) -> String {
let padded_code = format!("{:0width$}", code, width = digits);
let mut formated_code = String::new();
for (idx, ch) in padded_code.chars().enumerate() {
if (digits - idx) % 3 == 0 && (digits - idx) != 0 && idx != 0 {
formated_code.push(' ');
}
formated_code.push(ch);
}
formated_code
}
pub fn time_based_counter(period: u32) -> u64 {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
timestamp / period as u64
}
pub fn remaining_time(period: u32) -> Duration {
let period = period as u128 * 1000;
let now: u128 = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis();
let duration = period - now % period;
Duration::from_millis(duration as u64)
}