1use std::time::{Duration, SystemTime};
3
4pub use crypto;
5pub use crypto::digest::Digest;
6pub use crypto::sha1::Sha1;
7
8use crate::config::TotpOptions;
9use crate::{TotpError, TotpResult};
10
11use serde::{Deserialize, Serialize};
12
13use super::secrets;
14
15static ALPHABET: base32::Alphabet = base32::Alphabet::Rfc4648 { padding: false };
16
17pub const RFC6238_RECOMMENDED_TIMESTEP: Duration = Duration::from_secs(30);
20
21#[derive(Serialize, Deserialize, Debug, Clone)]
22pub enum TokenAlgorithm {
23 #[serde(rename = "sha1")]
24 TotpSha1,
25 #[cfg(feature = "rsa_stoken")]
26 #[serde(rename = "stoken")]
27 SToken,
28}
29
30impl Copy for TokenAlgorithm {}
31
32#[allow(dead_code)]
33trait AsDigest {
34 fn as_digest(&self) -> Box<dyn Digest>;
35}
36
37impl AsDigest for TokenAlgorithm {
38 fn as_digest(&self) -> Box<dyn Digest> {
39 Box::new(match self {
40 TokenAlgorithm::TotpSha1 => Sha1::new(),
41 #[cfg(feature = "rsa_stoken")]
42 TokenAlgorithm::SToken => unreachable!("SToken cannot be used as a digest method"),
43 })
44 }
45}
46
47pub fn standard_totp(name: &str, options: &TotpOptions) -> TotpResult<String> {
67 let secret = secrets::get_secret(name, options)?;
68 generate_sha1_code(secret)
69}
70
71pub fn clean_secret(secret: &str) -> String {
73 secret.replace(' ', "").to_uppercase()
74}
75
76pub fn generate_sha1_code(secret: String) -> TotpResult<String> {
90 let now = SystemTime::now();
91 let seconds: Duration = now
92 .duration_since(SystemTime::UNIX_EPOCH)
93 .expect("Can't get time since UNIX_EPOCH?");
94
95 let clean_secret = secret.replace(' ', "").to_uppercase();
96 let secret = base32::decode(ALPHABET, &clean_secret)
97 .ok_or(TotpError("Failed to decode secret from base32"))?;
98
99 let algo_sha1 = Sha1::new();
100 totp(&secret, seconds, RFC6238_RECOMMENDED_TIMESTEP, 6, algo_sha1)
101}
102
103const DIGITS_MODULUS: [u32; 9] = [
104 1u32, 10u32, 100u32, 1000u32, 10_000u32, 100_000u32, 1_000_000u32, 10_000_000u32, 100_000_000u32, ];
114
115pub fn totp<D>(
133 secret: &[u8],
134 time_since_epoch: Duration,
135 time_step: Duration,
136 length: usize,
137 algo: D,
138) -> TotpResult<String>
139where
140 D: Digest,
141{
142 use byteorder::{BigEndian, ByteOrder};
143 use crypto::{hmac::Hmac, mac::Mac};
144
145 let mut buf: [u8; 8] = [0; 8];
146 BigEndian::write_u64(&mut buf, time_since_epoch.as_secs() / time_step.as_secs());
147
148 let mut hmac1 = Hmac::new(algo, secret);
149 hmac1.input(&buf);
150 let mac_result = hmac1.result();
151 let signature = mac_result.code();
152
153 let modulus: u32 = DIGITS_MODULUS[length];
154
155 let code: u32 = truncate(signature) % modulus;
156
157 Ok(format!("{:0>width$}", code, width = length))
161}
162
163fn truncate(signature: &[u8]) -> u32 {
164 let offset: usize = (signature[signature.len() - 1] & 0xF).into();
165 let bytes = &signature[offset..offset + std::mem::size_of::<u32>()];
166
167 let high = u32::from(bytes[0]);
168 let high_num = (high & 0x7F) << 24;
169
170 let mid = u32::from(bytes[1]);
171 let mid_num = mid << 16;
172
173 let lower = u32::from(bytes[2]);
174 let lower_num = lower << 8;
175
176 let bottom = u32::from(bytes[3]);
177
178 high_num | mid_num | lower_num | bottom
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 fn rfc6238_test<D: Digest>(
188 time_since_epoch: Duration,
189 digest: D,
190 expected_code: &str,
191 ) -> TotpResult<()> {
192 const RFC_SECRET_SEED: &[u8] = b"12345678901234567890";
193
194 let secret: Vec<u8> = std::iter::repeat(RFC_SECRET_SEED)
196 .flatten()
197 .take(digest.output_bytes())
198 .cloned()
199 .collect();
200
201 let code = totp(
202 &secret,
203 time_since_epoch,
204 RFC6238_RECOMMENDED_TIMESTEP,
205 8,
206 digest,
207 )?;
208
209 assert_eq!(code, expected_code);
210
211 Ok(())
212 }
213
214 #[cfg(test)]
215 #[test]
216 fn rfc6238_sha1_tests() -> TotpResult<()> {
217 fn algo() -> impl Digest {
221 Sha1::new()
222 }
223
224 rfc6238_test(Duration::from_secs(59), algo(), "94287082")?;
225 rfc6238_test(Duration::from_secs(1_111_111_109), algo(), "07081804")?;
226 rfc6238_test(Duration::from_secs(1_111_111_111), algo(), "14050471")?;
227 rfc6238_test(Duration::from_secs(1_234_567_890), algo(), "89005924")?;
228 rfc6238_test(Duration::from_secs(2_000_000_000), algo(), "69279037")?;
229 rfc6238_test(Duration::from_secs(20_000_000_000), algo(), "65353130")?;
230
231 Ok(())
232 }
233
234 #[cfg(test)]
235 #[test]
236 fn rfc6238_sha256_tests() -> TotpResult<()> {
237 fn algo() -> impl Digest {
241 crypto::sha2::Sha256::new()
242 }
243
244 rfc6238_test(Duration::from_secs(59), algo(), "46119246")?;
245 rfc6238_test(Duration::from_secs(1_111_111_109), algo(), "68084774")?;
246 rfc6238_test(Duration::from_secs(1_111_111_111), algo(), "67062674")?;
247 rfc6238_test(Duration::from_secs(1_234_567_890), algo(), "91819424")?;
248 rfc6238_test(Duration::from_secs(2_000_000_000), algo(), "90698825")?;
249 rfc6238_test(Duration::from_secs(20_000_000_000), algo(), "77737706")?;
250
251 Ok(())
252 }
253
254 #[cfg(test)]
255 #[test]
256 fn rfc6238_sha512_tests() -> TotpResult<()> {
257 fn algo() -> impl Digest {
261 crypto::sha2::Sha512::new()
262 }
263
264 rfc6238_test(Duration::from_secs(59), algo(), "90693936")?;
265 rfc6238_test(Duration::from_secs(1_111_111_109), algo(), "25091201")?;
266 rfc6238_test(Duration::from_secs(1_111_111_111), algo(), "99943326")?;
267 rfc6238_test(Duration::from_secs(1_234_567_890), algo(), "93441116")?;
268 rfc6238_test(Duration::from_secs(2_000_000_000), algo(), "38618901")?;
269 rfc6238_test(Duration::from_secs(20_000_000_000), algo(), "47863826")?;
270
271 Ok(())
272 }
273}