use super::{DEFAULT_TRUST_STORE_PATH, TRUST_STORE_ENV};
use crate::{LoaderResult, PluginLoaderConfig, PluginLoaderError};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::{
fs,
path::{Path, PathBuf},
};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrustedKeyEntry {
pub key_id: String,
pub public_key: String,
#[serde(default)]
pub expires_at: Option<DateTime<Utc>>,
#[serde(default)]
pub revoked: bool,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TrustStoreFile {
#[serde(default)]
pub keys: Vec<TrustedKeyEntry>,
}
impl TrustStoreFile {
pub fn load(path: &Path) -> LoaderResult<Self> {
let raw = fs::read_to_string(path).map_err(|e| {
PluginLoaderError::security(format!(
"Failed to read trust store {}: {}",
path.display(),
e
))
})?;
serde_json::from_str(&raw).map_err(|e| {
PluginLoaderError::security(format!(
"Failed to parse trust store {}: {}",
path.display(),
e
))
})
}
pub fn apply_to(&self, config: &mut PluginLoaderConfig) -> LoaderResult<usize> {
let mut trusted = 0;
for entry in &self.keys {
if entry.revoked {
if !config.revoked_keys.contains(&entry.key_id) {
config.revoked_keys.push(entry.key_id.clone());
}
continue;
}
let key = hex::decode(entry.public_key.trim()).map_err(|e| {
PluginLoaderError::security(format!(
"Invalid public key hex for key '{}': {}",
entry.key_id, e
))
})?;
if !config.trusted_keys.contains(&entry.key_id) {
config.trusted_keys.push(entry.key_id.clone());
}
config.key_data.insert(entry.key_id.clone(), key);
if let Some(expires_at) = entry.expires_at {
config.key_expires_at.insert(entry.key_id.clone(), expires_at);
}
trusted += 1;
}
Ok(trusted)
}
}
pub fn resolve_trust_store_path(explicit: Option<&Path>) -> Option<PathBuf> {
if let Some(path) = explicit {
return Some(path.to_path_buf());
}
if let Ok(path) = std::env::var(TRUST_STORE_ENV) {
if !path.trim().is_empty() {
return Some(PathBuf::from(shellexpand::tilde(path.trim()).as_ref()));
}
}
let default = PathBuf::from(shellexpand::tilde(DEFAULT_TRUST_STORE_PATH).as_ref());
default.exists().then_some(default)
}