oci_image_spec/image_digest/
algorithm.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use std::io::{Error, ErrorKind};
4
5/// SHA256 with hex encoding (lower case only)
6pub const SHA256: &str = "sha256";
7/// SHA384 with hex encoding (lower case only)
8pub const SHA384: &str = "sha384";
9/// SHA512 with hex encoding (lower case only)
10pub const SHA512: &str = "sha512";
11/// BLAKE3 with hex encoding (lower case only)
12pub const BLAKE3: &str = "blake3";
13
14// CANONICAL is the primary digest algorithm used with the distribution
15// project. Other digests may be used but this one is the primary storage
16// digest.
17pub const CANONICAL: &str = SHA256;
18
19use digest::DynDigest;
20use sha2::{Digest, Sha256, Sha384, Sha512};
21
22/// CryptoHash is the interface that any hash algorithm must implement
23pub trait CryptoHash {
24    // available reports whether the given hash function is usable in the current binary.
25    fn available(self) -> bool;
26    // size returns the length, in bytes, of a digest resulting from the given hash function.
27    fn size(self) -> isize;
28    // string returns the name of the hash function.
29    fn string(self) -> &'static str;
30    // set implemented to allow use of Algorithm as a command line flag.
31    fn set(self, _: &str) -> Result<Self, Error>
32    where
33        Self: Sized;
34    // digester returns a new digester for the specified algorithm. If the algorithm
35    // does not have a digester implementation, nil will be returned. This can be
36    // checked by calling Available before calling Digester.
37    fn digester(&self) -> Box<dyn DynDigest>;
38    // hash returns a new hash as used by the algorithm. If not available, the
39    // method will panic. Check Algorithm.Available() before calling.
40    fn hash(self) -> Box<dyn DynDigest>;
41    // encode encodes the raw bytes of a digest, typically from a hash.Hash, into
42    // the encoded portion of the digest.
43    fn encode(&self, _: &[u8]) -> String;
44    // from_reader returns the digest of the reader using the algorithm.
45    fn from_reader<R: std::io::Read>(&self, _: R) -> String;
46    // from_bytes digests the input and returns a Digest.
47    fn from_bytes(&self, _: &[u8]) -> String;
48    // from_string digests the string input and returns a Digest.
49    fn from_string(&self, _: &str) -> String;
50    // from_file digests the string input and returns a Digest.
51    fn from_file(&self, _: &str) -> Result<String, Error>;
52    // Validate validates the encoded portion string
53    fn validate(&self, _: &str) -> bool;
54}
55
56#[derive(Serialize, Deserialize, Debug, Clone)]
57pub struct Algorithm<'a> {
58    pub name: &'a str,
59    pub bitsize: isize,
60}
61
62impl Algorithm<'_> {
63    pub fn new(name: &str, size: isize) -> Algorithm {
64        Algorithm {
65            name,
66            bitsize: size,
67        }
68    }
69}
70
71impl CryptoHash for Algorithm<'static> {
72    fn available(self) -> bool {
73        true
74    }
75
76    fn string(self) -> &'static str {
77        self.name
78    }
79
80    fn size(self) -> isize {
81        self.bitsize
82    }
83
84    fn set(self, name: &str) -> Result<Self, Error> {
85        match name {
86            SHA256 => Ok(Algorithm::new(SHA256, 256)),
87            SHA384 => Ok(Algorithm::new(SHA384, 384)),
88            SHA512 => Ok(Algorithm::new(SHA512, 512)),
89            _ => Err(Error::new(ErrorKind::Other, "Unsupported algorithm")),
90        }
91    }
92
93    fn digester(&self) -> Box<dyn DynDigest> {
94        match self.name {
95            SHA256 => Box::new(Sha256::new()),
96            SHA384 => Box::new(Sha384::new()),
97            SHA512 => Box::new(Sha512::new()),
98            _ => panic!("Unsupported algorithm"),
99        }
100    }
101
102    fn hash(self) -> Box<dyn DynDigest> {
103        self.digester()
104    }
105
106    fn encode(&self, bytes: &[u8]) -> String {
107        let mut digest: Box<dyn DynDigest>;
108        match self.name {
109            SHA256 => {
110                digest = Box::new(Sha256::new());
111            }
112            SHA384 => {
113                digest = Box::new(Sha384::new());
114            }
115            SHA512 => {
116                digest = Box::new(Sha512::new());
117            }
118            BLAKE3 => {
119                let mut digest = blake3::Hasher::new();
120                digest.update(bytes);
121                return hex::encode(digest.finalize().as_bytes());
122            }
123            _ => panic!("Unsupported algorithm"),
124        };
125        digest.update(bytes);
126        hex::encode(digest.finalize())
127    }
128
129    fn from_reader<R: std::io::Read>(&self, reader: R) -> String {
130        let mut digest: Box<dyn DynDigest>;
131        match self.name {
132            SHA256 => {
133                digest = Box::new(Sha256::new());
134            }
135            SHA384 => {
136                digest = Box::new(Sha384::new());
137            }
138            SHA512 => {
139                digest = Box::new(Sha512::new());
140            }
141            BLAKE3 => {
142                let mut digest = blake3::Hasher::new();
143                let mut reader = reader;
144                let mut buffer = [0; 1024];
145                loop {
146                    let len = match reader.read(&mut buffer) {
147                        Ok(len) => len,
148                        Err(_) => break,
149                    };
150                    if len == 0 {
151                        break;
152                    }
153                    digest.update(&buffer[..len]);
154                }
155                return hex::encode(digest.finalize().as_bytes());
156            }
157            _ => panic!("Unsupported algorithm"),
158        };
159        let mut reader = reader;
160        let mut buffer = [0; 1024];
161        loop {
162            let len = match reader.read(&mut buffer) {
163                Ok(len) => len,
164                Err(_) => break,
165            };
166            if len == 0 {
167                break;
168            }
169            digest.update(&buffer[..len]);
170        }
171        hex::encode(digest.finalize())
172    }
173
174    fn from_bytes(&self, bytes: &[u8]) -> String {
175        let mut digest: Box<dyn DynDigest>;
176        match self.name {
177            SHA256 => {
178                digest = Box::new(Sha256::new());
179            }
180            SHA384 => {
181                digest = Box::new(Sha384::new());
182            }
183            SHA512 => {
184                digest = Box::new(Sha512::new());
185            }
186            BLAKE3 => {
187                let mut digest = blake3::Hasher::new();
188                digest.update(bytes);
189                return hex::encode(digest.finalize().as_bytes());
190            }
191            _ => panic!("Unsupported algorithm"),
192        };
193        digest.update(bytes);
194        hex::encode(digest.finalize())
195    }
196
197    fn from_string(&self, str: &str) -> String {
198        let mut digest: Box<dyn DynDigest>;
199        match self.name {
200            SHA256 => {
201                digest = Box::new(Sha256::new());
202            }
203            SHA384 => {
204                digest = Box::new(Sha384::new());
205            }
206            SHA512 => {
207                digest = Box::new(Sha512::new());
208            }
209            BLAKE3 => {
210                let mut digest = blake3::Hasher::new();
211                digest.update(str.as_bytes());
212                return hex::encode(digest.finalize().as_bytes());
213            }
214            _ => panic!("Unsupported algorithm"),
215        };
216        digest.update(str.as_bytes());
217        hex::encode(digest.finalize())
218    }
219
220    fn from_file(&self, path: &str) -> Result<String, Error> {
221        let mut digest: Box<dyn DynDigest>;
222        match self.name {
223            SHA256 => {
224                digest = Box::new(Sha256::new());
225            }
226            SHA384 => {
227                digest = Box::new(Sha384::new());
228            }
229            SHA512 => {
230                digest = Box::new(Sha512::new());
231            }
232            BLAKE3 => {
233                let mut digest = blake3::Hasher::new();
234                let mut file = std::fs::File::open(path)?;
235                let mut buffer = [0; 1024];
236                loop {
237                    let len = match std::io::Read::read(&mut file, &mut buffer) {
238                        Ok(len) => len,
239                        Err(_) => break,
240                    };
241                    if len == 0 {
242                        break;
243                    }
244                    digest.update(&buffer[..len]);
245                }
246                return Ok(hex::encode(digest.finalize().as_bytes()));
247            }
248            _ => panic!("Unsupported algorithm"),
249        };
250        let mut file = std::fs::File::open(path)?;
251        let mut buffer = [0; 1024];
252        loop {
253            let len = match std::io::Read::read(&mut file, &mut buffer) {
254                Ok(len) => len,
255                Err(_) => break,
256            };
257            if len == 0 {
258                break;
259            }
260            digest.update(&buffer[..len]);
261        }
262        Ok(hex::encode(digest.finalize()))
263    }
264
265    fn validate(&self, str: &str) -> bool {
266        let re = regex::Regex::new(&format!(r"^[0-9a-f]{{{}}}$", self.bitsize / 4)).unwrap();
267        re.is_match(str)
268    }
269}
270
271/// A digest is a cryptographic hash of a data stream.
272pub struct Algorithms<'a> {
273    algorithms: HashMap<&'a str, isize>,
274}
275
276impl<'a> Algorithms<'a> {
277    pub fn new() -> Self {
278        let mut algs = Algorithms {
279            algorithms: HashMap::new(),
280        };
281        algs.register_algorithm(CANONICAL, 256);
282        algs.register_algorithm(SHA256, 256);
283        algs.register_algorithm(SHA384, 384);
284        algs.register_algorithm(SHA512, 512);
285        algs.register_algorithm(BLAKE3, 256);
286        algs
287    }
288
289    // Add an algorithm to the list of available algorithms.
290    pub fn register_algorithm(self: &mut Self, name: &'a str, size: isize) -> bool {
291        match self.algorithms.get(name) {
292            Some(_) => false,
293            None => {
294                self.algorithms.insert(name, size);
295                true
296            }
297        }
298    }
299
300    pub fn get_algorithm(self: &Self, name: &'a str) -> Option<Algorithm<'a>> {
301        match self.algorithms.get(name) {
302            Some(size) => Some(Algorithm::new(name, *size)),
303            None => None,
304        }
305    }
306}
307
308#[cfg(test)]
309mod tests {
310    use super::{Algorithms, CryptoHash};
311
312    #[test]
313    fn encode_canonical() {
314        let algs = Algorithms::new();
315        let alg = algs.get_algorithm(super::CANONICAL).unwrap();
316        assert_eq!(
317            alg.encode(b"hello"),
318            "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
319        );
320        assert_eq!(
321            alg.encode(b"hello"),
322            "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
323        );
324    }
325
326    #[test]
327    fn encode() {
328        let algs = Algorithms::new();
329        let alg = algs.get_algorithm(super::SHA256).unwrap();
330        assert_eq!(
331            alg.encode(b"hello"),
332            "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
333        );
334        assert_eq!(
335            alg.encode(b"hello"),
336            "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
337        );
338    }
339
340    #[test]
341    fn encode_blake3() {
342        let algs = Algorithms::new();
343        let alg = algs.get_algorithm(super::BLAKE3).unwrap();
344        assert_eq!(
345            alg.encode(b"hello"),
346            "ea8f163db38682925e4491c5e58d4bb3506ef8c14eb78a86e908c5624a67200f"
347        );
348    }
349
350    #[test]
351    fn from_reader() {
352        let algs = Algorithms::new();
353        let alg = algs.get_algorithm(super::SHA256).unwrap();
354        assert_eq!(
355            alg.from_reader(b"hello".as_ref()),
356            "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
357        );
358    }
359
360    #[test]
361    fn from_bytes() {
362        let algs = Algorithms::new();
363        let alg = algs.get_algorithm(super::SHA256).unwrap();
364        assert_eq!(
365            alg.from_bytes(b"hello"),
366            "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
367        );
368    }
369
370    #[test]
371    fn from_file() {
372        let algs = Algorithms::new();
373        let alg = algs.get_algorithm(super::SHA256).unwrap();
374        assert_eq!(
375            alg.from_file(".gitignore").unwrap(),
376            "fca9125f1d755424b55a18877213a746135c1ce0087f9471a6f98cdd4600df83"
377        );
378    }
379
380    #[test]
381    fn validate() {
382        let algs = Algorithms::new();
383        let alg = algs.get_algorithm(super::SHA256).unwrap();
384        assert_eq!(
385            alg.validate("2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"),
386            true
387        );
388    }
389
390    #[test]
391    fn validate_blake3() {
392        let algs = Algorithms::new();
393        let alg = algs.get_algorithm(super::BLAKE3).unwrap();
394        assert_eq!(
395            alg.validate("2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"),
396            true
397        );
398    }
399}