//! Certificate management — load, save, check expiry, generate renewal warnings. use crate::{PublishError, PublishResult}; use std::time::{SystemTime, UNIX_EPOCH}; /// Info about a code signing certificate. #[derive(Debug, Clone)] pub struct CertInfo { pub name: String, pub team_id: String, pub serial: String, /// Certificate type: "Distribution", "Development", "Push", etc. pub cert_type: String, /// Expiry as Unix timestamp (seconds since epoch). pub expires_at: u64, /// Whether the certificate is currently valid. pub is_valid: bool, } impl CertInfo { pub fn new( name: impl Into, team_id: impl Into, serial: impl Into, cert_type: impl Into, expires_at: u64, ) -> Self { let now = unix_now(); Self { name: name.into(), team_id: team_id.into(), serial: serial.into(), cert_type: cert_type.into(), expires_at, is_valid: expires_at > now, } } /// Days until expiry (0 if already expired). pub fn days_until_expiry(&self) -> u64 { let now = unix_now(); if self.expires_at <= now { return 0; } (self.expires_at - now) / 86400 } /// Whether the cert expires within `days` days. pub fn expires_soon(&self, days: u64) -> bool { self.days_until_expiry() <= days } pub fn is_expired(&self) -> bool { unix_now() >= self.expires_at } } /// Certificate store — loads, caches, and checks expiry of code signing certs. pub struct CertStore { certs: Vec, /// How many days before expiry to warn (default: 30). pub warn_days: u64, } impl CertStore { pub fn new() -> Self { Self { certs: Vec::new(), warn_days: 30 } } pub fn with_warn_days(mut self, days: u64) -> Self { self.warn_days = days; self } /// Add a certificate to the store. pub fn add(&mut self, cert: CertInfo) { self.certs.push(cert); } /// Find a certificate by team ID and type. pub fn find(&self, team_id: &str, cert_type: &str) -> Option<&CertInfo> { self.certs.iter().find(|c| { c.team_id == team_id && c.cert_type == cert_type && !c.is_expired() }) } /// Get all certificates expiring soon (within `warn_days`). pub fn expiring_soon(&self) -> Vec<&CertInfo> { self.certs .iter() .filter(|c| c.expires_soon(self.warn_days)) .collect() } /// Generate renewal warnings for expiring certificates. pub fn renewal_warnings(&self) -> Vec { self.expiring_soon() .iter() .map(|c| { if c.is_expired() { format!( "EXPIRED: {} ({}) — team {}. Renew immediately.", c.name, c.cert_type, c.team_id ) } else { format!( "EXPIRING SOON: {} ({}) expires in {} days — team {}. Renew before publishing.", c.name, c.cert_type, c.days_until_expiry(), c.team_id ) } }) .collect() } /// Check that a valid distribution certificate exists for the given team. pub fn validate_for_distribution(&self, team_id: &str) -> PublishResult<()> { let cert = self.find(team_id, "Distribution"); match cert { None => Err(PublishError::Certificate(format!( "no valid Distribution certificate found for team {}. Run: el auth add-apple", team_id ))), Some(c) if c.expires_soon(7) => Err(PublishError::Certificate(format!( "Distribution certificate for team {} expires in {} days. Renew now.", team_id, c.days_until_expiry() ))), _ => Ok(()), } } /// Load certificates from a JSON file (stub — real impl would parse /// Apple's certificate PEM files or keychain API). pub fn load_from_file(_path: &str) -> PublishResult { // TODO: parse certificate PEM/P12 files, extract expiry via x509-parser Ok(Self::new()) } /// Save certificate metadata to a JSON cache file. pub fn save_to_file(&self, _path: &str) -> PublishResult<()> { // TODO: serialize cert metadata to JSON Ok(()) } } impl Default for CertStore { fn default() -> Self { Self::new() } } fn unix_now() -> u64 { SystemTime::now() .duration_since(UNIX_EPOCH) .map(|d| d.as_secs()) .unwrap_or(0) }