Skip to main content

seqknock_common/
mac.rs

1/*
2 * Copyright 2023 Jonas Eriksson
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17use std::time::SystemTime;
18
19use sp800_185::KMac;
20
21pub fn epoch_now_s() -> u64 {
22    SystemTime::now()
23        .duration_since(SystemTime::UNIX_EPOCH)
24        .expect("Unable to calculate epoch time")
25        .as_secs()
26}
27
28pub fn epoch_now_ms() -> u128 {
29    SystemTime::now()
30        .duration_since(SystemTime::UNIX_EPOCH)
31        .expect("Unable to calculate epoch time")
32        .as_millis()
33}
34
35pub struct SeqMac {
36    mac: KMac,
37    period: u64,
38}
39
40impl SeqMac {
41    pub fn new(psk: &str, port: u16, period: u64) -> Self {
42        // Generate customization string
43        let custom = format!("seqknock-v1-{port:05}");
44
45        SeqMac {
46            mac: KMac::new_kmac128(psk.as_bytes(), custom.as_bytes()),
47            period,
48        }
49    }
50
51    pub fn calc(&self, time: u64) -> u32 {
52        let mut mac = self.mac.clone();
53        mac.update(&(time / self.period).to_le_bytes());
54        let mut result: [u8; 4] = [0; 4];
55        mac.xof().squeeze(&mut result);
56        u32::from_le_bytes(result)
57    }
58}
59
60#[cfg(test)]
61mod tests {
62
63    use super::SeqMac;
64
65    #[test]
66    fn key_lengths() {
67        let time = 1672531200;
68
69        let mac_1 = SeqMac::new("a", 1, 1);
70        assert_eq!(mac_1.calc(time), 3568379456);
71        // 1025 chars
72        let mac_1025 = SeqMac::new(
73            "iemumahchua4waihauH4eChe7feonohHeimeubah0Hae1Weyaequiech5ohhufaeRahl9uz4shahv8eiceis5efah6iaw3oph6ievol9ohSeeXiocheishel5tuc4logaluoF7eeraetaiwoe3aih5eetee9hoo0iSh4phietha9aix7nohkaibohv6aesu9Dieviulu9Jeil3Ahmah9Aifohk4ahphae8eiKaechei1cie6aehoor9ofu1goo9ahfoo9shaa4aey2obaechai5oopeig6ailoovaeFouv3kihishaeChohPie8eigeeH2eod9ohvah6zue2ca4eGhaimiethiengoongeethemei9nee0IjieBahlik5riekiyaiphahb3lu0hee6otievie6go8Aijohng8ce5ooshophoda2Eunaepeethaecopae1Thuwaeyahpooz9ez7thi3lieL2Eope5air7iet2voh1lah9Oa5poo0eehohzai7rahPhaeghozaex6Eim4Soo7ChooceeNg8Gai6Air1wi5roogeeweic7ohngiewaiGohphoomee0ahdiree9Bedaibah4ook6sujae1oaxughei1quahphee6mohMeoP8hohaeng8niiXiethohchah1ootie4ibai4zaeceex8Iebaem1gu8keG7ing3rahchahHeihieteeQuohjeib1ia5Zie8so1pou2doohi9eelaejixoje2eegh1lee9ohdingaQueecai1aez0eexaez3el2ieB7ievooleefohg3Aidi4kieRoo9eicheiH0DoBohngaeshaigh6Biey8eecooyahchahgh9yoo8aiSho9Pho1aeWi0ohxohs1Kewah8aey2iho7oboh4jied1Ooquool0uel7eeg6roothai8Ahfoo8deh5ahqua7Zipaish7Rooxae7zeeGa6ja8iecoo4Goo8uequei7bohngiyohFoh2uCu8eingi",
74            1,
75            1,
76        );
77        assert_eq!(mac_1025.calc(time), 2346448994);
78        // This does work, but perhaps it should not?
79        let mac_0 = SeqMac::new("", 1, 1);
80        assert_eq!(mac_0.calc(time), 4132275335);
81    }
82
83    #[test]
84    fn with_static_macs() {
85        let mac_asdf_29 = SeqMac::new("asdf", 22, 29);
86        let mac_asdf_30 = SeqMac::new("asdf", 22, 30);
87        let mac_asdf_31 = SeqMac::new("asdf", 22, 31);
88        let mac_asde_30 = SeqMac::new("asde", 22, 30);
89        let mac_asdf_23_30 = SeqMac::new("asdf", 23, 30);
90        let mac_asdf_30_2 = SeqMac::new("asdf", 22, 30);
91        let time = 1672531200;
92
93        // Call twice, check for stability
94        assert_eq!(mac_asdf_29.calc(time), mac_asdf_29.calc(time));
95        assert_eq!(mac_asdf_30.calc(time), mac_asdf_30.calc(time));
96        assert_eq!(mac_asdf_31.calc(time), mac_asdf_31.calc(time));
97
98        // Check for stability over instansiations
99        assert_eq!(mac_asdf_30.calc(time), mac_asdf_30_2.calc(time));
100
101        // Other PSK should yield different result
102        assert_ne!(mac_asdf_30.calc(time), mac_asde_30.calc(time));
103
104        // Jump in time == period should yield different results
105        assert_ne!(mac_asdf_30.calc(time), mac_asdf_30.calc(time + 30));
106        assert_ne!(mac_asdf_30.calc(time), mac_asdf_30.calc(time - 30));
107
108        // Other periods should yield different results
109        assert_ne!(mac_asdf_30.calc(time), mac_asdf_29.calc(time));
110        assert_ne!(mac_asdf_30.calc(time), mac_asdf_31.calc(time));
111
112        // Assure that the point of change is after 30s
113        let result0 = mac_asdf_30.calc(time);
114        let mut offset1 = 1;
115        // Run until result change
116        loop {
117            if result0 != mac_asdf_30.calc(time + offset1) {
118                break;
119            }
120            offset1 += 1;
121        }
122        let result1 = mac_asdf_30.calc(time + offset1);
123        let mut offset2 = 1;
124        // Run until it changes again
125        loop {
126            if result1 != mac_asdf_30.calc(time + offset1 + offset2) {
127                break;
128            }
129            offset2 += 1;
130        }
131        // Check that the change point is correct
132        assert_eq!(offset2, 30);
133
134        // Different ports should yield different results
135        assert_ne!(mac_asdf_30.calc(time), mac_asdf_23_30.calc(time));
136
137        // Check against pre-calculated values to ensure stability of algorithm
138        assert_eq!(mac_asdf_29.calc(time), 2407226594);
139        assert_eq!(mac_asdf_30.calc(time), 2237033466);
140        assert_eq!(mac_asdf_31.calc(time), 967110946);
141    }
142}