use anyhow::{bail, Context, Result};
use byteorder::{LittleEndian, ReadBytesExt};
use rand_core::{OsRng, RngCore};
use sha2::{Digest, Sha256};
use std::{
io,
io::{Read, SeekFrom::Start, Write},
};
use std::io::Seek;
use uuid::Uuid;
pub const SHA256_SIZE: usize = 32;
pub const BLOCK_SIZE: usize = 4096;
pub type Sha256Digest = [u8; SHA256_SIZE];
pub type Salt = Sha256Digest;
#[derive(Debug, Clone)]
pub struct VerityHeader {
pub header: [u8; 8],
pub version: u32,
pub hash_type: u32,
pub uuid: [u8; 16],
pub algorithm: [u8; 32],
pub data_block_size: u32,
pub hash_block_size: u32,
pub data_blocks: u64,
pub salt_size: u16,
pub salt: [u8; 256],
}
impl VerityHeader {
pub const HEADER: &'static [u8; 6] = b"verity";
pub const ALGORITHM: &'static [u8; 6] = b"sha256";
pub const VERITY_VERSION: u32 = 1;
fn new(uuid: &[u8; 16], data_blocks: u64, salt_size: u16, salt: &Salt) -> VerityHeader {
let mut padded_header = [0u8; 8];
padded_header[..VerityHeader::HEADER.len()].copy_from_slice(VerityHeader::HEADER);
let mut padded_algorithm = [0u8; 32];
padded_algorithm[..VerityHeader::ALGORITHM.len()].copy_from_slice(VerityHeader::ALGORITHM);
let mut padded_salt = [0u8; 256];
padded_salt[..salt.len()].copy_from_slice(salt);
VerityHeader {
header: padded_header,
version: 1,
hash_type: 1,
uuid: *uuid,
algorithm: padded_algorithm,
data_block_size: BLOCK_SIZE as u32,
hash_block_size: BLOCK_SIZE as u32,
data_blocks,
salt_size,
salt: padded_salt,
}
}
pub fn from_bytes<T: Read>(src: &mut T) -> Result<VerityHeader> {
let mut header = [0u8; 8];
src.read_exact(&mut header)?;
let version = src.read_u32::<LittleEndian>()?;
let hash_type = src.read_u32::<LittleEndian>()?;
let mut uuid = [0u8; 16];
src.read_exact(&mut uuid)?;
let mut algorithm = [0u8; 32];
src.read_exact(&mut algorithm)?;
let data_block_size = src.read_u32::<LittleEndian>()?;
let hash_block_size = src.read_u32::<LittleEndian>()?;
let data_blocks = src.read_u64::<LittleEndian>()?;
let salt_size = src.read_u16::<LittleEndian>()?;
io::copy(&mut src.take(6), &mut io::sink())?; let mut salt = [0u8; 256];
src.read_exact(&mut salt)?;
Ok(VerityHeader {
header,
version,
hash_type,
uuid,
algorithm,
data_block_size,
hash_block_size,
data_blocks,
salt_size,
salt,
})
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut raw_sb: Vec<u8> = Vec::with_capacity(BLOCK_SIZE);
raw_sb.extend(self.header);
raw_sb.extend(self.version.to_ne_bytes());
raw_sb.extend(self.hash_type.to_ne_bytes());
raw_sb.extend(self.uuid);
raw_sb.extend(self.algorithm);
raw_sb.extend(self.data_block_size.to_ne_bytes());
raw_sb.extend(self.hash_block_size.to_ne_bytes());
raw_sb.extend(self.data_blocks.to_ne_bytes());
raw_sb.extend(self.salt_size.to_ne_bytes());
raw_sb.extend([0; 6]); raw_sb.extend(self.salt);
raw_sb.resize(BLOCK_SIZE, 0); raw_sb
}
pub fn check(&self) -> Result<()> {
if !self.header.starts_with(VerityHeader::HEADER) {
bail!("invalid verity header")
} else if self.version != VerityHeader::VERITY_VERSION {
bail!("unsupported verity version {}", self.version)
} else if !self.algorithm.starts_with(VerityHeader::ALGORITHM) {
bail!("unsupported verity algorithm")
} else {
Ok(())
}
}
}
pub fn append_dm_verity_block<I: Read + Write + Seek>(
mut fsimg: I,
fsimg_size: u64,
) -> Result<Sha256Digest> {
let (level_offsets, tree_size) =
calculate_hash_tree_level_offsets(fsimg_size as usize, BLOCK_SIZE, SHA256_SIZE);
let (salt, root_hash, hash_tree) =
generate_hash_tree(&mut fsimg, fsimg_size, &level_offsets, tree_size)?;
append_superblock_and_hashtree(fsimg, fsimg_size, &salt, &hash_tree)?;
Ok(root_hash)
}
fn generate_salt() -> Salt {
let mut salt: Salt = [0u8; SHA256_SIZE];
OsRng.fill_bytes(&mut salt);
salt
}
fn calculate_hash_tree_level_offsets(
image_size: usize,
block_size: usize,
digest_size: usize,
) -> (Vec<usize>, usize) {
let mut level_offsets: Vec<usize> = vec![];
let mut level_sizes: Vec<usize> = vec![];
let mut tree_size = 0;
let mut num_levels = 0;
let mut rem_size = image_size;
while rem_size > block_size {
let num_blocks = rem_size.div_ceil(block_size);
let level_size = round_up_to_multiple(num_blocks * digest_size, block_size);
level_sizes.push(level_size);
tree_size += level_size;
num_levels += 1;
rem_size = level_size;
}
for n in 0..num_levels {
let mut offset = 0;
#[allow(clippy::needless_range_loop)]
for m in (n + 1)..num_levels {
offset += level_sizes[m];
}
level_offsets.push(offset);
}
(level_offsets, tree_size)
}
fn generate_hash_tree<R: Read + Seek>(
mut fsimg: R,
image_size: u64,
level_offsets: &[usize],
tree_size: usize,
) -> Result<(Salt, Sha256Digest, Vec<u8>)> {
let mut hashes: Vec<[u8; SHA256_SIZE]> = vec![];
let mut level_num = 0;
let mut level_size = image_size;
let mut hash_tree = vec![0_u8; tree_size];
if image_size % BLOCK_SIZE as u64 != 0 {
bail!("failed to generate verity has tree: the image size {} is not a multiple of the block size {}",
image_size,
BLOCK_SIZE);
}
let salt = generate_salt();
loop {
hashes.clear();
let mut rem_size = level_size;
while rem_size > 0 {
let mut sha256 = Sha256::new();
sha256.update(salt);
if level_num == 0 {
let offset = level_size - rem_size;
let mut data = vec![0_u8; BLOCK_SIZE];
fsimg
.seek(Start(offset))
.context("failed to seek in fs-image")?;
fsimg
.read_exact(&mut data)
.context("failed to read from fs-image")?;
sha256.update(&data);
} else {
let offset = level_offsets[level_num - 1] + level_size as usize - rem_size as usize;
sha256.update(&hash_tree[offset..offset + BLOCK_SIZE]);
}
rem_size -= BLOCK_SIZE as u64;
hashes.push(sha256.finalize().into());
}
if hashes.len() == 1 {
break;
}
let mut level = hashes.iter().flat_map(|s| s.iter().copied()).collect();
pad_to_block_size(&mut level);
let offset = level_offsets[level_num];
hash_tree[offset..offset + level.len()].copy_from_slice(level.as_slice());
level_size = level.len() as u64;
level_num += 1;
}
let root_hash = hashes[0];
Ok((salt, root_hash, hash_tree))
}
fn append_superblock_and_hashtree<W: Write + Seek>(
mut fsimg: W,
fsimg_size: u64,
salt: &Salt,
hash_tree: &[u8],
) -> Result<()> {
let mut uuid = [0u8; 16];
uuid.copy_from_slice(
hex::decode(Uuid::new_v4().to_string().replace('-', ""))
.context("failed to create valid uuid")?
.as_slice(),
);
assert_eq!(fsimg_size % BLOCK_SIZE as u64, 0);
let data_blocks = fsimg_size / BLOCK_SIZE as u64;
let header = VerityHeader::new(&uuid, data_blocks, SHA256_SIZE as u16, salt).to_bytes();
fsimg
.write_all(&header)
.context("failed to write verity header")?;
fsimg
.write_all(hash_tree)
.context("failed to write verity hash tree")?;
Ok(())
}
fn pad_to_block_size(data: &mut Vec<u8>) {
let pad_size = round_up_to_multiple(data.len(), BLOCK_SIZE) - data.len();
data.append(&mut vec![0_u8; pad_size]);
}
fn round_up_to_multiple(number: usize, multiple: usize) -> usize {
number + ((multiple - (number % multiple)) % multiple)
}