wasmsign2 0.2.7

An implementation of the WebAssembly modules signatures proposal
Documentation
use log::*;
use std::io::{prelude::*, BufReader, BufWriter};

use crate::error::*;
use crate::wasm_module::*;
use crate::ED25519_PK_ID;
use crate::SIGNATURE_VERSION;
use crate::SIGNATURE_WASM_MODULE_CONTENT_TYPE;

pub const SIGNATURE_SECTION_HEADER_NAME: &str = "signature";
pub const SIGNATURE_SECTION_DELIMITER_NAME: &str = "signature_delimiter";

pub const MAX_HASHES: usize = 64;
pub const MAX_SIGNATURES: usize = 256;

#[derive(PartialEq, Debug, Clone, Eq)]
pub struct SignatureForHashes {
    pub key_id: Option<Vec<u8>>,
    pub alg_id: u8,
    pub signature: Vec<u8>,
}

#[derive(PartialEq, Debug, Clone, Eq)]
pub struct SignedHashes {
    pub hashes: Vec<Vec<u8>>,
    pub signatures: Vec<SignatureForHashes>,
}

#[derive(PartialEq, Debug, Clone, Eq)]
pub struct SignatureData {
    pub specification_version: u8,
    pub content_type: u8,
    pub hash_function: u8,
    pub signed_hashes_set: Vec<SignedHashes>,
}

impl SignatureForHashes {
    pub fn serialize(&self) -> Result<Vec<u8>, WSError> {
        let mut writer = BufWriter::new(Vec::new());
        if let Some(key_id) = &self.key_id {
            varint::put_slice(&mut writer, key_id)?;
        } else {
            varint::put(&mut writer, 0)?;
        }
        writer.write_all(&[self.alg_id])?;
        varint::put_slice(&mut writer, &self.signature)?;
        Ok(writer.into_inner().unwrap())
    }

    pub fn deserialize(bin: impl AsRef<[u8]>) -> Result<Self, WSError> {
        let mut reader = BufReader::new(bin.as_ref());
        let key_id_bytes = varint::get_slice(&mut reader)?;
        let key_id = if key_id_bytes.is_empty() { None } else { Some(key_id_bytes) };
        let mut alg_id_buf = [0u8; 1];
        reader.read_exact(&mut alg_id_buf)?;
        let alg_id = alg_id_buf[0];
        if alg_id != ED25519_PK_ID {
            debug!("Unsupported algorithm: {:02x}", alg_id);
            return Err(WSError::ParseError);
        }
        let signature = varint::get_slice(&mut reader)?;
        Ok(Self { key_id, alg_id, signature })
    }
}

impl SignedHashes {
    pub fn serialize(&self) -> Result<Vec<u8>, WSError> {
        let mut writer = BufWriter::new(Vec::new());
        varint::put(&mut writer, self.hashes.len() as _)?;
        for hash in &self.hashes {
            writer.write_all(hash)?;
        }
        varint::put(&mut writer, self.signatures.len() as _)?;
        for signature in &self.signatures {
            varint::put_slice(&mut writer, &signature.serialize()?)?;
        }
        Ok(writer.into_inner().unwrap())
    }

    pub fn deserialize(bin: impl AsRef<[u8]>) -> Result<Self, WSError> {
        let mut reader = BufReader::new(bin.as_ref());
        let hashes_count = varint::get32(&mut reader)? as _;
        if hashes_count > MAX_HASHES {
            debug!("Too many hashes: {} (max: {})", hashes_count, MAX_HASHES);
            return Err(WSError::TooManyHashes(MAX_HASHES));
        }
        let mut hashes = Vec::with_capacity(hashes_count);
        for _ in 0..hashes_count {
            let mut hash = vec![0; 32];
            reader.read_exact(&mut hash)?;
            hashes.push(hash);
        }
        let signatures_count = varint::get32(&mut reader)? as _;
        if signatures_count > MAX_SIGNATURES {
            debug!(
                "Too many signatures: {} (max: {})",
                signatures_count, MAX_SIGNATURES
            );
            return Err(WSError::TooManySignatures(MAX_SIGNATURES));
        }
        let mut signatures = Vec::with_capacity(signatures_count);
        for _ in 0..signatures_count {
            let bin = varint::get_slice(&mut reader)?;
            if let Ok(signature) = SignatureForHashes::deserialize(bin) {
                signatures.push(signature);
            }
        }
        Ok(Self { hashes, signatures })
    }
}

impl SignatureData {
    pub fn serialize(&self) -> Result<Vec<u8>, WSError> {
        let mut writer = BufWriter::new(Vec::new());
        varint::put(&mut writer, self.specification_version as _)?;
        varint::put(&mut writer, self.content_type as _)?;
        varint::put(&mut writer, self.hash_function as _)?;
        varint::put(&mut writer, self.signed_hashes_set.len() as _)?;
        for signed_hashes in &self.signed_hashes_set {
            varint::put_slice(&mut writer, &signed_hashes.serialize()?)?;
        }
        Ok(writer.into_inner().unwrap())
    }

    pub fn deserialize(bin: impl AsRef<[u8]>) -> Result<Self, WSError> {
        let mut reader = BufReader::new(bin.as_ref());
        let specification_version = varint::get7(&mut reader)?;
        if specification_version != SIGNATURE_VERSION {
            debug!(
                "Unsupported specification version: {:02x}",
                specification_version
            );
            return Err(WSError::ParseError);
        }
        let content_type = varint::get7(&mut reader)?;
        if content_type != SIGNATURE_WASM_MODULE_CONTENT_TYPE {
            debug!("Unsupported content type: {:02x}", content_type);
            return Err(WSError::ParseError);
        }
        let hash_function = varint::get7(&mut reader)?;
        let signed_hashes_count = varint::get32(&mut reader)? as _;
        if signed_hashes_count > MAX_HASHES {
            debug!(
                "Too many hashes: {} (max: {})",
                signed_hashes_count, MAX_HASHES
            );
            return Err(WSError::TooManyHashes(MAX_HASHES));
        }
        let mut signed_hashes_set = Vec::with_capacity(signed_hashes_count);
        for _ in 0..signed_hashes_count {
            let bin = varint::get_slice(&mut reader)?;
            let signed_hashes = SignedHashes::deserialize(bin)?;
            signed_hashes_set.push(signed_hashes);
        }
        Ok(Self {
            specification_version,
            content_type,
            hash_function,
            signed_hashes_set,
        })
    }
}

pub fn new_delimiter_section() -> Result<Section, WSError> {
    let mut custom_payload = vec![0u8; 16];
    getrandom::fill(&mut custom_payload)
        .map_err(|_| WSError::InternalError("RNG error".to_string()))?;
    Ok(Section::Custom(CustomSection::new(
        SIGNATURE_SECTION_DELIMITER_NAME.to_string(),
        custom_payload,
    )))
}