use std::time::Duration;
#[derive(Debug, Clone)]
pub struct CertProfile {
pub validity: Duration,
pub client_auth: bool,
pub server_auth: bool,
pub max_cn_bytes: usize,
}
impl Default for CertProfile {
fn default() -> Self {
Self {
validity: Duration::from_secs(7 * 24 * 60 * 60),
client_auth: true,
server_auth: false,
max_cn_bytes: 128,
}
}
}
pub const MIN_VALIDITY: Duration = Duration::from_secs(60);
pub const MAX_VALIDITY: Duration = Duration::from_secs(5 * 365 * 86_400);
impl CertProfile {
pub fn client_auth_for_days(days: u64) -> Self {
let profile = Self { validity: Duration::from_secs(days * 86_400), ..Self::default() };
profile.validate().expect("validity out of range");
profile
}
pub fn validate(&self) -> Result<(), String> {
if self.validity < MIN_VALIDITY {
return Err(format!(
"validity {:?} < MIN_VALIDITY {:?}",
self.validity, MIN_VALIDITY
));
}
if self.validity > MAX_VALIDITY {
return Err(format!(
"validity {:?} > MAX_VALIDITY {:?}",
self.validity, MAX_VALIDITY
));
}
Ok(())
}
pub fn validate_cn(&self, cn: &str) -> Result<(), String> {
if cn.is_empty() {
return Err("CN must not be empty".into());
}
if cn.as_bytes().len() > self.max_cn_bytes {
return Err(format!("CN exceeds {} bytes", self.max_cn_bytes));
}
if cn.chars().any(|c| c.is_control()) {
return Err("CN contains control characters".into());
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validate_cn_rejects_empty_oversize_and_control_chars() {
let p = CertProfile::default();
assert!(p.validate_cn("gateway-001").is_ok());
assert!(p.validate_cn("").is_err());
let huge = "x".repeat(p.max_cn_bytes + 1);
assert!(p.validate_cn(&huge).is_err());
assert!(p.validate_cn("bad\x07cn").is_err());
}
#[test]
fn validate_rejects_zero_and_decade_long_validity() {
let mut p = CertProfile::default();
p.validity = Duration::from_secs(0);
assert!(p.validate().is_err(), "zero validity must be rejected");
p.validity = Duration::from_secs(10 * 365 * 86_400);
assert!(p.validate().is_err(), "ten-year validity must be rejected");
p.validity = Duration::from_secs(30 * 86_400);
assert!(p.validate().is_ok(), "30 days must be accepted");
}
#[test]
#[should_panic(expected = "validity out of range")]
fn client_auth_for_days_panics_on_zero() {
let _ = CertProfile::client_auth_for_days(0);
}
}