use blake3::Hasher; use rand::RngCore; use std::path::Path; use tracing::{info, trace, warn}; pub struct MasterKey([u8; 32]); impl MasterKey { pub fn generate() -> Self { let mut key = [0u8; 32]; rand::thread_rng().fill_bytes(&mut key); Self(key) } pub fn from_hex(hex_str: &str) -> cgcx_core::Result { let bytes = hex::decode(hex_str.trim()) .map_err(|e| cgcx_core::CgcxError::Crypto(format!("invalid master key hex: {}", e)))?; if bytes.len() != 32 { return Err(cgcx_core::CgcxError::Crypto(format!( "master key must be 32 bytes (64 hex chars), got {} bytes", bytes.len() ))); } let mut key = [0u8; 32]; key.copy_from_slice(&bytes); Ok(Self(key)) } pub fn to_hex(&self) -> String { hex::encode(self.0) } pub fn as_bytes(&self) -> &[u8; 32] { &self.0 } pub fn fingerprint(&self) -> String { let hash = Hasher::new().update(&self.0).finalize(); hex::encode(&hash.as_bytes()[..8]) } pub fn load_from_env(var: &str) -> cgcx_core::Result { sodiumoxide::init().map_err(|_| cgcx_core::CgcxError::Crypto("sodiumoxide init failed".into()))?; match std::env::var(var) { Ok(val) => { let key = Self::from_hex(&val)?; info!("Master key loaded from env var {}", var); Ok(key) } Err(_) => { let key = Self::generate(); warn!( "Env var {} not set. A new master key has been generated.\n\ SAVE THIS KEY IMMEDIATELY (64 hex chars):\n{}\n\ Set it as {}= to persist across restarts.", var, key.to_hex(), var ); Ok(key) } } } pub fn load_from_file(path: &Path) -> cgcx_core::Result { sodiumoxide::init().map_err(|_| cgcx_core::CgcxError::Crypto("sodiumoxide init failed".into()))?; if path.exists() { let val = std::fs::read_to_string(path) .map_err(|e| cgcx_core::CgcxError::Crypto(format!("read key file: {}", e)))?; let key = Self::from_hex(&val)?; info!("Master key loaded from file {:?}", path); Ok(key) } else { let key = Self::generate(); std::fs::write(path, key.to_hex()) .map_err(|e| cgcx_core::CgcxError::Crypto(format!("write key file: {}", e)))?; #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; let mut perms = std::fs::metadata(path).unwrap().permissions(); perms.set_mode(0o600); std::fs::set_permissions(path, perms).ok(); } warn!("Generated new master key and wrote to {:?}", path); Ok(key) } } pub fn log_startup(&self, debug_log_keys: bool) { info!("Storage master key loaded. fingerprint={}", self.fingerprint()); if debug_log_keys { trace!("Master key full hex: {}", self.to_hex()); } } }