pub mod elf;
pub mod mcuboot;
use crate::WSError;
use std::io::Write;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FormatType {
Wasm,
Elf,
Mcuboot,
}
impl FormatType {
pub fn content_type_id(&self) -> u8 {
match self {
FormatType::Wasm => 0x01,
FormatType::Elf => 0x02,
FormatType::Mcuboot => 0x03,
}
}
pub fn signature_domain(&self) -> &'static str {
match self {
FormatType::Wasm => "wasmsig",
FormatType::Elf => "elfsig",
FormatType::Mcuboot => "mcubootsig",
}
}
pub fn detect(data: &[u8]) -> Option<FormatType> {
if data.len() < 4 {
return None;
}
if data[0..4] == [0x00, 0x61, 0x73, 0x6d] {
return Some(FormatType::Wasm);
}
if data[0..4] == [0x7f, 0x45, 0x4c, 0x46] {
return Some(FormatType::Elf);
}
if data[0..4] == [0x3d, 0xb8, 0xf3, 0x96] {
return Some(FormatType::Mcuboot);
}
None
}
pub fn from_str(s: &str) -> Result<FormatType, WSError> {
match s.to_lowercase().as_str() {
"wasm" => Ok(FormatType::Wasm),
"elf" => Ok(FormatType::Elf),
"mcuboot" => Ok(FormatType::Mcuboot),
_ => Err(WSError::UsageError(
"Unknown format. Use: wasm, elf, or mcuboot",
)),
}
}
}
pub trait SignableArtifact: Sized {
fn format_type(&self) -> FormatType;
fn compute_hash(&self) -> Result<[u8; 32], WSError>;
fn attach_signature(&mut self, signature_data: &[u8]) -> Result<(), WSError>;
fn detach_signature(&self) -> Result<Option<Vec<u8>>, WSError>;
fn serialize(&self, writer: &mut dyn Write) -> Result<(), WSError>;
fn serialize_to_file(&self, path: &str) -> Result<(), WSError> {
let mut file = std::fs::File::create(path)?;
self.serialize(&mut file)
}
fn content_bytes(&self) -> &[u8];
}
pub fn validate_format_consistency(
declared: FormatType,
data: &[u8],
) -> Result<(), WSError> {
if let Some(detected) = FormatType::detect(data) {
if detected != declared {
return Err(WSError::InternalError(format!(
"Format mismatch: declared {:?} but file magic indicates {:?}. \
This may indicate a polyglot file attack (AS-17).",
declared, detected,
)));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_detection_wasm() {
let wasm_magic = [0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00];
assert_eq!(FormatType::detect(&wasm_magic), Some(FormatType::Wasm));
}
#[test]
fn test_format_detection_elf() {
let elf_magic = [0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00];
assert_eq!(FormatType::detect(&elf_magic), Some(FormatType::Elf));
}
#[test]
fn test_format_detection_mcuboot() {
let mcuboot_magic = [0x3d, 0xb8, 0xf3, 0x96];
assert_eq!(FormatType::detect(&mcuboot_magic), Some(FormatType::Mcuboot));
}
#[test]
fn test_format_detection_unknown() {
let unknown = [0x00, 0x00, 0x00, 0x00];
assert_eq!(FormatType::detect(&unknown), None);
}
#[test]
fn test_format_detection_too_short() {
let short = [0x7f, 0x45];
assert_eq!(FormatType::detect(&short), None);
}
#[test]
fn test_format_from_str() {
assert_eq!(FormatType::from_str("wasm").unwrap(), FormatType::Wasm);
assert_eq!(FormatType::from_str("elf").unwrap(), FormatType::Elf);
assert_eq!(FormatType::from_str("ELF").unwrap(), FormatType::Elf);
assert_eq!(FormatType::from_str("mcuboot").unwrap(), FormatType::Mcuboot);
assert!(FormatType::from_str("unknown").is_err());
}
#[test]
fn test_format_consistency_ok() {
let elf_data = [0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00];
assert!(validate_format_consistency(FormatType::Elf, &elf_data).is_ok());
}
#[test]
fn test_format_consistency_mismatch() {
let elf_data = [0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00];
assert!(validate_format_consistency(FormatType::Wasm, &elf_data).is_err());
}
#[test]
fn test_content_type_ids() {
assert_eq!(FormatType::Wasm.content_type_id(), 0x01);
assert_eq!(FormatType::Elf.content_type_id(), 0x02);
assert_eq!(FormatType::Mcuboot.content_type_id(), 0x03);
}
#[test]
fn test_domain_separation() {
assert_eq!(FormatType::Wasm.signature_domain(), "wasmsig");
assert_eq!(FormatType::Elf.signature_domain(), "elfsig");
assert_eq!(FormatType::Mcuboot.signature_domain(), "mcubootsig");
}
}
#[cfg(kani)]
mod proofs {
use super::*;
#[kani::proof]
fn proof_format_detection_mutual_exclusivity() {
let b0: u8 = kani::any();
let b1: u8 = kani::any();
let b2: u8 = kani::any();
let b3: u8 = kani::any();
let data = [b0, b1, b2, b3];
let mut count = 0u8;
if data == [0x00, 0x61, 0x73, 0x6d] {
count += 1; }
if data == [0x7f, 0x45, 0x4c, 0x46] {
count += 1; }
if data == [0x3d, 0xb8, 0xf3, 0x96] {
count += 1; }
assert!(count <= 1, "Multiple formats detected for same magic bytes");
}
#[kani::proof]
fn proof_consistency_validation_agrees_with_detection() {
let b0: u8 = kani::any();
let b1: u8 = kani::any();
let b2: u8 = kani::any();
let b3: u8 = kani::any();
let data = [b0, b1, b2, b3];
if let Some(detected) = FormatType::detect(&data) {
let redetected = FormatType::detect(&data);
assert!(
redetected == Some(detected),
"detect() is pure — second call must return the same value"
);
}
}
#[kani::proof]
fn proof_content_type_ids_unique() {
let wasm_id = FormatType::Wasm.content_type_id();
let elf_id = FormatType::Elf.content_type_id();
let mcuboot_id = FormatType::Mcuboot.content_type_id();
assert_ne!(wasm_id, elf_id);
assert_ne!(wasm_id, mcuboot_id);
assert_ne!(elf_id, mcuboot_id);
}
#[kani::proof]
fn proof_domain_separation_distinct() {
let wasm_domain = FormatType::Wasm.signature_domain();
let elf_domain = FormatType::Elf.signature_domain();
let mcuboot_domain = FormatType::Mcuboot.signature_domain();
assert_ne!(wasm_domain, elf_domain);
assert_ne!(wasm_domain, mcuboot_domain);
assert_ne!(elf_domain, mcuboot_domain);
}
}