use crate::error::WSError;
use crate::platform::{KeyHandle, SecureKeyProvider};
use crate::provisioning::OfflineVerifier;
use crate::signature::*;
use crate::wasm_module::*;
use crate::*;
use log::*;
use std::io::Read;
pub fn sign_with_certificate(
provider: &dyn SecureKeyProvider,
key_handle: KeyHandle,
mut module: Module,
certificate_chain: &[Vec<u8>],
) -> Result<Module, WSError> {
if certificate_chain.is_empty() {
return Err(WSError::InvalidArgument);
}
let mut out_sections = vec![Section::Custom(CustomSection::default())];
let mut hasher = Hash::new();
let mut previous_signature_data = None;
for section in module.sections.into_iter() {
if section.is_signature_header() {
if let Section::Custom(custom_section) = §ion {
previous_signature_data = Some(custom_section.signature_data()?);
}
continue; }
section.serialize(&mut hasher)?;
out_sections.push(section);
}
let h = hasher.finalize().to_vec();
let mut msg: Vec<u8> = vec![];
msg.extend_from_slice(SIGNATURE_WASM_DOMAIN.as_bytes());
msg.extend_from_slice(&[
SIGNATURE_VERSION,
SIGNATURE_WASM_MODULE_CONTENT_TYPE,
SIGNATURE_HASH_FUNCTION,
]);
msg.extend_from_slice(&h);
let signature = provider.sign(key_handle, &msg)?;
let new_signature = SignatureForHashes {
key_id: None, alg_id: ED25519_PK_ID,
signature,
certificate_chain: Some(certificate_chain.to_vec()),
};
let signed_hashes_set = if let Some(prev_sig_data) = previous_signature_data {
let mut updated_set = prev_sig_data.signed_hashes_set;
let mut found = false;
for signed_hashes in &mut updated_set {
if signed_hashes.hashes.contains(&h) {
signed_hashes.signatures.push(new_signature.clone());
found = true;
break;
}
}
if !found {
updated_set.push(SignedHashes {
hashes: vec![h],
signatures: vec![new_signature],
});
}
updated_set
} else {
vec![SignedHashes {
hashes: vec![h],
signatures: vec![new_signature],
}]
};
let signature_data = SignatureData {
specification_version: SIGNATURE_VERSION,
content_type: SIGNATURE_WASM_MODULE_CONTENT_TYPE,
hash_function: SIGNATURE_HASH_FUNCTION,
signed_hashes_set,
};
out_sections[0] = Section::Custom(CustomSection::new(
SIGNATURE_SECTION_HEADER_NAME.to_string(),
signature_data.serialize()?,
));
module.sections = out_sections;
Ok(module)
}
pub fn verify_with_certificate(
reader: &mut impl Read,
verifier: &OfflineVerifier,
) -> Result<(), WSError> {
let stream = Module::init_from_reader(reader)?;
let mut sections = Module::iterate(stream)?;
let signature_header_section = sections.next().ok_or(WSError::ParseError)??;
let signature_header = match signature_header_section {
Section::Custom(custom_section) if custom_section.is_signature_header() => custom_section,
_ => {
debug!("This module is not signed");
return Err(WSError::NoSignatures);
}
};
let signature_data = signature_header.signature_data()?;
if signature_data.signed_hashes_set.is_empty() {
return Err(WSError::NoSignatures);
}
let mut hasher = Hash::new();
for section in sections {
let section = section?;
if section.is_signature_header() || section.is_signature_delimiter() {
continue;
}
section.serialize(&mut hasher)?;
}
let computed_hash = hasher.finalize().to_vec();
for signed_hashes in &signature_data.signed_hashes_set {
for hash in &signed_hashes.hashes {
if hash != &computed_hash {
continue; }
if let Some(sig_for_hash) = signed_hashes.signatures.first() {
let cert_chain = sig_for_hash.certificate_chain.as_ref().ok_or_else(|| {
WSError::VerificationError(
"No certificate chain found in signature".to_string(),
)
})?;
if cert_chain.is_empty() {
return Err(WSError::VerificationError(
"Empty certificate chain".to_string(),
));
}
verifier.verify_certificate_chain(cert_chain, None)?;
let device_cert_der = &cert_chain[0];
let public_key = extract_public_key_from_certificate(device_cert_der)?;
let mut msg: Vec<u8> = vec![];
msg.extend_from_slice(SIGNATURE_WASM_DOMAIN.as_bytes());
msg.extend_from_slice(&[
signature_data.specification_version,
signature_data.content_type,
signature_data.hash_function,
]);
msg.extend_from_slice(hash);
let signature = ed25519_compact::Signature::from_slice(&sig_for_hash.signature)
.map_err(|_| WSError::ParseError)?;
public_key.pk.verify(&msg, &signature)?;
return Ok(());
}
}
}
Err(WSError::VerificationFailed)
}
#[derive(Debug, Clone)]
pub struct SignatureInfo {
pub index: usize,
pub has_certificate_chain: bool,
pub certificate_count: usize,
pub subject_dn: Option<String>,
pub key_id: Option<Vec<u8>>,
}
#[derive(Debug, Clone)]
pub struct VerificationResult {
pub info: SignatureInfo,
pub verified: bool,
pub error: Option<String>,
}
pub fn verify_all_certificates(
reader: &mut impl Read,
verifiers: &[&OfflineVerifier],
) -> Result<Vec<VerificationResult>, WSError> {
let stream = Module::init_from_reader(reader)?;
let mut sections = Module::iterate(stream)?;
let signature_header_section = sections.next().ok_or(WSError::ParseError)??;
let signature_header = match signature_header_section {
Section::Custom(custom_section) if custom_section.is_signature_header() => custom_section,
_ => {
debug!("This module is not signed");
return Err(WSError::NoSignatures);
}
};
let signature_data = signature_header.signature_data()?;
if signature_data.signed_hashes_set.is_empty() {
return Err(WSError::NoSignatures);
}
let mut hasher = Hash::new();
for section in sections {
let section = section?;
if section.is_signature_header() || section.is_signature_delimiter() {
continue;
}
section.serialize(&mut hasher)?;
}
let computed_hash = hasher.finalize().to_vec();
let mut results = Vec::new();
let mut sig_index = 0;
for signed_hashes in &signature_data.signed_hashes_set {
for hash in &signed_hashes.hashes {
if hash != &computed_hash {
continue; }
for sig_for_hash in &signed_hashes.signatures {
let info = SignatureInfo {
index: sig_index,
has_certificate_chain: sig_for_hash.certificate_chain.is_some(),
certificate_count: sig_for_hash
.certificate_chain
.as_ref()
.map(|c| c.len())
.unwrap_or(0),
subject_dn: sig_for_hash
.certificate_chain
.as_ref()
.and_then(|chain| chain.first())
.and_then(|cert| extract_subject_dn(cert).ok()),
key_id: sig_for_hash.key_id.clone(),
};
let mut verified = false;
let mut last_error = None;
if let Some(cert_chain) = &sig_for_hash.certificate_chain {
if cert_chain.is_empty() {
results.push(VerificationResult {
info,
verified: false,
error: Some("Empty certificate chain".to_string()),
});
sig_index += 1;
continue;
}
for verifier in verifiers {
let chain_result = verifier.verify_certificate_chain(cert_chain, None);
if let Err(e) = chain_result {
last_error =
Some(format!("Certificate chain verification failed: {:?}", e));
continue;
}
let public_key = match extract_public_key_from_certificate(&cert_chain[0]) {
Ok(pk) => pk,
Err(e) => {
last_error = Some(format!("Failed to extract public key: {:?}", e));
continue;
}
};
let mut msg: Vec<u8> = vec![];
msg.extend_from_slice(SIGNATURE_WASM_DOMAIN.as_bytes());
msg.extend_from_slice(&[
signature_data.specification_version,
signature_data.content_type,
signature_data.hash_function,
]);
msg.extend_from_slice(hash);
let signature =
match ed25519_compact::Signature::from_slice(&sig_for_hash.signature) {
Ok(sig) => sig,
Err(_) => {
last_error = Some("Invalid signature format".to_string());
continue;
}
};
if public_key.pk.verify(&msg, &signature).is_ok() {
verified = true;
break; } else {
last_error = Some("Signature verification failed".to_string());
}
}
} else {
last_error =
Some("No certificate chain (use regular verification)".to_string());
}
results.push(VerificationResult {
info,
verified,
error: if verified { None } else { last_error },
});
sig_index += 1;
}
}
}
Ok(results)
}
pub fn inspect_signatures(reader: &mut impl Read) -> Result<Vec<SignatureInfo>, WSError> {
let stream = Module::init_from_reader(reader)?;
let mut sections = Module::iterate(stream)?;
let signature_header_section = sections.next().ok_or(WSError::ParseError)??;
let signature_header = match signature_header_section {
Section::Custom(custom_section) if custom_section.is_signature_header() => custom_section,
_ => {
debug!("This module is not signed");
return Err(WSError::NoSignatures);
}
};
let signature_data = signature_header.signature_data()?;
let mut signatures = Vec::new();
let mut sig_index = 0;
for signed_hashes in &signature_data.signed_hashes_set {
for sig_for_hash in &signed_hashes.signatures {
signatures.push(SignatureInfo {
index: sig_index,
has_certificate_chain: sig_for_hash.certificate_chain.is_some(),
certificate_count: sig_for_hash
.certificate_chain
.as_ref()
.map(|c| c.len())
.unwrap_or(0),
subject_dn: sig_for_hash
.certificate_chain
.as_ref()
.and_then(|chain| chain.first())
.and_then(|cert| extract_subject_dn(cert).ok()),
key_id: sig_for_hash.key_id.clone(),
});
sig_index += 1;
}
}
Ok(signatures)
}
fn extract_subject_dn(cert_der: &[u8]) -> Result<String, WSError> {
use x509_parser::prelude::*;
let (_, cert) = X509Certificate::from_der(cert_der)
.map_err(|e| WSError::X509Error(format!("Failed to parse certificate: {:?}", e)))?;
Ok(cert.subject().to_string())
}
fn extract_public_key_from_certificate(cert_der: &[u8]) -> Result<PublicKey, WSError> {
use x509_parser::prelude::*;
let (_, cert) = X509Certificate::from_der(cert_der)
.map_err(|e| WSError::X509Error(format!("Failed to parse certificate: {:?}", e)))?;
let public_key_info = cert.public_key();
let public_key_bytes = &public_key_info.subject_public_key.data;
if public_key_bytes.len() != 32 {
return Err(WSError::X509Error(format!(
"Invalid public key length: {} (expected 32 for Ed25519)",
public_key_bytes.len()
)));
}
let pk = ed25519_compact::PublicKey::from_slice(public_key_bytes)
.map_err(WSError::CryptoError)?;
Ok(PublicKey { pk, key_id: None })
}
#[cfg(test)]
mod tests {
use super::*;
use crate::platform::software::SoftwareProvider;
use crate::provisioning::OfflineVerifierBuilder;
use crate::provisioning::ca::{CAConfig, PrivateCA};
use crate::provisioning::{CertificateConfig, DeviceIdentity, ProvisioningResult, ProvisioningSession};
#[test]
fn test_sign_and_verify_with_certificate() {
let root_config = CAConfig::new("Test Corp", "Test Root CA");
let root_ca = PrivateCA::create_root(root_config).unwrap();
let provider = SoftwareProvider::new();
let key_handle = provider.generate_key().unwrap();
let device_id = DeviceIdentity::new("device-test");
let cert_config = CertificateConfig::new("device-test");
let device_cert = provider
.with_keypair(key_handle, |device_keypair| {
root_ca
.sign_device_certificate_with_keypair(device_keypair, &device_id, &cert_config)
})
.unwrap()
.unwrap();
let prov_result = ProvisioningResult {
key_handle,
certificate: device_cert,
certificate_chain: vec![root_ca.certificate().to_vec()],
device_id: device_id.id().to_string(),
serial_number: vec![1, 2, 3, 4],
};
let wasm_bytes: Vec<u8> = vec![
0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, ];
let module = Module::deserialize(&mut wasm_bytes.as_slice()).unwrap();
let cert_chain = prov_result.full_chain();
let signed_module =
sign_with_certificate(&provider, prov_result.key_handle, module, &cert_chain).unwrap();
let verifier = OfflineVerifierBuilder::new()
.with_root(root_ca.certificate())
.unwrap()
.build()
.unwrap();
let mut module_bytes = Vec::new();
signed_module.serialize(&mut module_bytes).unwrap();
let result = verify_with_certificate(&mut module_bytes.as_slice(), &verifier);
assert!(result.is_ok(), "Verification failed: {:?}", result.err());
println!("✓ WASM signed and verified with certificates");
}
#[test]
fn test_multi_signature_owner_plus_integrator() {
let owner_ca =
PrivateCA::create_root(CAConfig::new("Owner Corp", "Owner Root CA")).unwrap();
let owner_provider = SoftwareProvider::new();
let owner_key = owner_provider.generate_key().unwrap();
let owner_id = DeviceIdentity::new("owner-device");
let owner_config = CertificateConfig::new("owner-device");
let owner_cert = owner_provider
.with_keypair(owner_key, |owner_keypair| {
owner_ca
.sign_device_certificate_with_keypair(owner_keypair, &owner_id, &owner_config)
})
.unwrap()
.unwrap();
let wasm_bytes: Vec<u8> = vec![
0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, ];
let module = Module::deserialize(&mut wasm_bytes.as_slice()).unwrap();
let owner_signed = sign_with_certificate(
&owner_provider,
owner_key,
module,
&[owner_cert.clone(), owner_ca.certificate().to_vec()],
)
.unwrap();
let integrator_ca =
PrivateCA::create_root(CAConfig::new("Integrator Inc", "Integrator Root CA")).unwrap();
let integrator_provider = SoftwareProvider::new();
let integrator_key = integrator_provider.generate_key().unwrap();
let integrator_id = DeviceIdentity::new("integrator-device");
let integrator_config = CertificateConfig::new("integrator-device");
let integrator_cert = integrator_provider
.with_keypair(integrator_key, |integrator_keypair| {
integrator_ca.sign_device_certificate_with_keypair(
integrator_keypair,
&integrator_id,
&integrator_config,
)
})
.unwrap()
.unwrap();
let dual_signed = sign_with_certificate(
&integrator_provider,
integrator_key,
owner_signed,
&[integrator_cert, integrator_ca.certificate().to_vec()],
)
.unwrap();
let mut dual_bytes = Vec::new();
dual_signed.serialize(&mut dual_bytes).unwrap();
let signatures = inspect_signatures(&mut dual_bytes.as_slice()).unwrap();
assert_eq!(signatures.len(), 2, "Should have 2 signatures");
for (i, sig) in signatures.iter().enumerate() {
assert!(
sig.has_certificate_chain,
"Signature {} should have certificate chain",
i
);
assert_eq!(
sig.certificate_count, 2,
"Signature {} should have 2 certificates (device + root)",
i
);
assert!(
sig.subject_dn.is_some(),
"Signature {} should have subject DN",
i
);
}
println!("✓ Found 2 signatures:");
for sig in &signatures {
println!(
" - Signature {}: {} ({} certs)",
sig.index,
sig.subject_dn.as_ref().unwrap(),
sig.certificate_count
);
}
let owner_verifier = OfflineVerifierBuilder::new()
.with_root(owner_ca.certificate())
.unwrap()
.build()
.unwrap();
let integrator_verifier = OfflineVerifierBuilder::new()
.with_root(integrator_ca.certificate())
.unwrap()
.build()
.unwrap();
let results = verify_all_certificates(
&mut dual_bytes.as_slice(),
&[&owner_verifier, &integrator_verifier],
)
.unwrap();
assert_eq!(results.len(), 2, "Should have 2 verification results");
for result in &results {
assert!(
result.verified,
"Signature {} failed: {:?}",
result.info.index, result.error
);
}
println!("✓ Both signatures verified successfully");
println!("✓ Owner + Integrator multi-signature works!");
}
#[test]
fn test_extract_public_key_from_certificate() {
let root_config = CAConfig::new("Test Corp", "Test Root CA");
let root_ca = PrivateCA::create_root(root_config).unwrap();
let provider = SoftwareProvider::new();
let device_id = DeviceIdentity::new("device-pk-test");
let cert_config = CertificateConfig::new("device-pk-test");
let prov_result =
ProvisioningSession::provision(&root_ca, &provider, device_id, cert_config, false)
.unwrap();
let public_key = extract_public_key_from_certificate(&prov_result.certificate);
let _ = public_key;
}
}