1use anyhow::{bail, ensure, Context, Error, Result};
16use openssl::hash::{Hasher, MessageDigest};
17use openssl::sha;
18use serde::{Deserialize, Serialize};
19use serde_with::{DeserializeFromStr, SerializeDisplay};
20use std::fmt;
21use std::fs::OpenOptions;
22use std::io::{self, Read, Write};
23use std::os::unix::io::AsRawFd;
24use std::path::Path;
25use std::str::FromStr;
26
27#[derive(Clone, Debug, DeserializeFromStr, SerializeDisplay, PartialEq, Eq)]
29pub enum IgnitionHash {
30 Sha256(Vec<u8>),
32 Sha512(Vec<u8>),
34}
35
36enum IgnitionHasher {
39 Sha256(sha::Sha256),
40 Sha512(sha::Sha512),
41}
42
43impl FromStr for IgnitionHash {
44 type Err = Error;
45
46 fn from_str(input: &str) -> Result<Self, Self::Err> {
51 let parts: Vec<_> = input.splitn(2, '-').collect();
52 if parts.len() != 2 {
53 bail!("failed to detect hash-type and digest in '{}'", input);
54 }
55 let (hash_kind, hex_digest) = (parts[0], parts[1]);
56
57 let hash = match hash_kind {
58 "sha256" => {
59 let digest = hex::decode(hex_digest).context("decoding hex digest")?;
60 ensure!(
61 digest.len().saturating_mul(8) == 256,
62 "wrong digest length ({})",
63 digest.len().saturating_mul(8)
64 );
65 IgnitionHash::Sha256(digest)
66 }
67 "sha512" => {
68 let digest = hex::decode(hex_digest).context("decoding hex digest")?;
69 ensure!(
70 digest.len().saturating_mul(8) == 512,
71 "wrong digest length ({})",
72 digest.len().saturating_mul(8)
73 );
74 IgnitionHash::Sha512(digest)
75 }
76 x => bail!("unknown hash type '{}'", x),
77 };
78
79 Ok(hash)
80 }
81}
82
83impl fmt::Display for IgnitionHash {
84 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85 let (kind, value) = match self {
86 Self::Sha256(v) => ("sha256", v),
87 Self::Sha512(v) => ("sha512", v),
88 };
89 write!(f, "{}-{}", kind, hex::encode(value))
90 }
91}
92
93impl IgnitionHash {
94 pub fn validate(&self, input: &mut impl Read) -> Result<()> {
96 let (mut hasher, digest) = match self {
97 IgnitionHash::Sha256(val) => (IgnitionHasher::Sha256(sha::Sha256::new()), val),
98 IgnitionHash::Sha512(val) => (IgnitionHasher::Sha512(sha::Sha512::new()), val),
99 };
100 let mut buf = [0u8; 128 * 1024];
101 loop {
102 match input.read(&mut buf) {
103 Ok(0) => break,
104 Ok(n) => match hasher {
105 IgnitionHasher::Sha256(ref mut h) => h.update(&buf[..n]),
106 IgnitionHasher::Sha512(ref mut h) => h.update(&buf[..n]),
107 },
108 Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
109 Err(e) => return Err(e).context("reading input"),
110 };
111 }
112 let computed = match hasher {
113 IgnitionHasher::Sha256(h) => h.finish().to_vec(),
114 IgnitionHasher::Sha512(h) => h.finish().to_vec(),
115 };
116
117 if &computed != digest {
118 bail!(
119 "hash mismatch, computed '{}' but expected '{}'",
120 hex::encode(computed),
121 hex::encode(digest),
122 );
123 }
124
125 Ok(())
126 }
127}
128
129#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Default)]
130pub struct Sha256Digest(pub [u8; 32]);
131
132impl TryFrom<Hasher> for Sha256Digest {
133 type Error = Error;
134
135 fn try_from(mut hasher: Hasher) -> std::result::Result<Self, Self::Error> {
136 let digest = hasher.finish().context("finishing hash")?;
137 Ok(Sha256Digest(
138 digest.as_ref().try_into().context("converting to SHA256")?,
139 ))
140 }
141}
142
143impl Sha256Digest {
144 pub fn from_path(path: &Path) -> Result<Self> {
146 let mut f = OpenOptions::new()
147 .read(true)
148 .open(path)
149 .with_context(|| format!("opening {path:?}"))?;
150
151 Self::from_file(&mut f)
152 }
153
154 pub fn from_file(f: &mut std::fs::File) -> Result<Self> {
157 if unsafe { libc::posix_fadvise(f.as_raw_fd(), 0, 0, libc::POSIX_FADV_SEQUENTIAL) } < 0 {
159 eprintln!(
160 "posix_fadvise(SEQUENTIAL) failed (errno {}) -- ignoring...",
161 nix::errno::Errno::last_raw()
162 );
163 }
164
165 Self::from_reader(f)
166 }
167
168 pub fn from_reader(r: &mut impl Read) -> Result<Self> {
170 let mut hasher = Hasher::new(MessageDigest::sha256()).context("creating SHA256 hasher")?;
171 std::io::copy(r, &mut hasher)?;
172 hasher.try_into()
173 }
174
175 pub fn to_hex_string(&self) -> Result<String> {
176 let mut buf: Vec<u8> = Vec::with_capacity(64);
177 for i in 0..32 {
178 write!(buf, "{:02x}", self.0[i])?;
179 }
180 Ok(String::from_utf8(buf)?)
181 }
182}
183
184pub struct WriteHasher<W: Write> {
185 writer: W,
186 hasher: Hasher,
187}
188
189impl<W: Write> WriteHasher<W> {
190 pub fn new(writer: W, hasher: Hasher) -> Self {
191 WriteHasher { writer, hasher }
192 }
193
194 pub fn new_sha256(writer: W) -> Result<Self> {
195 let hasher = Hasher::new(MessageDigest::sha256()).context("creating SHA256 hasher")?;
196 Ok(WriteHasher { writer, hasher })
197 }
198}
199
200impl<W: Write> Write for WriteHasher<W> {
201 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
202 if buf.is_empty() {
203 return Ok(0);
204 }
205
206 let n = self.writer.write(buf)?;
207 self.hasher.write_all(&buf[..n])?;
208
209 Ok(n)
210 }
211
212 fn flush(&mut self) -> io::Result<()> {
213 self.writer.flush()?;
214 self.hasher.flush()?;
215 Ok(())
216 }
217}
218
219impl<W: Write> TryFrom<WriteHasher<W>> for Sha256Digest {
220 type Error = Error;
221
222 fn try_from(wrapper: WriteHasher<W>) -> std::result::Result<Self, Self::Error> {
223 Sha256Digest::try_from(wrapper.hasher)
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230
231 #[test]
232 fn test_ignition_hash_cli_parse() {
233 let err_cases = vec!["", "foo-bar", "-bar", "sha512", "sha512-", "sha512-00"];
234 for arg in err_cases {
235 IgnitionHash::from_str(arg).expect_err(&format!("input: {arg}"));
236 }
237
238 let null_digest = "sha512-cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e";
239 IgnitionHash::from_str(null_digest).unwrap();
240 }
241
242 #[test]
243 fn test_ignition_hash_validate() {
244 let input = vec![b'a', b'b', b'c'];
245 let hash_args = [
246 (true, "sha256-ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"),
247 (true, "sha512-ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"),
248 (false, "sha256-aa7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"),
249 (false, "sha512-cdaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f")
250 ];
251 for (valid, hash_arg) in &hash_args {
252 let hasher = IgnitionHash::from_str(hash_arg).unwrap();
253 let mut rd = std::io::Cursor::new(&input);
254 assert!(hasher.validate(&mut rd).is_ok() == *valid);
255 }
256 }
257}