//! Federated Policy & Trust Packs. //! //! A Trust Pack is a cryptographically signed bundle of assertions and aliases //! that can be exported from one project and imported into another. //! This enables organizations to distribute security policies, exceptions, //! and patterns without manual reconfiguration. use std::fs; use std::path::Path; use ed25519_dalek::{Signer, SigningKey, VerifyingKey}; use rkyv::{Archive, Deserialize, Serialize}; use stemedb_core::types::{Assertion, ConceptAlias}; use tracing::{info, instrument}; use crate::types::PredicateAliasSet; use crate::{current_timestamp, AphoriaError}; /// Record of a signature for audit trail. /// /// When a Trust Pack is re-signed (key rotation), the previous signature /// is preserved in the signature chain for audit purposes. #[derive(Archive, Deserialize, Serialize, Debug, Clone)] #[archive(check_bytes)] pub struct SignatureRecord { /// Public key of the signer (32 bytes). pub issuer_id: [u8; 32], /// Ed25519 signature. pub signature: [u8; 64], /// Timestamp when this signature was created. pub signed_at: u64, /// Optional reason for re-signing (e.g., "Key rotation", "Security incident"). pub reason: Option, } /// Serializable predicate alias set for Trust Packs. /// /// This is a serializable version of PredicateAliasSet that can be /// included in Trust Pack archives. #[derive(Archive, Deserialize, Serialize, Debug, Clone)] #[archive(check_bytes)] pub struct PackPredicateAliasSet { /// Canonical predicate name. pub canonical: String, /// Aliases that map to the canonical name. pub aliases: Vec, } impl From<&PredicateAliasSet> for PackPredicateAliasSet { fn from(set: &PredicateAliasSet) -> Self { Self { canonical: set.canonical.clone(), aliases: set.aliases.clone() } } } impl From<&PackPredicateAliasSet> for PredicateAliasSet { fn from(set: &PackPredicateAliasSet) -> Self { Self { canonical: set.canonical.clone(), aliases: set.aliases.clone() } } } /// A signed bundle of assertions and aliases. #[derive(Archive, Deserialize, Serialize, Debug, Clone)] #[archive(check_bytes)] pub struct TrustPack { /// Metadata about the pack. pub header: PackHeader, /// Assertions (e.g., acknowledgments, custom rules). pub assertions: Vec, /// Aliases (e.g., mapping custom code paths to RFCs). pub aliases: Vec, /// Predicate aliases for semantic matching. pub predicate_aliases: Vec, /// Ed25519 signature of the serialized content (excluding signature field). pub signature: [u8; 64], /// Chain of previous signatures for audit trail (key rotation history). pub signature_chain: Vec, } /// Metadata header for a Trust Pack. #[derive(Archive, Deserialize, Serialize, Debug, Clone)] #[archive(check_bytes)] pub struct PackHeader { /// Human-readable name of the policy pack. pub name: String, /// Version string (semver recommended). pub version: String, /// Public key of the issuer (32 bytes). pub issuer_id: [u8; 32], /// Creation timestamp (Unix epoch). pub timestamp: u64, } impl TrustPack { /// Create a new Trust Pack. /// /// Signs the content automatically. pub fn new( name: String, version: String, assertions: Vec, aliases: Vec, signing_key: &SigningKey, ) -> Result { Self::new_with_predicate_aliases( name, version, assertions, aliases, Vec::new(), signing_key, ) } /// Create a new Trust Pack with predicate aliases. /// /// Signs the content automatically. pub fn new_with_predicate_aliases( name: String, version: String, assertions: Vec, aliases: Vec, predicate_aliases: Vec, signing_key: &SigningKey, ) -> Result { let timestamp = current_timestamp(); let issuer_id = signing_key.verifying_key().to_bytes(); let header = PackHeader { name, version, issuer_id, timestamp }; // Create temporary pack with zeroed signature to compute hash let temp_pack = TrustPack { header: header.clone(), assertions: assertions.clone(), aliases: aliases.clone(), predicate_aliases: predicate_aliases.clone(), signature: [0u8; 64], signature_chain: Vec::new(), }; // Serialize to bytes for signing let bytes = rkyv::to_bytes::<_, 1024>(&temp_pack) .map_err(|e| AphoriaError::Storage(format!("Serialization failed: {}", e)))?; // Sign the bytes let signature = signing_key.sign(&bytes).to_bytes(); Ok(TrustPack { header, assertions, aliases, predicate_aliases, signature, signature_chain: Vec::new(), }) } /// Save the Trust Pack to a file. pub fn save(&self, path: &Path) -> Result<(), AphoriaError> { let bytes = rkyv::to_bytes::<_, 1024>(self) .map_err(|e| AphoriaError::Storage(format!("Serialization failed: {}", e)))?; fs::write(path, bytes).map_err(|e| { AphoriaError::Storage(format!("Failed to write policy to {}: {e}", path.display())) })?; Ok(()) } /// Load a Trust Pack from a file and verify signature. pub fn load(path: &Path) -> Result { let bytes = fs::read(path).map_err(|e| { AphoriaError::Storage(format!("Failed to read policy from {}: {e}", path.display())) })?; let pack: TrustPack = rkyv::from_bytes(&bytes) .map_err(|e| AphoriaError::Storage(format!("Deserialization failed: {}", e)))?; // Verify signature pack.verify()?; Ok(pack) } /// Verify the signature of the Trust Pack. pub fn verify(&self) -> Result<(), AphoriaError> { use ed25519_dalek::{Signature, Verifier}; // Reconstruct the zero-signature version to check against let temp_pack = TrustPack { header: self.header.clone(), assertions: self.assertions.clone(), aliases: self.aliases.clone(), predicate_aliases: self.predicate_aliases.clone(), signature: [0u8; 64], signature_chain: self.signature_chain.clone(), }; let bytes = rkyv::to_bytes::<_, 1024>(&temp_pack) .map_err(|e| AphoriaError::Storage(format!("Serialization failed: {}", e)))?; let verifying_key = VerifyingKey::from_bytes(&self.header.issuer_id) .map_err(|e| AphoriaError::Storage(format!("Invalid issuer key: {}", e)))?; let signature = Signature::from_bytes(&self.signature); verifying_key .verify(&bytes, &signature) .map_err(|_| AphoriaError::Storage("Invalid policy signature".to_string()))?; Ok(()) } /// Load a Trust Pack from a file WITHOUT verifying signature. /// /// Used for key rotation when the old key is no longer available. pub fn load_unverified(path: &Path) -> Result { let bytes = fs::read(path).map_err(|e| { AphoriaError::Storage(format!("Failed to read policy from {}: {e}", path.display())) })?; let pack: TrustPack = rkyv::from_bytes(&bytes) .map_err(|e| AphoriaError::Storage(format!("Deserialization failed: {}", e)))?; Ok(pack) } /// Re-sign a Trust Pack with a new key, preserving the signature chain. /// /// This is used for key rotation. The old signature is added to the /// signature chain for audit purposes. pub fn resign( name: String, version: String, assertions: Vec, aliases: Vec, predicate_aliases: Vec, signing_key: &SigningKey, signature_chain: Vec, ) -> Result { let timestamp = current_timestamp(); let issuer_id = signing_key.verifying_key().to_bytes(); let header = PackHeader { name, version, issuer_id, timestamp }; // Create temporary pack with zeroed signature to compute hash let temp_pack = TrustPack { header: header.clone(), assertions: assertions.clone(), aliases: aliases.clone(), predicate_aliases: predicate_aliases.clone(), signature: [0u8; 64], signature_chain: signature_chain.clone(), }; // Serialize to bytes for signing let bytes = rkyv::to_bytes::<_, 1024>(&temp_pack) .map_err(|e| AphoriaError::Storage(format!("Serialization failed: {}", e)))?; // Sign the bytes let signature = signing_key.sign(&bytes).to_bytes(); Ok(TrustPack { header, assertions, aliases, predicate_aliases, signature, signature_chain }) } } /// Manager for loading and resolving policies. pub struct PolicyManager { cache_dir: std::path::PathBuf, } impl PolicyManager { /// Create a new policy manager with the specified cache directory. /// /// The cache directory is used for storing downloaded remote policies. pub fn new(cache_dir: &Path) -> Self { Self { cache_dir: cache_dir.to_path_buf() } } /// Resolve and load a list of policy URIs. /// /// Supports: /// - Local paths: `file://./policies/security.pack` or just `./policies/security.pack` /// - HTTP(S): `https://example.com/policies/security.pack` #[instrument(skip(self))] pub fn load_policies(&self, uris: &[String]) -> Result, AphoriaError> { let mut packs = Vec::new(); for uri in uris { let pack = if uri.starts_with("http://") || uri.starts_with("https://") { self.fetch_remote(uri)? } else { let path = if let Some(p) = uri.strip_prefix("file://") { Path::new(p) } else { Path::new(uri) }; TrustPack::load(path)? }; info!(name = %pack.header.name, version = %pack.header.version, "Loaded policy"); packs.push(pack); } Ok(packs) } fn fetch_remote(&self, url: &str) -> Result { // Hash URL for cache filename let hash = blake3::hash(url.as_bytes()); let filename = format!("{}.pack", hash.to_hex()); let cache_path = self.cache_dir.join(filename); if !cache_path.exists() { info!(url, "Fetching remote policy"); let resp = ureq::get(url) .call() .map_err(|e| AphoriaError::Storage(format!("Network error: {}", e)))?; let mut reader = resp.into_reader(); let mut file = fs::File::create(&cache_path).map_err(|e| { AphoriaError::Storage(format!( "Failed to create cache file {}: {e}", cache_path.display() )) })?; std::io::copy(&mut reader, &mut file).map_err(|e| { AphoriaError::Storage(format!( "Failed to write to cache file {}: {e}", cache_path.display() )) })?; } TrustPack::load(&cache_path) } }