use crate::error::WSError;
use crate::platform::SecureKeyProvider;
use crate::provisioning::{CertificateConfig, DeviceIdentity, PrivateCA, ProvisioningResult};
pub struct ProvisioningSession;
impl ProvisioningSession {
pub fn provision(
ca: &PrivateCA,
provider: &dyn SecureKeyProvider,
device_id: DeviceIdentity,
config: CertificateConfig,
lock_key: bool,
) -> Result<ProvisioningResult, WSError> {
device_id.validate()?;
log::info!("Generating key for device: {}", device_id);
let key_handle = provider.generate_key()?;
log::info!("Extracting public key");
let public_key = provider.get_public_key(key_handle)?;
log::info!("Signing device certificate");
let certificate = ca.sign_device_certificate(&public_key, &device_id, &config)?;
let mut certificate_chain = Vec::new();
certificate_chain.push(ca.certificate().to_vec());
let serial_number = Self::generate_serial_number()?;
if lock_key {
log::info!("Locking key slot (write-once mode)");
}
log::info!("Device provisioned successfully: {}", device_id);
Ok(ProvisioningResult {
key_handle,
certificate,
certificate_chain,
device_id: device_id.id().to_string(),
serial_number,
})
}
pub fn provision_batch(
ca: &PrivateCA,
provider: &dyn SecureKeyProvider,
devices: Vec<(DeviceIdentity, CertificateConfig)>,
lock_keys: bool,
) -> Vec<Result<ProvisioningResult, WSError>> {
devices
.into_iter()
.map(|(device_id, config)| Self::provision(ca, provider, device_id, config, lock_keys))
.collect()
}
fn generate_serial_number() -> Result<Vec<u8>, WSError> {
use crate::provisioning::current_timestamp;
let timestamp = current_timestamp()?;
let serial = timestamp.to_be_bytes().to_vec();
Ok(serial)
}
pub fn verify_provisioned_device(
provider: &dyn SecureKeyProvider,
result: &ProvisioningResult,
test_data: &[u8],
) -> Result<(), WSError> {
log::info!("Testing device signature");
let signature = provider.sign(result.key_handle, test_data)?;
let _public_key = provider.get_public_key(result.key_handle)?;
if signature.is_empty() {
return Err(WSError::VerificationError("Empty signature".to_string()));
}
log::info!("Device verification successful");
Ok(())
}
}
#[derive(Debug, Default, Clone)]
pub struct ProvisioningStats {
pub total_provisioned: usize,
pub successful: usize,
pub failed: usize,
pub avg_time_ms: u64,
}
impl ProvisioningStats {
pub fn new() -> Self {
Self::default()
}
pub fn record_success(&mut self, duration_ms: u64) {
self.total_provisioned += 1;
self.successful += 1;
self.update_avg_time(duration_ms);
}
pub fn record_failure(&mut self, duration_ms: u64) {
self.total_provisioned += 1;
self.failed += 1;
self.update_avg_time(duration_ms);
}
fn update_avg_time(&mut self, duration_ms: u64) {
let total = self.total_provisioned;
if total == 1 {
self.avg_time_ms = duration_ms;
} else {
self.avg_time_ms =
((self.avg_time_ms * (total - 1) as u64) + duration_ms) / total as u64;
}
}
pub fn success_rate(&self) -> f64 {
if self.total_provisioned == 0 {
0.0
} else {
self.successful as f64 / self.total_provisioned as f64
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::platform::software::SoftwareProvider;
use crate::provisioning::ca::{CAConfig, PrivateCA};
#[test]
fn test_generate_serial_number() {
let serial1 = ProvisioningSession::generate_serial_number().unwrap();
let _serial2 = ProvisioningSession::generate_serial_number().unwrap();
assert_eq!(serial1.len(), 8); assert!(!serial1.is_empty());
}
#[test]
fn test_provision_device() {
let ca_config = CAConfig::new("Test Corp", "Test CA");
let ca = PrivateCA::create_root(ca_config).unwrap();
let provider = SoftwareProvider::new();
let device_id = DeviceIdentity::new("device-test-001");
let config = CertificateConfig::new("device-test-001");
let result = ProvisioningSession::provision(
&ca, &provider, device_id, config, false, );
assert!(result.is_ok());
let result = result.unwrap();
assert_eq!(result.device_id, "device-test-001");
assert!(!result.certificate.is_empty());
assert!(!result.certificate_chain.is_empty());
assert!(!result.serial_number.is_empty());
}
#[test]
fn test_provision_batch() {
let ca_config = CAConfig::new("Test Corp", "Test CA");
let ca = PrivateCA::create_root(ca_config).unwrap();
let provider = SoftwareProvider::new();
let devices = vec![
(
DeviceIdentity::new("device-001"),
CertificateConfig::new("device-001"),
),
(
DeviceIdentity::new("device-002"),
CertificateConfig::new("device-002"),
),
(
DeviceIdentity::new("device-003"),
CertificateConfig::new("device-003"),
),
];
let results = ProvisioningSession::provision_batch(&ca, &provider, devices, false);
assert_eq!(results.len(), 3);
for result in results {
assert!(result.is_ok());
}
}
#[test]
fn test_provisioning_stats() {
let mut stats = ProvisioningStats::new();
assert_eq!(stats.total_provisioned, 0);
assert_eq!(stats.successful, 0);
assert_eq!(stats.failed, 0);
stats.record_success(100);
assert_eq!(stats.total_provisioned, 1);
assert_eq!(stats.successful, 1);
assert_eq!(stats.avg_time_ms, 100);
stats.record_success(200);
assert_eq!(stats.total_provisioned, 2);
assert_eq!(stats.successful, 2);
assert_eq!(stats.avg_time_ms, 150);
stats.record_failure(50);
assert_eq!(stats.total_provisioned, 3);
assert_eq!(stats.successful, 2);
assert_eq!(stats.failed, 1);
assert_eq!(stats.success_rate(), 2.0 / 3.0);
}
#[test]
fn test_verify_provisioned_device() {
let ca_config = CAConfig::new("Test Corp", "Test CA");
let ca = PrivateCA::create_root(ca_config).unwrap();
let provider = SoftwareProvider::new();
let device_id = DeviceIdentity::new("device-test");
let config = CertificateConfig::new("device-test");
let result =
ProvisioningSession::provision(&ca, &provider, device_id, config, false).unwrap();
let test_data = b"test data for verification";
let verify_result =
ProvisioningSession::verify_provisioned_device(&provider, &result, test_data);
assert!(verify_result.is_ok());
}
}