//! Hash verification for WasmBox tool binaries. use sha2::{Digest, Sha256}; use subtle::ConstantTimeEq; use thiserror::Error; #[derive(Debug, Error)] pub enum VerifyError { #[error("hash mismatch: expected {expected}, got {actual}")] HashMismatch { expected: String, actual: String }, #[error("invalid hash format: {0}")] InvalidFormat(String), #[error("io error: {0}")] Io(#[from] std::io::Error), } /// Compute the SHA-256 hash of bytes, returning a `sha256:hex` string. pub fn hash_bytes(data: &[u8]) -> String { let mut hasher = Sha256::new(); hasher.update(data); let result = hasher.finalize(); format!("sha256:{}", hex::encode(result)) } /// Compute the SHA-256 hash of a file. pub fn hash_file(path: &std::path::Path) -> Result { let data = std::fs::read(path)?; Ok(hash_bytes(&data)) } /// Verify that a binary's hash matches the expected hash (constant-time comparison). pub fn verify_hash(data: &[u8], expected: &str) -> Result<(), VerifyError> { let expected_hex = expected .strip_prefix("sha256:") .ok_or_else(|| VerifyError::InvalidFormat("hash must start with 'sha256:'".into()))?; let expected_bytes = hex::decode(expected_hex).map_err(|e| VerifyError::InvalidFormat(e.to_string()))?; let mut hasher = Sha256::new(); hasher.update(data); let actual = hasher.finalize(); if actual.as_slice().ct_eq(&expected_bytes).into() { Ok(()) } else { Err(VerifyError::HashMismatch { expected: expected.to_string(), actual: format!("sha256:{}", hex::encode(actual)), }) } } /// Verify that a file's hash matches the expected hash. pub fn verify_file(path: &std::path::Path, expected: &str) -> Result<(), VerifyError> { let data = std::fs::read(path)?; verify_hash(&data, expected) } #[cfg(test)] mod tests { use super::*; #[test] fn hash_and_verify_roundtrip() { let data = b"hello wasmbox"; let hash = hash_bytes(data); assert!(hash.starts_with("sha256:")); verify_hash(data, &hash).unwrap(); } #[test] fn verify_mismatch() { let data = b"hello wasmbox"; let bad_hash = "sha256:0000000000000000000000000000000000000000000000000000000000000000"; assert!(verify_hash(data, bad_hash).is_err()); } #[test] fn verify_invalid_format() { assert!(verify_hash(b"data", "md5:abc").is_err()); } }