1
2use std::time::{SystemTime,Duration};
3
4use sha1::Sha1;
5use hmac::{Hmac, Mac};
6
7pub fn hotp( secret: &[u8], counter: u64) -> u32 {
8 let hmac_message: &[u8; 8] = &[
10 ( ( counter >> 56 ) & 0xff ) as u8,
11 ( ( counter >> 48 ) & 0xff ) as u8,
12 ( ( counter >> 40 ) & 0xff ) as u8,
13 ( ( counter >> 32 ) & 0xff ) as u8,
14 ( ( counter >> 24 ) & 0xff ) as u8,
15 ( ( counter >> 16 ) & 0xff ) as u8,
16 ( ( counter >> 8 ) & 0xff ) as u8,
17 ( ( counter >> 0 ) & 0xff ) as u8,
18 ];
19
20 let hash = hmac_sha1(secret, hmac_message);
22
23 let dynamic_offset = ( hash[19] & (0xf as u8) ) as usize ;
25
26 ( ( ( hash[dynamic_offset] as u32 ) & 0x7f ) << 24
28 | ( hash[dynamic_offset+1] as u32 ) << 16
29 | ( hash[dynamic_offset+2] as u32 ) << 8
30 | ( hash[dynamic_offset+3] as u32 )
31 ) as u32
32}
33
34pub fn hotp_validate(secret: &[u8], counter: u64, guess: u32, guess_digits: u32) -> bool {
35 guess == ( hotp(secret, counter) % 10u32.pow(guess_digits) )
36}
37
38pub fn totp( secret: &[u8], time: SystemTime, window: Duration) -> u32 {
39 let counter: u64 =
40 time.duration_since(SystemTime::UNIX_EPOCH)
41 .expect("We should never be getting authentication requests 50 years in the past")
42 .as_secs()
43 / window.as_secs();
44
45 hotp(secret, counter)
46}
47
48pub fn totp_validate( secret: &[u8], time: SystemTime, window_seconds: Duration, guess: u32,
49 guess_digits: u32) -> bool {
50 guess == ( totp(secret, time, window_seconds) % 10u32.pow(guess_digits) )
51}
52
53
54fn hmac_sha1(key: &[u8], message: &[u8]) -> [u8;20] {
55 let mut hasher: Hmac<Sha1> = Mac::new_from_slice(key) .expect("HMAC algoritms can take keys of any size");
57
58 hasher.update(message);
60
61 hasher.finalize().into_bytes().into()
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 const HOTP_TEST_SECRET: &'static[u8] = &[
71 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30,
72 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30,
73 ];
74
75 const HOTP_TEST_VALUES: [u32; 10] = [
76 1284755224,
77 1094287082,
78 137359152,
79 1726969429,
80 1640338314,
81 868254676,
82 1918287922,
83 82162583,
84 673399871,
85 645520489,
86 ];
87
88 #[test]
89 fn test_hotp() {
90 for counter in 0..10_usize {
91 if HOTP_TEST_VALUES[counter] != hotp(HOTP_TEST_SECRET, counter as u64 ) {
92 panic!("Expected {} for counter {}, found {}.", HOTP_TEST_VALUES[counter], counter,
93 hotp(HOTP_TEST_SECRET, counter as u64) );
94 }
95 }
96 }
97
98 #[test]
99 fn test_hotp_4_digits() {
100 for counter in 0..10_usize {
101 if !hotp_validate(HOTP_TEST_SECRET, counter as u64 , HOTP_TEST_VALUES[counter] % 10u32.pow(4), 4) {
102 panic!("Error validating code {} for counter {}.", HOTP_TEST_VALUES[counter] % 10u32.pow(4), counter);
103 }
104 }
105 }
106
107 #[test]
108 fn test_totp() {
109 let window: Duration = Duration::from_secs(30);
110 let mut time: SystemTime = SystemTime::UNIX_EPOCH;
111 for counter in 0..10_usize {
112 if HOTP_TEST_VALUES[counter] != totp(HOTP_TEST_SECRET, time, window ) {
113 panic!("Expected {} for counter {}, found {}.", HOTP_TEST_VALUES[counter], counter,
114 hotp(HOTP_TEST_SECRET, counter as u64) );
115 }
116 time += window;
117 }
118 }
119
120 #[test]
121 fn test_totp_4_digits() {
122 let window: Duration = Duration::from_secs(30);
123 let mut time: SystemTime = SystemTime::UNIX_EPOCH;
124 for counter in 0..10_usize {
125 if !totp_validate(HOTP_TEST_SECRET, time, window, HOTP_TEST_VALUES[counter] % 10u32.pow(4), 4) {
126 panic!("Error validating code {} for counter {}.", HOTP_TEST_VALUES[counter] % 10u32.pow(4), counter);
127 }
128 if totp_validate(HOTP_TEST_SECRET, time+window, window, HOTP_TEST_VALUES[counter] % 10u32.pow(4), 4) {
129 panic!("Error incorrectly validating code {} for counter {}.", HOTP_TEST_VALUES[counter] % 10u32.pow(4), counter+1);
130 }
131 time += window;
132 }
133 }
134}