otp/
lib.rs

1
2use std::time::{SystemTime,Duration};
3
4use sha1::Sha1;
5use hmac::{Hmac, Mac};
6
7pub fn hotp( secret: &[u8], counter: u64) -> u32 {
8  // build hmac key from counter
9  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    // hmac the key and secret
21    let hash = hmac_sha1(secret, hmac_message);
22
23    // calculate the dynamic offset for the value
24    let dynamic_offset = ( hash[19] & (0xf as u8) ) as usize ;
25
26    // build the u32 code from the hash
27    ( ( ( 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    // Create the hasher with the key. We can use expect for Hmac algorithms as they allow arbitrary key sizes.
56    let mut hasher: Hmac<Sha1> = Mac::new_from_slice(key) .expect("HMAC algoritms can take keys of any size");
57
58    // hash the message
59    hasher.update(message);
60
61    // finalize the hash and convert to a static array
62    hasher.finalize().into_bytes().into()
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    // "12345678901234567890"
70    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}