Skip to main content

tackler_core/kernel/
hash.rs

1/*
2 * Tackler-NG 2023-2025
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6use crate::tackler;
7use digest::DynDigest;
8use std::fmt::{Debug, Formatter, Write};
9use tackler_api::metadata::Checksum;
10
11#[derive(Clone)]
12pub struct Hash {
13    hash_algo: String,
14    hasher: Box<dyn DynDigest>,
15}
16
17impl Default for Hash {
18    fn default() -> Self {
19        Hash {
20            hash_algo: "SHA-256".to_string(),
21            hasher: Box::new(sha2::Sha256::default()),
22        }
23    }
24}
25
26impl Debug for Hash {
27    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
28        write!(f, "hash: {:?}", self.hash_algo)
29    }
30}
31
32impl Hash {
33    const NAMES: &'static [&'static str] =
34        &["SHA-256", "SHA-512", "SHA-512/256", "SHA3-256", "SHA3-512"];
35
36    /// Hash function based on algorithm name
37    ///
38    /// Supported algorithms: "SHA-256", "SHA-512",
39    /// "SHA-512/256", "SHA3-256", "SHA3-512"
40    ///
41    /// # Errors
42    /// Returns `Err` in case of invalid hash algorithm
43    pub fn from(algo: &str) -> Result<Hash, tackler::Error> {
44        match algo {
45            "SHA-256" => Ok(Hash {
46                hash_algo: "SHA-256".to_string(),
47                hasher: Box::new(sha2::Sha256::default()),
48            }),
49            "SHA-512" => Ok(Hash {
50                hash_algo: "SHA-512".to_string(),
51                hasher: Box::new(sha2::Sha512::default()),
52            }),
53            "SHA-512/256" => Ok(Hash {
54                hash_algo: "SHA-512/256".to_string(),
55                hasher: Box::new(sha2::Sha512_256::default()),
56            }),
57            "SHA3-256" => Ok(Hash {
58                hash_algo: "SHA3-256".to_string(),
59                hasher: Box::new(sha3::Sha3_256::default()),
60            }),
61            "SHA3-512" => Ok(Hash {
62                hash_algo: "SHA3-512".to_string(),
63                hasher: Box::new(sha3::Sha3_512::default()),
64            }),
65            _ => {
66                let mut msg = format!("Unknown hash algorithm: '{algo}'. ");
67                writeln!(msg, "Valid options are: {}", Self::NAMES.join(", "))?;
68                Err(msg.into())
69            }
70        }
71    }
72
73    /// Calculate checksum
74    #[must_use]
75    pub fn checksum(&self, items: &[String], separator: &[u8]) -> Checksum {
76        let mut hasher = self.hasher.clone();
77
78        for i in items {
79            hasher.update(i.as_bytes());
80            hasher.update(separator);
81        }
82        let hash = hasher.finalize();
83
84        Checksum {
85            algorithm: self.hash_algo.clone(),
86            value: hash.iter().fold(String::new(), |mut output, b| {
87                let _ = write!(output, "{b:02x}");
88                output
89            }),
90        }
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn hasher_no_input() {
100        let hash = Hash::from("SHA-256").unwrap(/*:test:*/);
101
102        let uuids = vec![];
103        let cs = hash.checksum(&uuids, "\n".as_bytes());
104
105        // same as:
106        //   echo -ne "" | sha256sum
107        assert_eq!(
108            cs.value,
109            "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
110        );
111    }
112
113    #[test]
114    fn hasher_empty_input() {
115        let hash = Hash::from("SHA-256").unwrap(/*:test:*/);
116
117        let uuids = vec![String::new()];
118        let cs = hash.checksum(&uuids, "\n".as_bytes());
119
120        // same as:
121        //   echo -ne "\n" | sha256sum
122        assert_eq!(
123            cs.value,
124            "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"
125        );
126    }
127
128    #[test]
129    fn hasher_multiple_rounds() {
130        let hash = Hash::from("SHA-256").unwrap(/*:test:*/);
131
132        let foo = vec!["foo".to_string()];
133        let cs_foo = hash.checksum(&foo, "\n".as_bytes());
134
135        assert_eq!(
136            cs_foo.value,
137            "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c"
138        );
139
140        let bar = vec!["bar".to_string()];
141        let cs_bar = hash.checksum(&bar, "\n".as_bytes());
142        assert_eq!(
143            cs_bar.value,
144            "7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730"
145        );
146    }
147
148    #[test]
149    // test: 1b6876c4-3ef2-43f4-b14b-1bdaa56180fa
150    fn hash_functions() {
151        let mut c = 0;
152        for name in Hash::NAMES {
153            let hash = Hash::from(name);
154            assert!(hash.is_ok());
155            c += 1;
156        }
157        assert_eq!(c, Hash::NAMES.len());
158    }
159
160    #[test]
161    // test: 1edf6ced-8bfb-49e8-a307-c05cf7f6cc7e
162    fn hasher_err() {
163        let hash = Hash::from("foo");
164        let mut msg = "Unknown hash algorithm: 'foo'. ".to_string();
165        let _ = writeln!(
166            msg,
167            "Valid options are: SHA-256, SHA-512, SHA-512/256, SHA3-256, SHA3-512"
168        );
169
170        assert!(hash.is_err());
171        assert_eq!(hash.err().unwrap(/*:test:*/).to_string(), msg);
172    }
173
174    //
175    // Tests of the correct selection of Hash Algorithm
176    //
177    fn get_test_vector() -> Vec<String> {
178        vec![
179            "9c123cbe-4acd-475d-bbcf-96c1fcba58cb".to_string(),
180            "2e546b18-6ce6-4bb3-9f4b-21b77a768a4c".to_string(),
181            "67bdab27-da08-4647-b0d1-57c9ed129657".to_string(),
182        ]
183    }
184
185    #[test]
186    fn hasher_sha2_256() {
187        let hash = Hash::from("SHA-256").unwrap(/*:test:*/);
188
189        let uuids = get_test_vector();
190        let cs = hash.checksum(&uuids, "\n".as_bytes());
191
192        assert_eq!(
193            cs.value,
194            "16418783ef294f830721159ee59cc3388c8b69c13afba2256cf756c6097fe687"
195        );
196    }
197    #[test]
198    fn hasher_sha2_512() {
199        let hash = Hash::from("SHA-512").unwrap(/*:test:*/);
200
201        let uuids = get_test_vector();
202        let cs = hash.checksum(&uuids, "\n".as_bytes());
203
204        assert_eq!(
205            cs.value,
206            "51a370f86b7218012c8a7a555a1ae099b32fe83ed032e82481de5fa7ea3a90baa6948a4f559668ad3696a08b0445fe4e5964dba695b45653b4e678ab200ede17"
207        );
208    }
209
210    #[test]
211    fn hasher_sha2_512_256() {
212        let hash = Hash::from("SHA-512/256").unwrap(/*:test:*/);
213
214        let uuids = get_test_vector();
215        let cs = hash.checksum(&uuids, "\n".as_bytes());
216
217        assert_eq!(
218            cs.value,
219            "fd5e794d47589c83ed5def2485699f232aed7e7df2869ef8b681cbf07af7cb66"
220        );
221    }
222
223    #[test]
224    fn hasher_sha3_256() {
225        let hash = Hash::from("SHA3-256").unwrap(/*:test:*/);
226
227        let uuids = get_test_vector();
228        let cs = hash.checksum(&uuids, "\n".as_bytes());
229
230        assert_eq!(
231            cs.value,
232            "ee677c3486b0b6c8d61eb3b5cb650762dbf741bb6b050934f4c3c4f6551d1841"
233        );
234    }
235
236    #[test]
237    fn hasher_sha3_512() {
238        let hash = Hash::from("SHA3-512").unwrap(/*:test:*/);
239
240        let uuids = get_test_vector();
241        let cs = hash.checksum(&uuids, "\n".as_bytes());
242
243        assert_eq!(
244            cs.value,
245            "6cc3e6d3eb1d920ac3439a0b748244aa5f997ba0a813457bae7b99f3f58f035ce18eb3646a35517c19987dfe0bb60ebcc81ee5320f4d2348132fdb55bb8d8a25"
246        );
247    }
248}