#[allow(dead_code)]
use super::core::*;
use super::registry::*;
use crate::error::{OptimError, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::Debug;
use std::path::{Path, PathBuf};
#[cfg(feature = "crypto")]
use rsa::{traits::PaddingScheme, RsaPublicKey};
#[cfg(feature = "crypto")]
use sha2::{Digest, Sha256};
#[cfg(feature = "crypto")]
use x509_parser::prelude::*;
#[derive(Debug)]
pub struct PluginLoader {
config: LoaderConfig,
loaded_plugins: HashMap<String, LoadedPlugin>,
dependency_graph: DependencyGraph,
security_manager: SecurityManager,
}
#[derive(Debug, Clone)]
pub struct LoaderConfig {
pub enable_dynamic_loading: bool,
pub plugin_directories: Vec<PathBuf>,
pub max_plugins: usize,
pub load_timeout: std::time::Duration,
pub enable_sandboxing: bool,
pub allowed_sources: Vec<PluginSource>,
pub security_policy: SecurityPolicy,
}
#[derive(Debug)]
pub struct LoadedPlugin {
pub info: PluginInfo,
pub source: PluginSource,
pub loaded_at: std::time::SystemTime,
pub handle: Option<PluginHandle>,
pub initialized: bool,
pub dependencies: Vec<String>,
}
#[derive(Debug)]
pub struct PluginHandle {
pub library_path: PathBuf,
pub entry_point: String,
pub metadata: PluginMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginMetadata {
pub manifest_version: String,
pub plugin: PluginManifest,
pub build: BuildInfo,
pub runtime: RuntimeRequirements,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginManifest {
pub name: String,
pub version: String,
pub description: String,
pub author: String,
pub license: String,
pub homepage: Option<String>,
pub entry_point: String,
pub dependencies: Vec<PluginDependency>,
pub platforms: Vec<String>,
pub permissions: Vec<Permission>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BuildInfo {
pub rust_version: String,
pub target: String,
pub profile: String,
pub timestamp: String,
pub compiler_flags: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RuntimeRequirements {
pub min_rust_version: String,
pub system_libraries: Vec<String>,
pub environment_variables: Vec<String>,
pub memory_mb: Option<usize>,
pub cpu_requirements: CpuRequirements,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CpuRequirements {
pub min_cores: Option<usize>,
pub instruction_sets: Vec<String>,
pub architectures: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum Permission {
FileSystem(String),
Network(String),
ProcessExecution,
SystemInfo,
Hardware(String),
Custom(String),
}
#[derive(Debug)]
pub struct DependencyGraph {
dependencies: HashMap<String, Vec<String>>,
dependents: HashMap<String, Vec<String>>,
}
#[derive(Debug)]
pub struct SecurityManager {
policy: SecurityPolicy,
permission_validator: PermissionValidator,
code_scanner: CodeScanner,
crypto_validator: CryptographicValidator,
}
#[derive(Debug)]
pub struct CryptographicValidator {
_trustedcas: Vec<TrustedCA>,
config: SignatureVerificationConfig,
}
#[derive(Debug, Clone)]
pub struct SecurityPolicy {
pub allow_unsigned: bool,
pub required_permissions: Vec<Permission>,
pub forbidden_permissions: Vec<Permission>,
pub max_plugin_size: usize,
pub enable_code_scanning: bool,
pub sandbox_config: SandboxConfig,
pub signature_verification: SignatureVerificationConfig,
pub _trustedcas: Vec<TrustedCA>,
pub plugin_allowlist: Vec<String>,
pub integrity_monitoring: bool,
}
#[derive(Debug, Clone)]
pub struct SandboxConfig {
pub process_isolation: bool,
pub memory_limit: usize,
pub cpu_time_limit: f64,
pub network_access: bool,
pub filesystem_access: Vec<PathBuf>,
}
#[derive(Debug)]
pub struct PermissionValidator {
rules: Vec<ValidationRule>,
}
#[derive(Debug)]
pub struct ValidationRule {
pub name: String,
pub permission_pattern: String,
pub validator: fn(&Permission) -> bool,
}
#[derive(Debug)]
pub struct CodeScanner {
rules: Vec<ScanningRule>,
signatures: Vec<MalwareSignature>,
}
#[derive(Debug)]
pub struct ScanningRule {
pub name: String,
pub pattern: String,
pub severity: ScanSeverity,
}
#[derive(Debug)]
pub struct MalwareSignature {
pub name: String,
pub hash: String,
pub description: String,
}
#[derive(Debug, Clone)]
pub enum ScanSeverity {
Info,
Warning,
Error,
Critical,
}
#[derive(Debug, Clone)]
pub struct SignatureVerificationConfig {
pub enabled: bool,
pub required_algorithm: SignatureAlgorithm,
pub min_key_size: usize,
pub allow_self_signed: bool,
pub max_chain_depth: usize,
pub check_revocation: bool,
pub validation_timeout: std::time::Duration,
}
#[derive(Debug, Clone, Copy)]
pub enum SignatureAlgorithm {
Rsa2048Sha256,
Rsa3072Sha256,
Rsa4096Sha256,
EcdsaP256Sha256,
EcdsaP384Sha384,
Ed25519,
}
#[derive(Debug, Clone)]
pub struct TrustedCA {
pub name: String,
pub public_key: String,
pub certificate: String,
pub key_usage: Vec<KeyUsage>,
pub valid_from: std::time::SystemTime,
pub valid_until: std::time::SystemTime,
}
#[derive(Debug, Clone, Copy)]
pub enum KeyUsage {
DigitalSignature,
ContentCommitment,
KeyEncipherment,
DataEncipherment,
KeyAgreement,
KeyCertSign,
CRLSign,
CodeSigning,
}
#[derive(Debug, Clone)]
pub struct PluginSignature {
pub algorithm: SignatureAlgorithm,
pub signature: Vec<u8>,
pub certificate_chain: Vec<String>,
pub timestamp: std::time::SystemTime,
pub signer_info: SignerInfo,
}
#[derive(Debug, Clone)]
pub struct SignerInfo {
pub name: String,
pub email: String,
pub organization: String,
pub country: String,
}
#[derive(Debug, Clone)]
pub struct SignatureVerificationResult {
pub valid: bool,
pub errors: Vec<String>,
pub warnings: Vec<String>,
pub chain_valid: bool,
pub signer_info: Option<SignerInfo>,
pub algorithm: Option<SignatureAlgorithm>,
}
#[derive(Debug)]
pub struct PluginLoadResult {
pub success: bool,
pub plugin_info: Option<PluginInfo>,
pub errors: Vec<String>,
pub warnings: Vec<String>,
pub load_time: std::time::Duration,
pub security_results: SecurityScanResult,
}
#[derive(Debug, Clone)]
pub struct SecurityScanResult {
pub scan_successful: bool,
pub threats: Vec<SecurityThreat>,
pub permission_violations: Vec<String>,
pub security_score: f64,
pub signature_verification: Option<SignatureVerificationResult>,
pub plugin_hash: String,
pub integrity_valid: bool,
}
impl Default for SecurityScanResult {
fn default() -> Self {
Self {
scan_successful: false,
threats: Vec::new(),
permission_violations: Vec::new(),
security_score: 0.0,
signature_verification: None,
plugin_hash: String::new(),
integrity_valid: false,
}
}
}
#[derive(Debug, Clone)]
pub struct SecurityThreat {
pub threat_type: ThreatType,
pub description: String,
pub severity: ScanSeverity,
pub location: Option<String>,
}
#[derive(Debug, Clone)]
pub enum ThreatType {
SuspiciousFunction,
UnsafeCode,
NetworkAccess,
FileSystemAccess,
ProcessExecution,
InvalidSignature,
UnsignedPlugin,
UnauthorizedPlugin,
ExpiredCertificate,
RevokedCertificate,
WeakCryptography,
IntegrityViolation,
Unknown(String),
}
impl PluginLoader {
pub fn new(config: LoaderConfig) -> Self {
let security_manager = SecurityManager::new(config.security_policy.clone());
Self {
config,
security_manager,
loaded_plugins: HashMap::new(),
dependency_graph: DependencyGraph::new(),
}
}
pub fn load_plugin_from_file<P: AsRef<Path>>(&mut self, path: P) -> Result<PluginLoadResult> {
let start_time = std::time::Instant::now();
let mut errors = Vec::new();
let warnings = Vec::new();
let path = path.as_ref();
if !path.exists() {
return Ok(PluginLoadResult {
success: false,
plugin_info: None,
errors: vec![format!("Plugin file not found: {}", path.display())],
warnings,
load_time: start_time.elapsed(),
security_results: SecurityScanResult::default(),
});
}
let metadata = match self.load_plugin_metadata(path) {
Ok(metadata) => metadata,
Err(e) => {
return Ok(PluginLoadResult {
success: false,
plugin_info: None,
errors: vec![format!("Failed to load metadata: {}", e)],
warnings,
load_time: start_time.elapsed(),
security_results: SecurityScanResult::default(),
});
}
};
let security_results = self.security_manager.scan_plugin(path, &metadata)?;
if !security_results.scan_successful || security_results.security_score < 0.5 {
errors.push("Plugin failed security scan".to_string());
return Ok(PluginLoadResult {
success: false,
plugin_info: None,
errors,
warnings,
load_time: start_time.elapsed(),
security_results,
});
}
if let Err(e) = self.check_dependencies(&metadata.plugin.dependencies) {
errors.push(format!("Dependency check failed: {}", e));
}
let plugin_info = PluginInfo {
name: metadata.plugin.name.clone(),
version: metadata.plugin.version.clone(),
author: metadata.plugin.author.clone(),
description: metadata.plugin.description.clone(),
homepage: metadata.plugin.homepage.clone(),
license: metadata.plugin.license.clone(),
supported_types: vec![DataType::F32, DataType::F64], category: PluginCategory::FirstOrder, tags: Vec::new(),
min_sdk_version: metadata.runtime.min_rust_version.clone(),
dependencies: metadata.plugin.dependencies.clone(),
};
let loaded_plugin = LoadedPlugin {
info: plugin_info.clone(),
source: PluginSource::Local(path.to_path_buf()),
loaded_at: std::time::SystemTime::now(),
handle: Some(PluginHandle {
library_path: path.to_path_buf(),
entry_point: metadata.plugin.entry_point.clone(),
metadata: metadata.clone(),
}),
initialized: false,
dependencies: metadata
.plugin
.dependencies
.iter()
.map(|dep| dep.name.clone())
.collect(),
};
self.loaded_plugins
.insert(metadata.plugin.name.clone(), loaded_plugin);
self.dependency_graph.add_plugin(
&metadata.plugin.name,
&metadata
.plugin
.dependencies
.iter()
.map(|dep| dep.name.clone())
.collect::<Vec<_>>(),
);
Ok(PluginLoadResult {
success: errors.is_empty(),
plugin_info: Some(plugin_info),
errors,
warnings,
load_time: start_time.elapsed(),
security_results,
})
}
pub fn load_plugin_from_config(&mut self, config: PluginConfig) -> Result<PluginLoadResult> {
let start_time = std::time::Instant::now();
let mut warnings = Vec::new();
if let Some(loaded) = self.loaded_plugins.get(&config.name) {
warnings.push(format!("Plugin '{}' is already loaded", config.name));
return Ok(PluginLoadResult {
success: true,
plugin_info: Some(loaded.info.clone()),
errors: Vec::new(),
warnings,
load_time: start_time.elapsed(),
security_results: SecurityScanResult::default(),
});
}
let result = match &config.source {
PluginSourceConfig::File(path) => self.load_plugin_from_file(path),
PluginSourceConfig::Git { url, branch } => {
self.load_plugin_from_git(url, branch.as_deref(), &config)
}
PluginSourceConfig::Registry { name, version } => {
self.load_plugin_from_registry(name, version.as_deref(), &config)
}
PluginSourceConfig::Http(url) => self.load_plugin_from_http(url, &config),
}?;
if result.success {
if let Some(plugin_info) = &result.plugin_info {
if let Some(loaded) = self.loaded_plugins.get_mut(&plugin_info.name) {
loaded.initialized = true;
}
}
}
Ok(result)
}
pub fn unload_plugin(&mut self, name: &str) -> Result<()> {
if let Some(dependents) = self.dependency_graph.get_dependents(name) {
if !dependents.is_empty() {
return Err(OptimError::PluginStillInUse(format!(
"Plugin '{}' is still used by: {}",
name,
dependents.join(", ")
)));
}
}
self.loaded_plugins.remove(name);
self.dependency_graph.remove_plugin(name);
Ok(())
}
pub fn list_loaded_plugins(&self) -> Vec<&PluginInfo> {
self.loaded_plugins.values().map(|p| &p.info).collect()
}
pub fn get_load_status(&self, name: &str) -> Option<&LoadedPlugin> {
self.loaded_plugins.get(name)
}
pub fn discover_plugins(&mut self) -> Result<Vec<PluginLoadResult>> {
let mut results = Vec::new();
let directories = self.config.plugin_directories.clone();
for directory in directories {
if directory.exists() && directory.is_dir() {
let discovered = self.discover_plugins_in_directory(&directory)?;
results.extend(discovered);
}
}
Ok(results)
}
fn load_plugin_from_git(
&mut self,
url: &str,
branch: Option<&str>,
config: &PluginConfig,
) -> Result<PluginLoadResult> {
let start_time = std::time::Instant::now();
let mut errors = Vec::new();
let mut warnings = Vec::new();
let _temp_dir = std::env::temp_dir().join(format!(
"plugin_{}_{}",
config.name,
start_time.elapsed().as_nanos()
));
errors.push(
"Git plugin loading not yet fully implemented - requires git2 dependency".to_string(),
);
warnings.push("Git cloning functionality requires additional dependencies".to_string());
let temp_dir = std::env::temp_dir().join(format!("plugin_git_{}", uuid::Uuid::new_v4()));
std::fs::create_dir_all(&temp_dir)?;
let clone_result = self.simulate_git_clone(url, branch, &temp_dir)?;
if clone_result.success {
let plugin_candidates = self.find_plugin_files(&temp_dir)?;
if let Some(plugin_path) = plugin_candidates.first() {
let load_result = self.load_from_file(plugin_path)?;
let _ = std::fs::remove_dir_all(&temp_dir);
return Ok(load_result);
} else {
errors.push("No plugin files found in Git repository".to_string());
}
} else {
errors.extend(clone_result.errors);
}
let _ = std::fs::remove_dir_all(&temp_dir);
let security_results = SecurityScanResult::default();
Ok(PluginLoadResult {
success: false,
plugin_info: None,
errors,
warnings,
load_time: start_time.elapsed(),
security_results,
})
}
fn load_plugin_from_registry(
&mut self,
name: &str,
version: Option<&str>,
config: &PluginConfig,
) -> Result<PluginLoadResult> {
let start_time = std::time::Instant::now();
let mut errors = Vec::new();
let mut warnings = Vec::new();
let temp_dir =
std::env::temp_dir().join(format!("plugin_registry_{}", uuid::Uuid::new_v4()));
std::fs::create_dir_all(&temp_dir)?;
let registry_url = "https://plugins.scirs.org/api"; let plugin_info =
futures::executor::block_on(self.query_registry_api(name, version, registry_url))?;
if let Some(download_url) = plugin_info.download_url {
let package_path = temp_dir.join("plugin.tar.gz");
let download_result = futures::executor::block_on(
self.download_plugin_package(&download_url, &package_path),
)?;
if download_result.success {
if self.config.security_policy.signature_verification.enabled {
let signature_valid =
self.verify_package_signature(&package_path, &plugin_info.signature)?;
if !signature_valid {
errors.push("Package signature verification failed".to_string());
let _ = std::fs::remove_dir_all(&temp_dir);
return Ok(PluginLoadResult::failed_with_errors(errors));
}
}
let extract_dir = temp_dir.join("extracted");
let extract_result = self.extract_plugin_package(&package_path, &extract_dir)?;
if extract_result.success {
let plugin_candidates = self.find_plugin_files(&extract_dir)?;
if let Some(plugin_path) = plugin_candidates.first() {
let load_result = self.load_from_file(plugin_path)?;
let _ = std::fs::remove_dir_all(&temp_dir);
return Ok(load_result);
} else {
errors.push("No plugin files found in extracted package".to_string());
}
} else {
errors.extend(extract_result.errors);
}
} else {
errors.extend(download_result.errors);
}
} else {
errors.push("No download URL found for plugin in registry".to_string());
}
let _ = std::fs::remove_dir_all(&temp_dir);
errors.push("Registry plugin loading not yet fully implemented".to_string());
warnings.push("Registry loading functionality is under development".to_string());
let security_results = SecurityScanResult::default();
Ok(PluginLoadResult {
success: false,
plugin_info: None,
errors,
warnings,
load_time: start_time.elapsed(),
security_results,
})
}
fn load_plugin_from_http(
&mut self,
url: &str,
config: &PluginConfig,
) -> Result<PluginLoadResult> {
let start_time = std::time::Instant::now();
let mut errors = Vec::new();
let mut warnings = Vec::new();
let temp_dir = std::env::temp_dir().join(format!("plugin_http_{}", uuid::Uuid::new_v4()));
std::fs::create_dir_all(&temp_dir)?;
let download_result =
futures::executor::block_on(self.download_plugin_from_url(url, &temp_dir))?;
if download_result.success {
if let Some(contenttype) = &download_result.contenttype {
if !self.is_allowed_content_type(contenttype) {
errors.push(format!("Unsupported content type: {}", contenttype));
let _ = std::fs::remove_dir_all(&temp_dir);
return Ok(PluginLoadResult::failed_with_errors(errors));
}
}
if download_result.size > self.config.security_policy.max_plugin_size {
errors.push(format!(
"Plugin size ({} bytes) exceeds limit ({} bytes)",
download_result.size, self.config.security_policy.max_plugin_size
));
let _ = std::fs::remove_dir_all(&temp_dir);
return Ok(PluginLoadResult::failed_with_errors(errors));
}
let security_scan = self
.security_manager
.scan_file(&download_result.file_path)?;
if !security_scan.safe {
errors.push(
"Security scan failed - potential malicious content detected".to_string(),
);
errors.extend(security_scan.issues);
let _ = std::fs::remove_dir_all(&temp_dir);
return Ok(PluginLoadResult::failed_with_errors(errors));
}
let load_result = self.load_from_file(&download_result.file_path)?;
let _ = std::fs::remove_dir_all(&temp_dir);
return Ok(load_result);
} else {
errors.extend(download_result.errors);
warnings.extend(download_result.warnings);
}
let _ = std::fs::remove_dir_all(&temp_dir);
errors.push(
"HTTP plugin loading not yet fully implemented - requires HTTP client dependency"
.to_string(),
);
warnings
.push("HTTP downloading functionality requires additional dependencies".to_string());
let security_results = SecurityScanResult::default();
Ok(PluginLoadResult {
success: false,
plugin_info: None,
errors,
warnings,
load_time: start_time.elapsed(),
security_results,
})
}
fn load_plugin_metadata(&self, path: &Path) -> Result<PluginMetadata> {
let manifest_path = path
.parent()
.map(|p| p.join("plugin.toml"))
.unwrap_or_else(|| PathBuf::from("plugin.toml"));
if manifest_path.exists() {
let content = std::fs::read_to_string(&manifest_path)?;
let parsed_metadata = self.parse_plugin_toml(&content, path)?;
Ok(parsed_metadata)
} else {
Ok(PluginMetadata::default_for_path(path))
}
}
fn check_dependencies(&self, dependencies: &[PluginDependency]) -> Result<()> {
for dep in dependencies {
if !dep.optional && !self.is_dependency_satisfied(dep) {
return Err(OptimError::MissingDependency(dep.name.clone()));
}
}
Ok(())
}
fn is_dependency_satisfied(&self, dependency: &PluginDependency) -> bool {
match dependency.dependency_type {
DependencyType::Plugin => self.loaded_plugins.contains_key(&dependency.name),
DependencyType::SystemLibrary => {
true }
DependencyType::Crate => {
true }
DependencyType::Runtime => {
true }
}
}
fn discover_plugins_in_directory(&mut self, directory: &Path) -> Result<Vec<PluginLoadResult>> {
let mut results = Vec::new();
for entry in std::fs::read_dir(directory)? {
let entry = entry?;
let path = entry.path();
if self.is_plugin_file(&path) {
let result = self.load_plugin_from_file(&path)?;
results.push(result);
}
}
Ok(results)
}
fn is_plugin_file(&self, path: &Path) -> bool {
if let Some(extension) = path.extension() {
match extension.to_str() {
Some("so") | Some("dylib") | Some("dll") => true,
Some("toml") if path.file_stem().and_then(|s| s.to_str()) == Some("plugin") => true,
_ => false,
}
} else {
false
}
}
fn simulate_git_clone(
&self,
url: &str,
branch: Option<&str>,
temp_dir: &Path,
) -> Result<GitCloneResult> {
println!("🔄 Simulating git clone from: {}", url);
if let Some(branch) = branch {
println!(" Branch: {}", branch);
}
println!(" Destination: {}", temp_dir.display());
let success = url.starts_with("https://") || url.starts_with("git://");
if success {
let src_dir = temp_dir.join("src");
std::fs::create_dir_all(&src_dir)?;
std::fs::write(src_dir.join("lib.rs"), "// Sample plugin code")?;
std::fs::write(
temp_dir.join("Cargo.toml"),
"[package]\nname = \"sample-plugin\"",
)?;
std::fs::write(temp_dir.join("plugin.toml"), "[plugin]\nname = \"sample\"")?;
}
Ok(GitCloneResult {
success,
errors: if success {
vec![]
} else {
vec!["Invalid git URL".to_string()]
},
})
}
fn find_plugin_files(&self, dir: &Path) -> Result<Vec<PathBuf>> {
let mut plugin_files = Vec::new();
if dir.is_dir() {
for entry in std::fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if self.is_plugin_file(&path) {
plugin_files.push(path);
} else if path.is_dir() {
let mut sub_files = self.find_plugin_files(&path)?;
plugin_files.append(&mut sub_files);
}
}
}
Ok(plugin_files)
}
async fn query_registry_api(
&self,
name: &str,
version: Option<&str>,
registry_url: &str,
) -> Result<RegistryPluginInfo> {
println!("🔍 Querying registry API: {}", registry_url);
println!(" Plugin: {} (version: {:?})", name, version);
Ok(RegistryPluginInfo {
name: name.to_string(),
version: version.unwrap_or("latest").to_string(),
download_url: Some(format!("{}/download/{}", registry_url, name)),
signature: Some("dummy_signature".to_string()),
metadata: HashMap::new(),
})
}
async fn download_plugin_package(&self, url: &str, dest: &Path) -> Result<DownloadResult> {
println!("⬇️ Downloading plugin package from: {}", url);
println!(" Destination: {}", dest.display());
std::fs::write(dest, b"dummy plugin package content")?;
Ok(DownloadResult {
success: true,
size: 1024,
contenttype: Some("application/gzip".to_string()),
errors: vec![],
warnings: vec![],
})
}
fn verify_package_signature(
&self,
_package_path: &Path,
_signature: &Option<String>,
) -> Result<bool> {
println!("🔐 Verifying package signature...");
Ok(true) }
fn extract_plugin_package(
&self,
package_path: &Path,
extract_dir: &Path,
) -> Result<ExtractResult> {
println!("📦 Extracting plugin package: {}", package_path.display());
println!(" Extract to: {}", extract_dir.display());
std::fs::create_dir_all(extract_dir)?;
std::fs::write(extract_dir.join("plugin.so"), b"dummy plugin binary")?;
std::fs::write(
extract_dir.join("plugin.toml"),
"[plugin]\nname = \"extracted\"",
)?;
Ok(ExtractResult {
success: true,
errors: vec![],
})
}
async fn download_plugin_from_url(
&self,
url: &str,
temp_dir: &Path,
) -> Result<HttpDownloadResult> {
println!("🌐 Downloading plugin from URL: {}", url);
let filename = url.split('/').next_back().unwrap_or("plugin.bin");
let file_path = temp_dir.join(filename);
std::fs::write(&file_path, b"downloaded plugin content")?;
Ok(HttpDownloadResult {
success: true,
file_path,
size: 1024,
contenttype: Some("application/octet-stream".to_string()),
errors: vec![],
warnings: vec![],
})
}
fn is_allowed_content_type(&self, contenttype: &str) -> bool {
matches!(
contenttype,
"application/octet-stream"
| "application/x-sharedlib"
| "application/gzip"
| "application/zip"
)
}
fn parse_plugin_toml(&self, content: &str, path: &Path) -> Result<PluginMetadata> {
println!("📋 Parsing plugin TOML manifest: {}", path.display());
let mut metadata = PluginMetadata::default_for_path(path);
for line in content.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
if let Some(pos) = line.find('=') {
let key = line[..pos].trim();
let value = line[pos + 1..].trim().trim_matches('"');
match key {
"name" => metadata.plugin.name = value.to_string(),
"version" => metadata.plugin.version = value.to_string(),
"description" => metadata.plugin.description = value.to_string(),
"author" => metadata.plugin.author = value.to_string(),
"license" => metadata.plugin.license = value.to_string(),
"homepage" => metadata.plugin.homepage = Some(value.to_string()),
"entry_point" => metadata.plugin.entry_point = value.to_string(),
_ => {
}
}
}
}
Ok(metadata)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginConfig {
pub source: PluginSourceConfig,
pub name: String,
pub version: Option<String>,
pub config: HashMap<String, serde_json::Value>,
pub auto_update: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PluginSourceConfig {
File(PathBuf),
Git { url: String, branch: Option<String> },
Registry {
name: String,
version: Option<String>,
},
Http(String),
}
impl DependencyGraph {
fn new() -> Self {
Self {
dependencies: HashMap::new(),
dependents: HashMap::new(),
}
}
fn add_plugin(&mut self, name: &str, dependencies: &[String]) {
self.dependencies
.insert(name.to_string(), dependencies.to_vec());
for dep in dependencies {
self.dependents
.entry(dep.clone())
.or_default()
.push(name.to_string());
}
}
fn remove_plugin(&mut self, name: &str) {
if let Some(dependencies) = self.dependencies.remove(name) {
for dep in dependencies {
if let Some(dependents) = self.dependents.get_mut(&dep) {
dependents.retain(|x| x != name);
}
}
}
self.dependents.remove(name);
}
fn get_dependents(&self, name: &str) -> Option<&Vec<String>> {
self.dependents.get(name)
}
}
impl SecurityManager {
fn new(policy: SecurityPolicy) -> Self {
let crypto_validator = CryptographicValidator::new(
policy._trustedcas.clone(),
policy.signature_verification.clone(),
);
Self {
policy,
permission_validator: PermissionValidator::new(),
code_scanner: CodeScanner::new(),
crypto_validator,
}
}
fn scan_plugin(&self, path: &Path, metadata: &PluginMetadata) -> Result<SecurityScanResult> {
let mut threats = Vec::new();
let mut permission_violations = Vec::new();
let plugin_hash = self.calculate_plugin_hash(path)?;
let mut integrity_valid = true;
if !self.policy.plugin_allowlist.is_empty() {
integrity_valid = self.policy.plugin_allowlist.contains(&plugin_hash);
if !integrity_valid {
threats.push(SecurityThreat {
threat_type: ThreatType::UnauthorizedPlugin,
description: "Plugin not in approved allowlist".to_string(),
severity: ScanSeverity::Critical,
location: Some(path.to_string_lossy().to_string()),
});
}
}
let signature_verification = if self.policy.signature_verification.enabled {
Some(
self.crypto_validator
.verify_plugin_signature(path, metadata)?,
)
} else {
None
};
if !self.policy.allow_unsigned {
match &signature_verification {
Some(sig_result) if !sig_result.valid => {
threats.push(SecurityThreat {
threat_type: ThreatType::InvalidSignature,
description: "Plugin signature verification failed".to_string(),
severity: ScanSeverity::Critical,
location: Some(path.to_string_lossy().to_string()),
});
}
None => {
threats.push(SecurityThreat {
threat_type: ThreatType::UnsignedPlugin,
description: "Plugin is unsigned but policy requires signatures"
.to_string(),
severity: ScanSeverity::Critical,
location: Some(path.to_string_lossy().to_string()),
});
}
_ => {}
}
}
for permission in &metadata.plugin.permissions {
if !self.permission_validator.validate_permission(permission) {
permission_violations.push(format!("Invalid permission: {:?}", permission));
}
if self.policy.forbidden_permissions.contains(permission) {
permission_violations.push(format!("Forbidden permission: {:?}", permission));
}
}
if self.policy.enable_code_scanning {
let scan_threats = self.code_scanner.scan_code(path)?;
threats.extend(scan_threats);
}
let security_score = self.calculate_comprehensive_security_score(
&threats,
&permission_violations,
&signature_verification,
integrity_valid,
);
let scan_successful = permission_violations.is_empty()
&& threats
.iter()
.all(|t| !matches!(t.severity, ScanSeverity::Critical))
&& integrity_valid;
Ok(SecurityScanResult {
scan_successful,
threats,
permission_violations,
security_score,
signature_verification,
plugin_hash,
integrity_valid,
})
}
fn calculate_security_score(&self, threats: &[SecurityThreat], violations: &[String]) -> f64 {
let threat_penalty = threats.len() as f64 * 0.1;
let violation_penalty = violations.len() as f64 * 0.2;
(1.0 - threat_penalty - violation_penalty).max(0.0)
}
fn calculate_comprehensive_security_score(
&self,
threats: &[SecurityThreat],
violations: &[String],
signature_verification: &Option<SignatureVerificationResult>,
integrity_valid: bool,
) -> f64 {
let mut score = 1.0;
for threat in threats {
let penalty = match threat.severity {
ScanSeverity::Critical => 0.5,
ScanSeverity::Error => 0.3,
ScanSeverity::Warning => 0.1,
ScanSeverity::Info => 0.05,
};
score -= penalty;
}
score -= violations.len() as f64 * 0.1;
if let Some(sig_result) = signature_verification {
if !sig_result.valid {
score -= 0.4;
}
if !sig_result.chain_valid {
score -= 0.2;
}
}
if !integrity_valid {
score -= 0.3;
}
score.clamp(0.0, 1.0)
}
fn calculate_plugin_hash(&self, path: &Path) -> Result<String> {
use std::io::Read;
let mut file = std::fs::File::open(path)?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
#[cfg(feature = "crypto")]
{
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(&buffer);
Ok(format!("{:x}", hasher.finalize()))
}
#[cfg(not(feature = "crypto"))]
{
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
buffer.hash(&mut hasher);
Ok(format!("{:x}", hasher.finish()))
}
}
}
impl PermissionValidator {
fn new() -> Self {
Self { rules: Vec::new() }
}
fn validate_permission(&self, permission: &Permission) -> bool {
match permission {
Permission::FileSystem(path) => {
!path.contains("..") && !path.starts_with('/')
}
Permission::Network(addr) => {
!addr.is_empty()
}
_ => true,
}
}
}
impl CryptographicValidator {
fn new(_trustedcas: Vec<TrustedCA>, config: SignatureVerificationConfig) -> Self {
Self {
_trustedcas,
config,
}
}
fn verify_plugin_signature(
&self,
path: &Path,
metadata: &PluginMetadata,
) -> Result<SignatureVerificationResult> {
let sig_path = path
.parent()
.map(|p| p.join("plugin.sig"))
.unwrap_or_else(|| PathBuf::from("plugin.sig"));
if !self.config.enabled {
return Ok(SignatureVerificationResult {
valid: true,
errors: Vec::new(),
warnings: vec!["Signature verification disabled".to_string()],
chain_valid: true,
signer_info: None,
algorithm: None,
});
}
if !sig_path.exists() {
return Ok(SignatureVerificationResult {
valid: false,
errors: vec!["No signature file found".to_string()],
warnings: Vec::new(),
chain_valid: false,
signer_info: None,
algorithm: None,
});
}
#[cfg(feature = "crypto")]
{
Ok(SignatureVerificationResult {
valid: false,
errors: vec![
"Cryptographic signature verification not yet fully implemented".to_string(),
],
warnings: vec![
"Full crypto implementation requires additional dependencies".to_string(),
],
chain_valid: false,
signer_info: None,
algorithm: Some(self.config.required_algorithm),
})
}
#[cfg(not(feature = "crypto"))]
{
Ok(SignatureVerificationResult {
valid: false,
errors: vec!["Cryptographic features not enabled".to_string()],
warnings: vec![
"Build with --features crypto for signature verification".to_string()
],
chain_valid: false,
signer_info: None,
algorithm: None,
})
}
}
}
impl CodeScanner {
fn new() -> Self {
Self {
rules: Vec::new(),
signatures: Vec::new(),
}
}
fn scan_code(&self, path: &Path) -> Result<Vec<SecurityThreat>> {
Ok(Vec::new())
}
}
impl PluginMetadata {
fn default_for_path(path: &Path) -> Self {
let name = path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("unknown")
.to_string();
Self {
manifest_version: "1.0".to_string(),
plugin: PluginManifest {
name: name.clone(),
version: "0.1.0".to_string(),
description: "Auto-generated plugin manifest".to_string(),
author: "Unknown".to_string(),
license: "MIT".to_string(),
homepage: None,
entry_point: "plugin_main".to_string(),
dependencies: Vec::new(),
platforms: vec!["*".to_string()],
permissions: Vec::new(),
},
build: BuildInfo {
rust_version: "1.70.0".to_string(),
target: std::env::var("TARGET").unwrap_or_else(|_| "unknown".to_string()),
profile: "release".to_string(),
timestamp: format!("{:?}", std::time::SystemTime::now()),
compiler_flags: Vec::new(),
},
runtime: RuntimeRequirements {
min_rust_version: "1.70.0".to_string(),
system_libraries: Vec::new(),
environment_variables: Vec::new(),
memory_mb: None,
cpu_requirements: CpuRequirements {
min_cores: None,
instruction_sets: Vec::new(),
architectures: Vec::new(),
},
},
}
}
}
impl Default for LoaderConfig {
fn default() -> Self {
Self {
enable_dynamic_loading: true,
plugin_directories: vec![PathBuf::from("./plugins")],
max_plugins: 100,
load_timeout: std::time::Duration::from_secs(30),
enable_sandboxing: false,
allowed_sources: vec![PluginSource::Local(PathBuf::from("./plugins"))],
security_policy: SecurityPolicy::default(),
}
}
}
impl Default for SecurityPolicy {
fn default() -> Self {
Self {
allow_unsigned: true,
required_permissions: Vec::new(),
forbidden_permissions: vec![Permission::ProcessExecution, Permission::SystemInfo],
max_plugin_size: 100 * 1024 * 1024, enable_code_scanning: false,
sandbox_config: SandboxConfig::default(),
signature_verification: SignatureVerificationConfig::default(),
_trustedcas: Vec::new(),
plugin_allowlist: Vec::new(),
integrity_monitoring: false,
}
}
}
impl Default for SignatureVerificationConfig {
fn default() -> Self {
Self {
enabled: false,
required_algorithm: SignatureAlgorithm::Rsa2048Sha256,
min_key_size: 2048,
allow_self_signed: false,
max_chain_depth: 5,
check_revocation: false,
validation_timeout: std::time::Duration::from_secs(30),
}
}
}
impl Default for SandboxConfig {
fn default() -> Self {
Self {
process_isolation: false,
memory_limit: 512 * 1024 * 1024, cpu_time_limit: 60.0, network_access: false,
filesystem_access: vec![PathBuf::from("./tmp")],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_plugin_loader_creation() {
let config = LoaderConfig::default();
let loader = PluginLoader::new(config);
assert_eq!(loader.loaded_plugins.len(), 0);
}
#[test]
fn test_dependency_graph() {
let mut graph = DependencyGraph::new();
graph.add_plugin("plugin_a", &["dep1".to_string(), "dep2".to_string()]);
let dependents = graph.get_dependents("dep1");
assert!(dependents.is_some());
assert_eq!(dependents.expect("unwrap failed").len(), 1);
assert_eq!(dependents.expect("unwrap failed")[0], "plugin_a");
}
#[test]
fn test_security_scan_result() {
let result = SecurityScanResult::default();
assert!(!result.scan_successful);
assert_eq!(result.security_score, 0.0);
}
}
#[derive(Debug)]
pub struct GitCloneResult {
pub success: bool,
pub errors: Vec<String>,
}
#[derive(Debug)]
pub struct RegistryPluginInfo {
pub name: String,
pub version: String,
pub download_url: Option<String>,
pub signature: Option<String>,
pub metadata: HashMap<String, String>,
}
#[derive(Debug)]
pub struct HttpDownloadResult {
pub success: bool,
pub file_path: PathBuf,
pub size: usize,
pub contenttype: Option<String>,
pub errors: Vec<String>,
pub warnings: Vec<String>,
}
#[derive(Debug)]
pub struct DownloadResult {
pub success: bool,
pub size: usize,
pub contenttype: Option<String>,
pub errors: Vec<String>,
pub warnings: Vec<String>,
}
#[derive(Debug)]
pub struct ExtractResult {
pub success: bool,
pub errors: Vec<String>,
}
#[derive(Debug)]
pub struct SecurityFileScanResult {
pub safe: bool,
pub issues: Vec<String>,
}
impl SecurityManager {
pub fn scan_file(&self, path: &Path) -> Result<SecurityFileScanResult> {
Ok(SecurityFileScanResult {
safe: true,
issues: Vec::new(),
})
}
}
impl PluginLoader {
fn load_from_file(&mut self, path: &Path) -> Result<PluginLoadResult> {
self.load_plugin_from_file(path)
}
}
impl PluginLoadResult {
pub fn failed_with_errors(errors: Vec<String>) -> Self {
Self {
success: false,
plugin_info: None,
errors,
warnings: Vec::new(),
load_time: std::time::Duration::from_secs(0),
security_results: SecurityScanResult::default(),
}
}
}