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 std::time::{SystemTime,Duration};

use sha1::Sha1;
use hmac::{Hmac, Mac};

pub fn hotp( secret: &[u8], counter: u64) -> u32 {
  // build hmac key from counter
  let hmac_message: &[u8; 8] = &[
    ( ( counter >> 56 ) & 0xff ) as u8,
    ( ( counter >> 48 ) & 0xff ) as u8,
    ( ( counter >> 40 ) & 0xff ) as u8,
    ( ( counter >> 32 ) & 0xff ) as u8,
    ( ( counter >> 24 ) & 0xff ) as u8,
    ( ( counter >> 16 ) & 0xff ) as u8,
    ( ( counter >> 8 ) & 0xff ) as u8,
    ( ( counter >> 0 ) & 0xff ) as u8,
    ];

    // hmac the key and secret
    let hash = hmac_sha1(secret, hmac_message);

    // calculate the dynamic offset for the value
    let dynamic_offset = ( hash[19] & (0xf as u8) ) as usize ;

    // build the u32 code from the hash
    ( ( ( hash[dynamic_offset] as u32 ) & 0x7f ) << 24
    | ( hash[dynamic_offset+1] as u32 ) << 16
    | ( hash[dynamic_offset+2] as u32 ) << 8
    | ( hash[dynamic_offset+3] as u32 )
    ) as u32
}

pub fn hotp_validate(secret: &[u8], counter: u64, guess: u32, guess_digits: u32) -> bool {
    guess == ( hotp(secret, counter) % 10u32.pow(guess_digits) )
}

pub fn totp( secret: &[u8], time: SystemTime, window: Duration) -> u32 {
    let counter: u64 = 
        time.duration_since(SystemTime::UNIX_EPOCH)
            .expect("We should never be getting authentication requests 50 years in the past")
            .as_secs()
            / window.as_secs();

    hotp(secret, counter)
}

pub fn totp_validate( secret: &[u8], time: SystemTime, window_seconds: Duration, guess: u32,
                       guess_digits: u32) -> bool {
    guess == ( totp(secret, time, window_seconds) % 10u32.pow(guess_digits) )
}


fn hmac_sha1(key: &[u8], message: &[u8]) -> [u8;20] {
    // Create the hasher with the key. We can use expect for Hmac algorithms as they allow arbitrary key sizes.
    let mut hasher: Hmac<Sha1> = Mac::new_from_slice(key) .expect("HMAC algoritms can take keys of any size");

    // hash the message
    hasher.update(message);

    // finalize the hash and convert to a static array
    hasher.finalize().into_bytes().into()
}

#[cfg(test)]
mod tests {
    use super::*;

    // "12345678901234567890"
    const HOTP_TEST_SECRET: &'static[u8] = &[
        0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30,
        0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30,
    ];

    const HOTP_TEST_VALUES: [u32; 10] = [
        1284755224,
        1094287082,
        137359152,
        1726969429,
        1640338314,
        868254676,
        1918287922,
        82162583,
        673399871,
        645520489,
    ];

    #[test]
    fn test_hotp() {
        for counter in 0..10_usize {
            if HOTP_TEST_VALUES[counter] !=  hotp(HOTP_TEST_SECRET, counter as u64 ) {
                panic!("Expected {} for counter {}, found {}.", HOTP_TEST_VALUES[counter], counter,
                    hotp(HOTP_TEST_SECRET, counter as u64) );
            }
        }
    }

    #[test]
    fn test_hotp_4_digits() {
        for counter in 0..10_usize {
            if !hotp_validate(HOTP_TEST_SECRET, counter as u64 , HOTP_TEST_VALUES[counter] % 10u32.pow(4), 4) {
                panic!("Error validating code {} for counter {}.", HOTP_TEST_VALUES[counter] % 10u32.pow(4), counter);
            }
        }
    }

    #[test]
    fn test_totp() {
        let window: Duration = Duration::from_secs(30);
        let mut time: SystemTime = SystemTime::UNIX_EPOCH;
       for counter in 0..10_usize {
           if HOTP_TEST_VALUES[counter] !=  totp(HOTP_TEST_SECRET, time, window ) {
               panic!("Expected {} for counter {}, found {}.", HOTP_TEST_VALUES[counter], counter,
                    hotp(HOTP_TEST_SECRET, counter as u64) );
           }
           time += window;
       }
    }

    #[test]
    fn test_totp_4_digits() {
        let window: Duration = Duration::from_secs(30);
        let mut time: SystemTime = SystemTime::UNIX_EPOCH;
        for counter in 0..10_usize {
            if !totp_validate(HOTP_TEST_SECRET, time, window, HOTP_TEST_VALUES[counter] % 10u32.pow(4), 4) {
                panic!("Error validating code {} for counter {}.", HOTP_TEST_VALUES[counter] % 10u32.pow(4), counter);
            }
            if totp_validate(HOTP_TEST_SECRET, time+window, window, HOTP_TEST_VALUES[counter] % 10u32.pow(4), 4) {
                panic!("Error incorrectly validating code {} for counter {}.", HOTP_TEST_VALUES[counter] % 10u32.pow(4), counter+1);
            }
            time += window;
        }
    }
}