mockforge_plugin_loader/
signature_gen.rs1use crate::signature::{PluginSignature, SignatureAlgorithm};
7use crate::{LoaderResult, PluginLoaderError};
8use ring::rand::SystemRandom;
9use ring::signature::KeyPair;
10use std::fs;
11use std::path::Path;
12
13pub fn sign_plugin_ed25519(
21 plugin_dir: &Path,
22 key_id: &str,
23 private_key_bytes: &[u8],
24) -> LoaderResult<PluginSignature> {
25 let manifest_file = plugin_dir.join("plugin.toml");
27 if !manifest_file.exists() {
28 return Err(PluginLoaderError::security("Plugin manifest (plugin.toml) not found"));
29 }
30
31 let manifest_content = fs::read(&manifest_file).map_err(|e| {
32 PluginLoaderError::security(format!("Failed to read plugin manifest: {}", e))
33 })?;
34
35 let hash = ring::digest::digest(&ring::digest::SHA256, &manifest_content);
37 let hash_bytes = hash.as_ref();
38
39 let key_pair = ring::signature::Ed25519KeyPair::from_pkcs8(private_key_bytes).map_err(|e| {
41 PluginLoaderError::security(format!("Failed to parse Ed25519 private key: {:?}", e))
42 })?;
43
44 let signature_bytes = key_pair.sign(&manifest_content);
45
46 let signature = PluginSignature::new(
48 SignatureAlgorithm::Ed25519,
49 key_id.to_string(),
50 signature_bytes.as_ref().to_vec(),
51 hash_bytes.to_vec(),
52 );
53
54 let sig_file = plugin_dir.join("plugin.sig");
56 let sig_json = serde_json::to_string_pretty(&signature).map_err(|e| {
57 PluginLoaderError::security(format!("Failed to serialize signature: {}", e))
58 })?;
59
60 fs::write(&sig_file, sig_json).map_err(|e| {
61 PluginLoaderError::security(format!("Failed to write signature file: {}", e))
62 })?;
63
64 Ok(signature)
65}
66
67pub fn generate_ed25519_keypair() -> LoaderResult<(Vec<u8>, Vec<u8>)> {
71 let rng = SystemRandom::new();
72 let pkcs8 = ring::signature::Ed25519KeyPair::generate_pkcs8(&rng).map_err(|e| {
73 PluginLoaderError::security(format!("Failed to generate Ed25519 key pair: {:?}", e))
74 })?;
75
76 let key_pair = ring::signature::Ed25519KeyPair::from_pkcs8(pkcs8.as_ref()).map_err(|e| {
77 PluginLoaderError::security(format!("Failed to parse generated key pair: {:?}", e))
78 })?;
79
80 let public_key = key_pair.public_key().as_ref().to_vec();
81 let private_key = pkcs8.as_ref().to_vec();
82
83 Ok((private_key, public_key))
84}
85
86pub fn save_keypair(
88 private_key: &[u8],
89 public_key: &[u8],
90 output_dir: &Path,
91 key_name: &str,
92) -> LoaderResult<()> {
93 let private_key_file = output_dir.join(format!("{}.private.key", key_name));
94 let public_key_file = output_dir.join(format!("{}.public.key", key_name));
95
96 fs::write(&private_key_file, hex::encode(private_key))
97 .map_err(|e| PluginLoaderError::fs(format!("Failed to write private key: {}", e)))?;
98
99 fs::write(&public_key_file, hex::encode(public_key))
100 .map_err(|e| PluginLoaderError::fs(format!("Failed to write public key: {}", e)))?;
101
102 Ok(())
103}
104
105pub fn load_key_from_file(path: &Path) -> LoaderResult<Vec<u8>> {
107 let hex_content = fs::read_to_string(path)
108 .map_err(|e| PluginLoaderError::fs(format!("Failed to read key file: {}", e)))?;
109
110 hex::decode(hex_content.trim())
111 .map_err(|e| PluginLoaderError::fs(format!("Failed to decode hex key: {}", e)))
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117 use tempfile::TempDir;
118
119 #[test]
120 fn test_generate_keypair() {
121 let result = generate_ed25519_keypair();
122 assert!(result.is_ok());
123
124 let (private_key, public_key) = result.unwrap();
125 assert!(!private_key.is_empty());
126 assert!(!public_key.is_empty());
127 }
128
129 #[test]
130 fn test_save_and_load_keypair() {
131 let temp_dir = TempDir::new().unwrap();
132 let (private_key, public_key) = generate_ed25519_keypair().unwrap();
133
134 save_keypair(&private_key, &public_key, temp_dir.path(), "test-key").unwrap();
136
137 let loaded_private =
139 load_key_from_file(&temp_dir.path().join("test-key.private.key")).unwrap();
140 let loaded_public =
141 load_key_from_file(&temp_dir.path().join("test-key.public.key")).unwrap();
142
143 assert_eq!(private_key, loaded_private);
144 assert_eq!(public_key, loaded_public);
145 }
146
147 #[test]
148 fn test_sign_plugin() {
149 let temp_dir = TempDir::new().unwrap();
150 let (private_key, _public_key) = generate_ed25519_keypair().unwrap();
151
152 let manifest_content = r#"
154[info]
155id = "test-plugin"
156name = "Test Plugin"
157version = "1.0.0"
158description = "A test plugin"
159author = "Test Author"
160"#;
161 fs::write(temp_dir.path().join("plugin.toml"), manifest_content).unwrap();
162
163 let result = sign_plugin_ed25519(temp_dir.path(), "test-key", &private_key);
165 assert!(result.is_ok());
166
167 assert!(temp_dir.path().join("plugin.sig").exists());
169
170 let sig_content = fs::read_to_string(temp_dir.path().join("plugin.sig")).unwrap();
172 let signature: PluginSignature = serde_json::from_str(&sig_content).unwrap();
173 assert_eq!(signature.algorithm, SignatureAlgorithm::Ed25519);
174 assert_eq!(signature.key_id, "test-key");
175 }
176}