use crate::error::WSError;
use crate::platform::KeyHandle;
use std::time::{SystemTime, UNIX_EPOCH};
pub mod ca;
pub mod csr;
pub mod device;
pub mod session;
pub mod verification;
pub mod wasm_signing;
pub use ca::PrivateCA;
pub use csr::CertificateSigningRequest;
pub use device::DeviceIdentity;
pub use session::ProvisioningSession;
pub use verification::{OfflineVerifier, OfflineVerifierBuilder};
pub use wasm_signing::{
SignatureInfo, VerificationResult, inspect_signatures, sign_with_certificate,
verify_all_certificates, verify_with_certificate,
};
#[derive(Debug, Clone)]
pub struct CertificateConfig {
pub device_id: String,
pub organization: String,
pub organizational_unit: Option<String>,
pub validity_days: u32,
pub serial_number: Option<Vec<u8>>,
}
impl Default for CertificateConfig {
fn default() -> Self {
Self {
device_id: String::new(),
organization: "Example Organization".to_string(),
organizational_unit: Some("IoT Devices".to_string()),
validity_days: 365, serial_number: None,
}
}
}
impl CertificateConfig {
pub fn new(device_id: impl Into<String>) -> Self {
Self {
device_id: device_id.into(),
..Default::default()
}
}
pub fn with_organization(mut self, org: impl Into<String>) -> Self {
self.organization = org.into();
self
}
pub fn with_organizational_unit(mut self, ou: impl Into<String>) -> Self {
self.organizational_unit = Some(ou.into());
self
}
pub fn with_validity_days(mut self, days: u32) -> Self {
self.validity_days = days;
self
}
}
#[derive(Debug, Clone)]
pub struct ProvisioningResult {
pub key_handle: KeyHandle,
pub certificate: Vec<u8>,
pub certificate_chain: Vec<Vec<u8>>,
pub device_id: String,
pub serial_number: Vec<u8>,
}
impl ProvisioningResult {
pub fn full_chain(&self) -> Vec<Vec<u8>> {
let mut chain = vec![self.certificate.clone()];
chain.extend(self.certificate_chain.iter().cloned());
chain
}
pub fn to_pem(&self) -> Result<String, WSError> {
use pem::Pem;
let mut pem_output = String::new();
let device_pem = Pem::new("CERTIFICATE", self.certificate.clone());
pem_output.push_str(&pem::encode(&device_pem));
pem_output.push('\n');
for cert_der in &self.certificate_chain {
let cert_pem = Pem::new("CERTIFICATE", cert_der.clone());
pem_output.push_str(&pem::encode(&cert_pem));
pem_output.push('\n');
}
Ok(pem_output)
}
}
pub(crate) fn current_timestamp() -> Result<u64, WSError> {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.map_err(|_e| WSError::InvalidArgument)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_certificate_config_default() {
let config = CertificateConfig::default();
assert_eq!(config.organization, "Example Organization");
assert_eq!(config.validity_days, 365);
}
#[test]
fn test_certificate_config_builder() {
let config = CertificateConfig::new("device-123")
.with_organization("Acme Corp")
.with_organizational_unit("Manufacturing")
.with_validity_days(730);
assert_eq!(config.device_id, "device-123");
assert_eq!(config.organization, "Acme Corp");
assert_eq!(
config.organizational_unit,
Some("Manufacturing".to_string())
);
assert_eq!(config.validity_days, 730);
}
#[test]
fn test_provisioning_result_full_chain() {
let result = ProvisioningResult {
key_handle: KeyHandle::from_raw(1),
certificate: vec![1, 2, 3],
certificate_chain: vec![vec![4, 5, 6], vec![7, 8, 9]],
device_id: "device-1".to_string(),
serial_number: vec![0x01],
};
let full_chain = result.full_chain();
assert_eq!(full_chain.len(), 3);
assert_eq!(full_chain[0], vec![1, 2, 3]); assert_eq!(full_chain[1], vec![4, 5, 6]); assert_eq!(full_chain[2], vec![7, 8, 9]); }
#[test]
fn test_current_timestamp() {
let ts = current_timestamp();
assert!(ts.is_ok());
let timestamp = ts.unwrap();
assert!(timestamp > 1577836800); assert!(timestamp < 4102444800); }
}