use sha2::digest::{self, OutputSizeUser, FixedOutputReset, FixedOutput, Reset, Update, Digest, HashMarker};
use sha2::digest::{generic_array::typenum::Unsigned, crypto_common::BlockSizeUser};
use sha2::{Sha256, Sha512};
use std::{io::Write, mem};
use crate::config::*;
static ZEROES: [u8; 128] = [0u8; 128];
pub trait InnerHash: Digest + BlockSizeUser + Clone + Default {
const INNER_HASH_ALGORITHM: InnerHashAlgorithm;
fn update_padded(&mut self, data: &[u8], padded_size: usize) {
self.update(data);
self.update_zeroes(padded_size.checked_sub(data.len()).unwrap());
}
fn update_zeroes(&mut self, amount: usize) {
let (quotient, remainder) = (amount / ZEROES.len(), amount % ZEROES.len());
if remainder != 0 { self.update(&ZEROES[..remainder]); }
for _ in 0..quotient { self.update(&ZEROES); }
}
}
impl InnerHash for Sha256 {
const INNER_HASH_ALGORITHM: InnerHashAlgorithm = InnerHashAlgorithm::Sha256;
}
impl InnerHash for Sha512 {
const INNER_HASH_ALGORITHM: InnerHashAlgorithm = InnerHashAlgorithm::Sha512;
}
#[derive(Clone)]
struct FixedSizeBlock<D> where D: InnerHash {
inner: D,
remaining: usize,
}
impl<D> FixedSizeBlock<D> where D: InnerHash {
fn new<S: AsRef<[u8]> + Clone + Default>(config: &FsVerityConfig<D, S>) -> Self {
Self { inner: config.salted_digest(), remaining: config.block_size }
}
fn append(&mut self, data: &[u8]) {
self.inner.update(data);
self.remaining = self.remaining.checked_sub(data.len()).unwrap();
}
fn fill_to_end(&mut self) {
self.inner.update_zeroes(self.remaining);
self.remaining = 0;
}
fn overflowing_append<'a>(&mut self, data: &'a [u8]) -> &'a [u8] {
let (a, b) = data.split_at(self.remaining.min(data.len()));
self.append(a);
b
}
fn finalize_into(mut self, dest: &mut digest::Output<D>) {
self.fill_to_end();
self.inner.finalize_into(dest);
}
fn finalize_into_and_reset<S: AsRef<[u8]> + Clone + Default>(&mut self, dest: &mut digest::Output<D>, config: &FsVerityConfig<D, S>) {
self.fill_to_end();
let Self {inner, ..} = mem::replace(self, Self::new(config));
inner.finalize_into(dest);
}
}
#[derive(Clone)]
struct FsVerityConfig<D=Sha256, S=[u8; 0]> where D: InnerHash, S: AsRef<[u8]> + Clone + Default {
block_size: usize,
salt: S,
salted_digest: D,
}
impl<D, S> FsVerityConfig<D, S> where D: InnerHash, S: AsRef<[u8]> + Clone + Default {
fn new(block_size: usize, salt: S) -> Self {
assert!(salt.as_ref().len() <= MAX_SALT_SIZE);
assert!(block_size.is_power_of_two());
assert!(block_size >= D::OutputSize::USIZE * 2);
assert!(D::OutputSize::USIZE <= MAX_DIGEST_SIZE);
let mut salted_digest = <D as digest::Digest>::new();
for chunk in salt.as_ref().chunks(D::BlockSize::USIZE) {
salted_digest.update_padded(chunk, D::BlockSize::USIZE);
}
Self {
block_size,
salt,
salted_digest,
}
}
fn salted_digest(&self) -> D {
self.salted_digest.clone()
}
fn inner_hash_algorithm(&self) -> InnerHashAlgorithm {
D::INNER_HASH_ALGORITHM
}
}
#[derive(Clone)]
pub struct FsVerityDigest<D=Sha256, S=[u8; 0]> where D: InnerHash, S: AsRef<[u8]> + Clone + Default {
config: FsVerityConfig<D, S>,
levels: Vec<FixedSizeBlock<D>>,
}
impl<D> FsVerityDigest<D> where D: InnerHash {
pub fn new() -> Self {
Self::new_with_salt(Default::default())
}
}
impl<D, S> FsVerityDigest<D, S> where D: InnerHash, S: AsRef<[u8]> + Clone + Default {
pub fn new_with_salt(salt: S) -> Self {
Self::new_with_salt_and_block_size(salt, DEFAULT_BLOCK_SIZE)
}
pub fn new_with_salt_and_block_size(salt: S, block_size: usize) -> Self {
let config = FsVerityConfig::new(block_size, salt);
Self {
config,
levels: vec![],
}
}
pub fn inner_hash_algorithm(&self) -> InnerHashAlgorithm {
self.config.inner_hash_algorithm()
}
}
impl<D, S> Default for FsVerityDigest<D, S> where D: InnerHash, S: AsRef<[u8]> + Clone + Default {
fn default() -> Self { Self::new_with_salt(Default::default()) }
}
impl<D, S> Update for FsVerityDigest<D, S> where D: InnerHash, S: AsRef<[u8]> + Clone + Default {
fn update(&mut self, data: &[u8]) {
for chunk in data.as_ref().chunks(self.config.block_size) {
let mut keep_space_for_one_digest = false;
let mut last_digest: digest::Output<Self>;
let mut overflow = chunk; for level in self.levels.iter_mut() {
overflow = level.overflowing_append(overflow);
if keep_space_for_one_digest {
if level.remaining >= D::OutputSize::USIZE {
assert!(overflow.len() == 0); break;
}
} else {
if overflow.len() == 0 { break; }
}
let mut tmp: digest::Output<Self> = Default::default();
level.finalize_into_and_reset(&mut tmp, &self.config);
level.append(overflow);
last_digest = tmp;
overflow = &last_digest;
keep_space_for_one_digest = true; }
if overflow.len() != 0 {
assert!(self.levels.len() < MAX_LEVELS);
let mut level = FixedSizeBlock::new(&self.config);
level.append(overflow);
self.levels.push(level);
}
}
}
}
impl<D, S> OutputSizeUser for FsVerityDigest<D, S> where D: InnerHash, S: AsRef<[u8]> + Clone + Default {
type OutputSize = D::OutputSize;
}
impl<D, S> FixedOutput for FsVerityDigest<D, S> where D: InnerHash, S: AsRef<[u8]> + Clone + Default {
fn finalize_into(mut self, out: &mut digest::Output<Self>) {
let compression_factor = self.config.block_size / D::OutputSize::USIZE;
let mut total_size: usize = 0;
let mut scale: usize = 1;
let mut last_digest: digest::Output<Self> = Default::default();
let mut overflow: &[u8] = &[];
for mut level in self.levels.drain(..) {
total_size += scale * (self.config.block_size - level.remaining);
level.append(overflow);
level.finalize_into(&mut last_digest);
overflow = &last_digest;
scale *= compression_factor;
}
let mut descriptor: D = self.config.salted_digest();
descriptor.update(&[1]);
descriptor.update(&[self.config.inner_hash_algorithm() as u8]);
descriptor.update(&[self.config.block_size.trailing_zeros() as u8]);
descriptor.update(&[self.config.salt.as_ref().len() as u8]);
descriptor.update(&[0; 4]);
descriptor.update(&(total_size as u64).to_le_bytes());
descriptor.update_padded(&last_digest, MAX_DIGEST_SIZE);
descriptor.update_padded(self.config.salt.as_ref(), MAX_SALT_SIZE);
descriptor.update_zeroes(144);
descriptor.finalize_into(out);
}
}
impl<D, S> BlockSizeUser for FsVerityDigest<D, S> where D: InnerHash, S: AsRef<[u8]> + Clone + Default {
type BlockSize = D::BlockSize;
}
impl<D, S> Reset for FsVerityDigest<D, S> where D: InnerHash, S: AsRef<[u8]> + Clone + Default {
fn reset(&mut self) {
*self = Self::new_with_salt_and_block_size(self.config.salt.clone(), self.config.block_size);
}
}
impl<D, S> FixedOutputReset for FsVerityDigest<D, S> where D: InnerHash, S: AsRef<[u8]> + Clone + Default {
fn finalize_into_reset(&mut self, out: &mut digest::Output<Self>) {
let new = Self::new_with_salt_and_block_size(self.config.salt.clone(), self.config.block_size);
let old = mem::replace(self, new);
FixedOutput::finalize_into(old, out);
}
}
impl<D, S> Write for FsVerityDigest<D, S> where D: InnerHash, S: AsRef<[u8]> + Clone + Default {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
Update::update(self, buf);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
impl<D, S> HashMarker for FsVerityDigest<D, S> where D: InnerHash, S: AsRef<[u8]> + Clone + Default {
}
pub type FsVeritySha256<S> = FsVerityDigest<Sha256, S>;
pub type FsVeritySha512<S> = FsVerityDigest<Sha512, S>;
pub trait DynFsVerityDigest: sha2::digest::DynDigest + Write {
fn inner_hash_algorithm(&self) -> InnerHashAlgorithm;
}
impl<D: InnerHash + 'static, S: AsRef<[u8]> + Clone + Default + 'static> DynFsVerityDigest for FsVerityDigest<D, S> {
fn inner_hash_algorithm(&self) -> InnerHashAlgorithm {
self.config.inner_hash_algorithm()
}
}
pub fn new_dyn(inner_hash: InnerHashAlgorithm) -> Box<dyn DynFsVerityDigest> {
match inner_hash {
InnerHashAlgorithm::Sha256 => { Box::new(FsVeritySha256::new()) }
InnerHashAlgorithm::Sha512 => { Box::new(FsVeritySha512::new()) }
}
}
pub fn new_dyn_with_salt<S: AsRef<[u8]> + Clone + Default + 'static>(inner_hash: InnerHashAlgorithm, salt: S) -> Box<dyn DynFsVerityDigest> {
match inner_hash {
InnerHashAlgorithm::Sha256 => { Box::new(FsVeritySha256::new_with_salt(salt)) }
InnerHashAlgorithm::Sha512 => { Box::new(FsVeritySha512::new_with_salt(salt)) }
}
}
pub fn new_dyn_with_salt_and_block_size<S: AsRef<[u8]> + Clone + Default + 'static>(inner_hash: InnerHashAlgorithm, salt: S, block_size: usize) -> Box<dyn DynFsVerityDigest> {
match inner_hash {
InnerHashAlgorithm::Sha256 => { Box::new(FsVeritySha256::new_with_salt_and_block_size(salt, block_size)) }
InnerHashAlgorithm::Sha512 => { Box::new(FsVeritySha512::new_with_salt_and_block_size(salt, block_size)) }
}
}
#[cfg(test)]
mod tests {
use std::io::BufReader;
use std::fs::File;
use crate::InnerHashAlgorithm;
use super::new_dyn;
#[test]
fn test_testfiles() {
let testfiles = "
sha256:3d248ca542a24fc62d1c43b916eae5016878e2533c88238480b26128a1f1af95 testfiles/empty
sha256:f5c2b9ded1595acfe8a996795264d488dd6140531f6a01f8f8086a83fd835935 testfiles/hashblock_0_0
sha256:5c00a54bd1d8341d7bbad060ff1b8e88ed2646d7bb38db6e752cd1cff66c0a78 testfiles/hashblock_0_-1
sha256:a7abb76568871169a79104d00679fae6521dfdb2a2648e380c02b10e96e217ff testfiles/hashblock_0_1
sha256:c4b519068d8c8c68fd5e362fc3526c5b11e15f8eb72d4678017906f9e7f2d137 testfiles/hashblock_-1_0
sha256:09510d2dbb55fa16f2768165c42d19c4da43301dfaa05705b2ecb4aaa4a5686a testfiles/hashblock_1_0
sha256:7aa0bb537c623562f898386ac88acd319267e4ab3200f3fd1cf648cfdb4a0379 testfiles/hashblock_-1_-1
sha256:f804e9777f91d3697ca015303c23251ad3d80205184cfa3d1066ab28cb906330 testfiles/hashblock_-1_1
sha256:26159b4fc68c63881c25c33b23f2583ffaa64fee411af33c3b03238eea56755c testfiles/hashblock_1_-1
sha256:57bed0934bf3ab4610d54938f03cff27bd0d9d76c9a77e283f9fb2b7e29c5ab8 testfiles/hashblock_1_1
sha256:3fd7a78101899a79cd337b1b4e5414be8bcb376b133370156ef6e65026d930ed testfiles/oneblock
sha256:c0b9455d545b6b1ee5e7b227bd1ed463aaa530a4840dcd93465163a2b3aff0da testfiles/oneblockplusonebyte
sha256:9845e616f7d2f7a1cd6742f0546a36d2e74d4eb8ae7d9bdc0b0df982c27861b7 testfiles/onebyte
".trim().lines().map(|l| {
let l = l.trim();
let (digest, path) = l.split_once(" ").unwrap();
let (digest_type, digest) = digest.split_once(":").unwrap();
let digest_type = digest_type.parse::<super::InnerHashAlgorithm>().unwrap();
let digest = hex::decode(digest).unwrap();
(digest_type, digest, path)
}).collect::<Vec<_>>();
for (digest_type, digest, path) in testfiles {
assert!(digest_type == InnerHashAlgorithm::Sha256);
let mut f = BufReader::new(File::open(path).unwrap());
let mut tmp = new_dyn(digest_type);
std::io::copy(&mut f, &mut tmp).unwrap();
let out = tmp.finalize();
let tmp = hex::encode(&digest);
let tmp2 = hex::encode(&out);
assert!(out.as_ref() == &digest, "expected: {} found: {} for file: {}", tmp, tmp2, path);
}
}
}