use sha2::{Digest, Sha256};
use crate::error::{Refusal, Result};
use crate::manifest::{Manifest, DIGEST_LEN};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Verified {
size: u32,
digest: [u8; DIGEST_LEN],
}
impl Verified {
pub fn size(&self) -> u32 {
self.size
}
pub fn digest(&self) -> [u8; DIGEST_LEN] {
self.digest
}
}
pub fn image_digest(image: &[u8]) -> [u8; DIGEST_LEN] {
Sha256::digest(image).into()
}
pub struct ImageVerifier {
hasher: Sha256,
expected_digest: [u8; DIGEST_LEN],
expected_size: u32,
seen: u64,
}
impl ImageVerifier {
pub fn new(manifest: &Manifest) -> Self {
Self {
hasher: Sha256::new(),
expected_digest: manifest.digest,
expected_size: manifest.size,
seen: 0,
}
}
pub fn update(&mut self, chunk: &[u8]) -> Result<()> {
self.seen += chunk.len() as u64;
if self.seen > u64::from(self.expected_size) {
return Err(Refusal::Size);
}
self.hasher.update(chunk);
Ok(())
}
pub fn finish(self) -> Result<Verified> {
if self.seen != u64::from(self.expected_size) {
return Err(Refusal::Size);
}
let digest: [u8; DIGEST_LEN] = self.hasher.finalize().into();
if digest != self.expected_digest {
return Err(Refusal::Digest);
}
Ok(Verified {
size: self.expected_size,
digest,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::manifest::{PayloadFormat, STRUCTURE_VERSION};
fn manifest_for(image: &[u8]) -> Manifest {
Manifest {
structure_version: STRUCTURE_VERSION,
sequence: 1,
vendor_id: [0; 16],
class_id: [0; 16],
format: PayloadFormat::Raw,
storage: 0,
digest: Sha256::digest(image).into(),
size: image.len() as u32,
expires: 0,
}
}
fn run(manifest: &Manifest, image: &[u8]) -> Result<Verified> {
let mut verifier = ImageVerifier::new(manifest);
for chunk in image.chunks(7) {
verifier.update(chunk)?;
}
verifier.finish()
}
#[test]
fn the_described_image_verifies() {
let image = b"the firmware, arriving in pieces";
let manifest = manifest_for(image);
let verified = run(&manifest, image).expect("verify");
assert_eq!(verified.size(), image.len() as u32);
assert_eq!(verified.digest(), manifest.digest);
}
#[test]
fn an_empty_image_verifies_when_that_is_what_was_described() {
let manifest = manifest_for(b"");
assert!(run(&manifest, b"").is_ok());
}
#[test]
fn a_tampered_image_is_refused() {
let image = b"the firmware, arriving in pieces";
let manifest = manifest_for(image);
let mut altered = *image;
altered[3] ^= 0x01;
assert_eq!(run(&manifest, &altered), Err(Refusal::Digest));
}
#[test]
fn a_short_image_is_refused() {
let image = b"the firmware, arriving in pieces";
let manifest = manifest_for(image);
assert_eq!(
run(&manifest, &image[..image.len() - 1]),
Err(Refusal::Size)
);
}
#[test]
fn an_oversized_image_is_stopped_while_it_arrives() {
let image = b"short";
let manifest = manifest_for(image);
let mut verifier = ImageVerifier::new(&manifest);
assert!(verifier.update(image).is_ok());
assert_eq!(verifier.update(b"more"), Err(Refusal::Size));
}
#[test]
fn reordered_chunks_are_refused() {
let image = b"order matters to a hash";
let manifest = manifest_for(image);
let mut verifier = ImageVerifier::new(&manifest);
verifier.update(&image[10..]).expect("update");
verifier.update(&image[..10]).expect("update");
assert_eq!(verifier.finish(), Err(Refusal::Digest));
}
}