/
githubmirror
/
coreos-installer
Обзор
Документация
Войти
/
githubmirror
/
coreos-installer
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/io/hash.rs
257 строк
8 KB
Yasmin Valim
hash: remove deprecated funcion and add updated one
13 фев 2025, 21:43
Не верифицирован
13 фев 2025, 21:43
5965de1
Код
Авторство
О чём код?
// Copyright 2019 CoreOS, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use anyhow::{bail, ensure, Context, Error, Result}; use openssl::hash::{Hasher, MessageDigest}; use openssl::sha; use serde::{Deserialize, Serialize}; use serde_with::{DeserializeFromStr, SerializeDisplay}; use std::fmt; use std::fs::OpenOptions; use std::io::{self, Read, Write}; use std::os::unix::io::AsRawFd; use std::path::Path; use std::str::FromStr; /// Ignition-style message digests #[derive(Clone, Debug, DeserializeFromStr, SerializeDisplay, PartialEq, Eq)] pub enum IgnitionHash { /// SHA-256 digest. Sha256(Vec<u8>), /// SHA-512 digest. Sha512(Vec<u8>), } /// Digest implementation. Helpfully, each digest in openssl::sha has a /// different type. enum IgnitionHasher { Sha256(sha::Sha256), Sha512(sha::Sha512), } impl FromStr for IgnitionHash { type Err = Error; /// Try to parse an hash-digest argument. /// /// This expects an input value following the `ignition.config.verification.hash` /// spec, i.e. `<type>-<value>` format. fn from_str(input: &str) -> Result<Self, Self::Err> { let parts: Vec<_> = input.splitn(2, '-').collect(); if parts.len() != 2 { bail!("failed to detect hash-type and digest in '{}'", input); } let (hash_kind, hex_digest) = (parts[0], parts[1]); let hash = match hash_kind { "sha256" => { let digest = hex::decode(hex_digest).context("decoding hex digest")?; ensure!( digest.len().saturating_mul(8) == 256, "wrong digest length ({})", digest.len().saturating_mul(8) ); IgnitionHash::Sha256(digest) } "sha512" => { let digest = hex::decode(hex_digest).context("decoding hex digest")?; ensure!( digest.len().saturating_mul(8) == 512, "wrong digest length ({})", digest.len().saturating_mul(8) ); IgnitionHash::Sha512(digest) } x => bail!("unknown hash type '{}'", x), }; Ok(hash) } } impl fmt::Display for IgnitionHash { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let (kind, value) = match self { Self::Sha256(v) => ("sha256", v), Self::Sha512(v) => ("sha512", v), }; write!(f, "{}-{}", kind, hex::encode(value)) } } impl IgnitionHash { /// Digest and validate input data. pub fn validate(&self, input: &mut impl Read) -> Result<()> { let (mut hasher, digest) = match self { IgnitionHash::Sha256(val) => (IgnitionHasher::Sha256(sha::Sha256::new()), val), IgnitionHash::Sha512(val) => (IgnitionHasher::Sha512(sha::Sha512::new()), val), }; let mut buf = [0u8; 128 * 1024]; loop { match input.read(&mut buf) { Ok(0) => break, Ok(n) => match hasher { IgnitionHasher::Sha256(ref mut h) => h.update(&buf[..n]), IgnitionHasher::Sha512(ref mut h) => h.update(&buf[..n]), }, Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue, Err(e) => return Err(e).context("reading input"), }; } let computed = match hasher { IgnitionHasher::Sha256(h) => h.finish().to_vec(), IgnitionHasher::Sha512(h) => h.finish().to_vec(), }; if &computed != digest { bail!( "hash mismatch, computed '{}' but expected '{}'", hex::encode(computed), hex::encode(digest), ); } Ok(()) } } #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Default)] pub struct Sha256Digest(pub [u8; 32]); impl TryFrom<Hasher> for Sha256Digest { type Error = Error; fn try_from(mut hasher: Hasher) -> std::result::Result<Self, Self::Error> { let digest = hasher.finish().context("finishing hash")?; Ok(Sha256Digest( digest.as_ref().try_into().context("converting to SHA256")?, )) } } impl Sha256Digest { /// Calculates the SHA256 of a file. pub fn from_path(path: &Path) -> Result<Self> { let mut f = OpenOptions::new() .read(true) .open(path) .with_context(|| format!("opening {path:?}"))?; Self::from_file(&mut f) } /// Calculates the SHA256 of an opened file. Note that the underlying file descriptor will have /// `posix_fadvise` called on it to optimize for sequential reading. pub fn from_file(f: &mut std::fs::File) -> Result<Self> { // tell kernel to optimize for sequential reading if unsafe { libc::posix_fadvise(f.as_raw_fd(), 0, 0, libc::POSIX_FADV_SEQUENTIAL) } < 0 { eprintln!( "posix_fadvise(SEQUENTIAL) failed (errno {}) -- ignoring...", nix::errno::Errno::last_raw() ); } Self::from_reader(f) } /// Calculates the SHA256 of a reader. pub fn from_reader(r: &mut impl Read) -> Result<Self> { let mut hasher = Hasher::new(MessageDigest::sha256()).context("creating SHA256 hasher")?; std::io::copy(r, &mut hasher)?; hasher.try_into() } pub fn to_hex_string(&self) -> Result<String> { let mut buf: Vec<u8> = Vec::with_capacity(64); for i in 0..32 { write!(buf, "{:02x}", self.0[i])?; } Ok(String::from_utf8(buf)?) } } pub struct WriteHasher<W: Write> { writer: W, hasher: Hasher, } impl<W: Write> WriteHasher<W> { pub fn new(writer: W, hasher: Hasher) -> Self { WriteHasher { writer, hasher } } pub fn new_sha256(writer: W) -> Result<Self> { let hasher = Hasher::new(MessageDigest::sha256()).context("creating SHA256 hasher")?; Ok(WriteHasher { writer, hasher }) } } impl<W: Write> Write for WriteHasher<W> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> { if buf.is_empty() { return Ok(0); } let n = self.writer.write(buf)?; self.hasher.write_all(&buf[..n])?; Ok(n) } fn flush(&mut self) -> io::Result<()> { self.writer.flush()?; self.hasher.flush()?; Ok(()) } } impl<W: Write> TryFrom<WriteHasher<W>> for Sha256Digest { type Error = Error; fn try_from(wrapper: WriteHasher<W>) -> std::result::Result<Self, Self::Error> { Sha256Digest::try_from(wrapper.hasher) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_ignition_hash_cli_parse() { let err_cases = vec!["", "foo-bar", "-bar", "sha512", "sha512-", "sha512-00"]; for arg in err_cases { IgnitionHash::from_str(arg).expect_err(&format!("input: {arg}")); } let null_digest = "sha512-cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"; IgnitionHash::from_str(null_digest).unwrap(); } #[test] fn test_ignition_hash_validate() { let input = vec![b'a', b'b', b'c']; let hash_args = [ (true, "sha256-ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"), (true, "sha512-ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"), (false, "sha256-aa7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"), (false, "sha512-cdaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f") ]; for (valid, hash_arg) in &hash_args { let hasher = IgnitionHash::from_str(hash_arg).unwrap(); let mut rd = std::io::Cursor::new(&input); assert!(hasher.validate(&mut rd).is_ok() == *valid); } } }