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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use base32;
use byteorder::{BigEndian, ReadBytesExt};
use ring::hmac;
use std::io::Cursor;
use base64;
use image::Luma;
use qrcode::QrCode;
#[derive(Debug)]
pub enum Algorithm {
SHA1,
SHA256,
SHA512,
}
#[derive(Debug)]
pub struct TOTP {
algorithm: Algorithm,
digits: usize,
skew: u8,
step: u64,
secret: Vec<u8>,
}
impl TOTP {
pub fn new(algorithm: Algorithm, digits: usize, skew: u8, step: u64, secret: Vec<u8>) -> TOTP {
TOTP {
algorithm: algorithm,
digits: digits,
skew: skew,
step: step,
secret: secret,
}
}
pub fn generate(&self, time: u64) -> String {
let key: hmac::Key;
match self.algorithm {
Algorithm::SHA1 => {
key = hmac::Key::new(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, &self.secret)
}
Algorithm::SHA256 => key = hmac::Key::new(hmac::HMAC_SHA256, &self.secret),
Algorithm::SHA512 => key = hmac::Key::new(hmac::HMAC_SHA512, &self.secret),
}
let ctr = (time / self.step).to_be_bytes().to_vec();
let result = hmac::sign(&key, &ctr);
let offset = (result.as_ref()[19] & 15) as usize;
let mut rdr = Cursor::new(result.as_ref()[offset..offset + 4].to_vec());
let result = rdr.read_u32::<BigEndian>().unwrap() & 0x7fff_ffff;
format!(
"{1:00$}",
self.digits,
result % (10 as u32).pow(self.digits as u32)
)
}
pub fn check(&self, token: String, time: u64) -> bool {
let key: hmac::Key;
match self.algorithm {
Algorithm::SHA1 => {
key = hmac::Key::new(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, &self.secret)
}
Algorithm::SHA256 => key = hmac::Key::new(hmac::HMAC_SHA256, &self.secret),
Algorithm::SHA512 => key = hmac::Key::new(hmac::HMAC_SHA512, &self.secret),
}
let basestep = time / 30 - (self.skew as u64);
for _i in 0..self.skew * 2 + 1 {
let result = hmac::sign(&key, &basestep.to_be_bytes().to_vec());
let offset = (result.as_ref()[19] & 15) as usize;
let mut rdr = Cursor::new(result.as_ref()[offset..offset + 4].to_vec());
let result = rdr.read_u32::<BigEndian>().unwrap() & 0x7fffffff;
if format!(
"{1:00$}",
self.digits,
result % (10 as u32).pow(self.digits as u32)
) == token
{
return true;
}
}
false
}
pub fn get_url(&self, label: String, issuer: String) -> String {
let algorithm: String;
match self.algorithm {
Algorithm::SHA1 => algorithm = "SHA1".to_string(),
Algorithm::SHA256 => algorithm = "SHA256".to_string(),
Algorithm::SHA512 => algorithm = "SHA512".to_string(),
}
format!(
"otpauth://totp/{}?secret={}&issuer={}&digits={}&algorithm={}",
label,
base32::encode(base32::Alphabet::RFC4648 { padding: false }, &self.secret),
issuer,
self.digits.to_string(),
algorithm,
)
}
pub fn get_qr(
&self,
label: String,
issuer: String,
) -> Result<String, Box<dyn std::error::Error>> {
let url = self.get_url(label, issuer);
let code = QrCode::new(&url)?;
let mut vec = Vec::new();
let size: u32 = ((code.width() + 8) * 8) as u32;
let encoder = image::png::PNGEncoder::new(&mut vec);
encoder.encode(
&code.render::<Luma<u8>>().build().to_vec(),
size,
size,
image::ColorType::L8,
)?;
Ok(base64::encode(vec))
}
}