apt_cmd/
hash.rs

1// Copyright 2021-2022 System76 <info@system76.com>
2// SPDX-License-Identifier: MPL-2.0
3
4use hex::FromHex;
5use md5::{digest::generic_array::GenericArray, Digest, Md5};
6use sha1::Sha1;
7use sha2::{Sha256, Sha512};
8use std::{io, path::Path};
9use thiserror::Error;
10
11use crate::request::RequestChecksum;
12
13#[derive(Debug, Error)]
14pub enum ChecksumError {
15    #[error("checksum invalid: {0}")]
16    InvalidInput(String),
17
18    #[error("unable to open the file to validate")]
19    FileOpen(#[source] io::Error),
20
21    #[error(
22        "file does not match expected size: found {} KiB but expected {} KiB",
23        found,
24        expected
25    )]
26    InvalidSize { found: u64, expected: u64 },
27
28    #[error("error during read of file")]
29    FileRead(#[source] io::Error),
30
31    #[error("checksum mismatch")]
32    Mismatch,
33}
34
35pub fn compare_hash(
36    path: &Path,
37    expected_size: u64,
38    expected_hash: &RequestChecksum,
39) -> Result<(), ChecksumError> {
40    use std::io::Read;
41
42    let mut file = std::fs::File::open(path).map_err(ChecksumError::FileOpen)?;
43
44    let file_size = file.metadata().unwrap().len();
45    if file_size != expected_size {
46        return Err(ChecksumError::InvalidSize {
47            found: file_size / 1024,
48            expected: expected_size / 1024,
49        });
50    }
51
52    match expected_hash {
53        RequestChecksum::Md5(sum) => {
54            let expected = <[u8; 16]>::from_hex(sum)
55                .map(GenericArray::from)
56                .map_err(|_| ChecksumError::InvalidInput(format!("MD5 {}", sum)))?;
57
58            let mut buffer = vec![0u8; 8 * 1024];
59            let mut hasher = Md5::new();
60
61            loop {
62                match file.read(&mut buffer) {
63                    Ok(0) => break,
64                    Ok(bytes) => hasher.update(&buffer[..bytes]),
65                    Err(why) => return Err(ChecksumError::FileRead(why)),
66                }
67            }
68
69            let hash = &*hasher.finalize();
70
71            if &*expected == hash {
72                Ok(())
73            } else {
74                Err(ChecksumError::Mismatch)
75            }
76        }
77        RequestChecksum::Sha1(sum) => {
78            let expected = <[u8; 20]>::from_hex(sum)
79                .map(GenericArray::from)
80                .map_err(|_| ChecksumError::InvalidInput(format!("SHA1 {}", sum)))?;
81
82            let mut buffer = vec![0u8; 8 * 1024];
83            let mut hasher = Sha1::new();
84
85            loop {
86                match file.read(&mut buffer) {
87                    Ok(0) => break,
88                    Ok(bytes) => hasher.update(&buffer[..bytes]),
89                    Err(why) => return Err(ChecksumError::FileRead(why)),
90                }
91            }
92
93            let hash = &*hasher.finalize();
94
95            if &*expected == hash {
96                Ok(())
97            } else {
98                Err(ChecksumError::Mismatch)
99            }
100        }
101        RequestChecksum::Sha256(sum) => {
102            let expected = <[u8; 32]>::from_hex(sum)
103                .map(GenericArray::from)
104                .map_err(|_| ChecksumError::InvalidInput(format!("SHA256 {}", sum)))?;
105
106            let mut buffer = vec![0u8; 8 * 1024];
107            let mut hasher = Sha256::new();
108
109            loop {
110                match file.read(&mut buffer) {
111                    Ok(0) => break,
112                    Ok(bytes) => hasher.update(&buffer[..bytes]),
113                    Err(why) => return Err(ChecksumError::FileRead(why)),
114                }
115            }
116
117            let hash = &*hasher.finalize();
118
119            if &*expected == hash {
120                Ok(())
121            } else {
122                Err(ChecksumError::Mismatch)
123            }
124        }
125        RequestChecksum::Sha512(sum) => {
126            let expected = <[u8; 64]>::from_hex(sum)
127                .map(GenericArray::from)
128                .map_err(|_| ChecksumError::InvalidInput(format!("SHA512 {}", sum)))?;
129
130            let mut buffer = vec![0u8; 8 * 1024];
131            let mut hasher = Sha512::new();
132
133            loop {
134                match file.read(&mut buffer) {
135                    Ok(0) => break,
136                    Ok(bytes) => hasher.update(&buffer[..bytes]),
137                    Err(why) => return Err(ChecksumError::FileRead(why)),
138                }
139            }
140
141            let hash = &*hasher.finalize();
142
143            if &*expected == hash {
144                Ok(())
145            } else {
146                Err(ChecksumError::Mismatch)
147            }
148        }
149    }
150}