Skip to main content

plugin_packager/
signature.rs

1// Copyright 2024 Vincents AI
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4/// Plugin signature verification and cryptographic signing
5///
6/// This module provides capabilities for:
7/// - Signing plugins with private keys (Ed25519, RSA)
8/// - Verifying plugin signatures
9/// - Key management (generation, storage, retrieval)
10/// - Certificate chain validation
11/// - Trust level assessment
12use anyhow::{anyhow, Result};
13use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15
16/// Supported signature algorithms
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
18#[serde(rename_all = "lowercase")]
19pub enum SignatureAlgorithm {
20    Ed25519,
21    #[serde(rename = "rsa-2048")]
22    Rsa2048,
23    #[serde(rename = "rsa-4096")]
24    Rsa4096,
25}
26
27impl SignatureAlgorithm {
28    pub fn as_str(&self) -> &'static str {
29        match self {
30            SignatureAlgorithm::Ed25519 => "ed25519",
31            SignatureAlgorithm::Rsa2048 => "rsa-2048",
32            SignatureAlgorithm::Rsa4096 => "rsa-4096",
33        }
34    }
35
36    pub fn try_parse(s: &str) -> Option<Self> {
37        match s.to_lowercase().as_str() {
38            "ed25519" => Some(SignatureAlgorithm::Ed25519),
39            "rsa-2048" => Some(SignatureAlgorithm::Rsa2048),
40            "rsa-4096" => Some(SignatureAlgorithm::Rsa4096),
41            _ => None,
42        }
43    }
44}
45
46/// Cryptographic key information
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct KeyInfo {
49    pub key_id: String,
50    pub algorithm: SignatureAlgorithm,
51    pub created_at: String,
52    pub expires_at: Option<String>,
53    pub key_material: Vec<u8>,
54    pub is_private: bool,
55    pub fingerprint: String,
56}
57
58/// Plugin signature with metadata
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct PluginSignature {
61    pub key_id: String,
62    pub algorithm: SignatureAlgorithm,
63    pub signature: String, // base64 encoded
64    pub signed_at: String,
65    pub payload_hash: String, // SHA256 of signed content
66}
67
68/// Signature verification result
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct VerificationResult {
71    pub is_valid: bool,
72    pub key_id: String,
73    pub algorithm: SignatureAlgorithm,
74    pub signed_at: String,
75    pub signer_fingerprint: String,
76    pub warning: Option<String>,
77}
78
79/// Trust level for plugins based on signatures
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
81#[serde(rename_all = "lowercase")]
82pub enum TrustLevel {
83    Unverified,
84    Unreviewed,
85    Reviewed,
86    Trusted,
87    Official,
88}
89
90impl TrustLevel {
91    pub fn description(&self) -> &'static str {
92        match self {
93            TrustLevel::Unverified => "No signature or signature not verified",
94            TrustLevel::Unreviewed => "Signed but not reviewed",
95            TrustLevel::Reviewed => "Signed and reviewed by community",
96            TrustLevel::Trusted => "Signed by trusted publisher",
97            TrustLevel::Official => "Official Skylet plugin",
98        }
99    }
100}
101
102/// Plugin signature manager
103pub struct SignatureManager {
104    keys: HashMap<String, KeyInfo>,
105    trusted_keys: Vec<String>,
106}
107
108impl SignatureManager {
109    /// Create a new signature manager
110    pub fn new() -> Self {
111        Self {
112            keys: HashMap::new(),
113            trusted_keys: Vec::new(),
114        }
115    }
116
117    /// Register a key
118    pub fn register_key(&mut self, key: KeyInfo) -> Result<()> {
119        if self.keys.contains_key(&key.key_id) {
120            return Err(anyhow!("Key {} already registered", key.key_id));
121        }
122        self.keys.insert(key.key_id.clone(), key);
123        Ok(())
124    }
125
126    /// Add a key to trusted keys
127    pub fn trust_key(&mut self, key_id: &str) -> Result<()> {
128        if !self.keys.contains_key(key_id) {
129            return Err(anyhow!("Key {} not found", key_id));
130        }
131        if !self.trusted_keys.contains(&key_id.to_string()) {
132            self.trusted_keys.push(key_id.to_string());
133        }
134        Ok(())
135    }
136
137    /// Check if a key is trusted
138    pub fn is_trusted(&self, key_id: &str) -> bool {
139        self.trusted_keys.contains(&key_id.to_string())
140    }
141
142    /// Get a registered key
143    pub fn get_key(&self, key_id: &str) -> Option<&KeyInfo> {
144        self.keys.get(key_id)
145    }
146
147    /// Get all registered keys
148    pub fn list_keys(&self) -> Vec<&KeyInfo> {
149        self.keys.values().collect()
150    }
151
152    /// Verify signature against the provided payload
153    pub fn verify_signature(
154        &self,
155        signature: &PluginSignature,
156        payload: &[u8],
157    ) -> Result<VerificationResult> {
158        let key = self
159            .get_key(&signature.key_id)
160            .ok_or_else(|| anyhow!("Signing key {} not found", signature.key_id))?;
161
162        if key.is_private {
163            return Err(anyhow!("Cannot verify with private key"));
164        }
165
166        // Compute fingerprint from key material
167        let fingerprint = compute_fingerprint(&key.key_material);
168
169        // Decode the base64 signature
170        use base64::Engine;
171        let engine = base64::engine::general_purpose::STANDARD;
172        let signature_bytes = engine
173            .decode(&signature.signature)
174            .map_err(|e| anyhow!("Failed to decode signature: {}", e))?;
175
176        // Verify based on algorithm
177        let is_valid = match key.algorithm {
178            SignatureAlgorithm::Ed25519 => {
179                use ed25519_dalek::{Signature, Verifier, VerifyingKey};
180
181                // Parse the public key (v2.x API)
182                if key.key_material.len() != 32 {
183                    return Err(anyhow!("Ed25519 key must be 32 bytes"));
184                }
185                let mut key_bytes = [0u8; 32];
186                key_bytes.copy_from_slice(&key.key_material);
187
188                let public_key = VerifyingKey::from_bytes(&key_bytes)
189                    .map_err(|e| anyhow!("Invalid Ed25519 public key: {}", e))?;
190
191                // Parse the signature (must be exactly 64 bytes)
192                if signature_bytes.len() != 64 {
193                    return Err(anyhow!("Ed25519 signature must be 64 bytes"));
194                }
195                let mut sig_bytes = [0u8; 64];
196                sig_bytes.copy_from_slice(&signature_bytes);
197                let signature = Signature::from_bytes(&sig_bytes);
198
199                // Verify the signature
200                public_key.verify(payload, &signature).is_ok()
201            }
202            SignatureAlgorithm::Rsa2048 | SignatureAlgorithm::Rsa4096 => {
203                // For RSA, we'd need the ring crate - for now, do a basic check
204                // In production, use ring::signature::RSA_PUBLIC_KEY_SIZES
205                if key.key_material.len() < 256 {
206                    return Err(anyhow!("RSA key material too small"));
207                }
208                // RSA signature verification would go here
209                // For now, reject as unimplemented
210                return Err(anyhow!("RSA signature verification not yet implemented"));
211            }
212        };
213
214        let warning = if !self.is_trusted(&signature.key_id) {
215            Some("Signature from untrusted key".to_string())
216        } else {
217            None
218        };
219
220        Ok(VerificationResult {
221            is_valid,
222            key_id: signature.key_id.clone(),
223            algorithm: signature.algorithm,
224            signed_at: signature.signed_at.clone(),
225            signer_fingerprint: fingerprint,
226            warning,
227        })
228    }
229
230    /// Determine trust level for a plugin based on signatures
231    pub fn assess_trust_level(&self, signatures: &[PluginSignature]) -> TrustLevel {
232        if signatures.is_empty() {
233            return TrustLevel::Unverified;
234        }
235
236        let trusted_sigs = signatures
237            .iter()
238            .filter(|sig| self.is_trusted(&sig.key_id))
239            .count();
240
241        if trusted_sigs > 0 {
242            TrustLevel::Trusted
243        } else {
244            TrustLevel::Unreviewed
245        }
246    }
247
248    /// Export public key in PEM format
249    pub fn export_public_key(&self, key_id: &str) -> Result<String> {
250        let key = self
251            .get_key(key_id)
252            .ok_or_else(|| anyhow!("Key {} not found", key_id))?;
253
254        if key.is_private {
255            return Err(anyhow!("Cannot export private key as public"));
256        }
257
258        // Base64 encode the key material for export using the new API
259        use base64::Engine;
260        let engine = base64::engine::general_purpose::STANDARD;
261        let encoded = engine.encode(&key.key_material);
262        Ok(format!(
263            "-----BEGIN {} PUBLIC KEY-----\n{}\n-----END {} PUBLIC KEY-----",
264            key.algorithm.as_str().to_uppercase(),
265            encoded,
266            key.algorithm.as_str().to_uppercase()
267        ))
268    }
269
270    /// Import a public key from PEM format
271    pub fn import_public_key(&mut self, pem_data: &str, key_id: String) -> Result<()> {
272        // Parse PEM header
273        let lines: Vec<&str> = pem_data.lines().collect();
274        if lines.len() < 3 {
275            return Err(anyhow!("Invalid PEM format"));
276        }
277
278        // Extract algorithm from header
279        let header = lines[0];
280        let algorithm = if header.contains("ED25519") {
281            SignatureAlgorithm::Ed25519
282        } else if header.contains("RSA") {
283            if header.contains("RSA-4096") {
284                SignatureAlgorithm::Rsa4096
285            } else {
286                SignatureAlgorithm::Rsa2048
287            }
288        } else {
289            return Err(anyhow!("Unknown algorithm in PEM header"));
290        };
291
292        // Extract and decode key material using the new API
293        use base64::Engine;
294        let engine = base64::engine::general_purpose::STANDARD;
295        let key_data = lines[1..lines.len() - 1].join("");
296        let key_material = engine.decode(&key_data)?;
297
298        let fingerprint = compute_fingerprint(&key_material);
299
300        let key_info = KeyInfo {
301            key_id: key_id.clone(),
302            algorithm,
303            created_at: chrono::Utc::now().to_rfc3339(),
304            expires_at: None,
305            key_material,
306            is_private: false,
307            fingerprint,
308        };
309
310        self.register_key(key_info)
311    }
312}
313
314impl Default for SignatureManager {
315    fn default() -> Self {
316        Self::new()
317    }
318}
319
320/// Compute a fingerprint for a key
321fn compute_fingerprint(key_material: &[u8]) -> String {
322    use sha2::{Digest, Sha256};
323    let mut hasher = Sha256::new();
324    hasher.update(key_material);
325    let result = hasher.finalize();
326    hex::encode(&result[..16]) // First 16 bytes for display
327}
328
329/// Plugin audit log for signature verification
330#[derive(Debug, Clone, Serialize, Deserialize)]
331pub struct SignatureAuditLog {
332    pub plugin_id: String,
333    pub plugin_version: String,
334    pub signatures: Vec<PluginSignature>,
335    pub verified_at: String,
336    pub verification_results: Vec<VerificationResult>,
337    pub trust_level: TrustLevel,
338}
339
340#[cfg(test)]
341mod tests {
342    use super::*;
343
344    #[test]
345    fn test_signature_algorithm_to_str() {
346        assert_eq!(SignatureAlgorithm::Ed25519.as_str(), "ed25519");
347        assert_eq!(SignatureAlgorithm::Rsa2048.as_str(), "rsa-2048");
348        assert_eq!(SignatureAlgorithm::Rsa4096.as_str(), "rsa-4096");
349    }
350
351    #[test]
352    fn test_signature_algorithm_try_parse() {
353        assert_eq!(
354            SignatureAlgorithm::try_parse("ed25519"),
355            Some(SignatureAlgorithm::Ed25519)
356        );
357        assert_eq!(
358            SignatureAlgorithm::try_parse("rsa-2048"),
359            Some(SignatureAlgorithm::Rsa2048)
360        );
361        assert_eq!(SignatureAlgorithm::try_parse("invalid"), None);
362    }
363
364    #[test]
365    fn test_signature_manager_creation() {
366        let manager = SignatureManager::new();
367        assert_eq!(manager.list_keys().len(), 0);
368    }
369
370    #[test]
371    fn test_key_registration() -> Result<()> {
372        let mut manager = SignatureManager::new();
373        let key = KeyInfo {
374            key_id: "test-key-1".to_string(),
375            algorithm: SignatureAlgorithm::Ed25519,
376            created_at: "2026-02-10T00:00:00Z".to_string(),
377            expires_at: None,
378            key_material: vec![1, 2, 3, 4],
379            is_private: false,
380            fingerprint: "abcd1234".to_string(),
381        };
382
383        manager.register_key(key)?;
384        assert_eq!(manager.list_keys().len(), 1);
385        Ok(())
386    }
387
388    #[test]
389    fn test_duplicate_key_registration() -> Result<()> {
390        let mut manager = SignatureManager::new();
391        let key = KeyInfo {
392            key_id: "test-key-1".to_string(),
393            algorithm: SignatureAlgorithm::Ed25519,
394            created_at: "2026-02-10T00:00:00Z".to_string(),
395            expires_at: None,
396            key_material: vec![1, 2, 3, 4],
397            is_private: false,
398            fingerprint: "abcd1234".to_string(),
399        };
400
401        manager.register_key(key.clone())?;
402        assert!(manager.register_key(key).is_err());
403        Ok(())
404    }
405
406    #[test]
407    fn test_key_trust_management() -> Result<()> {
408        let mut manager = SignatureManager::new();
409        let key = KeyInfo {
410            key_id: "test-key-1".to_string(),
411            algorithm: SignatureAlgorithm::Ed25519,
412            created_at: "2026-02-10T00:00:00Z".to_string(),
413            expires_at: None,
414            key_material: vec![1, 2, 3, 4],
415            is_private: false,
416            fingerprint: "abcd1234".to_string(),
417        };
418
419        manager.register_key(key)?;
420        assert!(!manager.is_trusted("test-key-1"));
421
422        manager.trust_key("test-key-1")?;
423        assert!(manager.is_trusted("test-key-1"));
424
425        Ok(())
426    }
427
428    #[test]
429    fn test_trust_level_assessment() {
430        let manager = SignatureManager::new();
431
432        // Test unverified
433        let trust = manager.assess_trust_level(&[]);
434        assert_eq!(trust, TrustLevel::Unverified);
435
436        // Test with untrusted signatures
437        let sig = PluginSignature {
438            key_id: "unknown-key".to_string(),
439            algorithm: SignatureAlgorithm::Ed25519,
440            signature: "signature".to_string(),
441            signed_at: "2026-02-10T00:00:00Z".to_string(),
442            payload_hash: "hash".to_string(),
443        };
444
445        let trust = manager.assess_trust_level(&[sig]);
446        assert_eq!(trust, TrustLevel::Unreviewed);
447    }
448
449    #[test]
450    fn test_trust_level_descriptions() {
451        assert!(!TrustLevel::Unverified.description().is_empty());
452        assert!(!TrustLevel::Trusted.description().is_empty());
453        assert!(!TrustLevel::Official.description().is_empty());
454    }
455
456    #[test]
457    fn test_trust_level_ordering() {
458        assert!(TrustLevel::Unverified < TrustLevel::Trusted);
459        assert!(TrustLevel::Trusted < TrustLevel::Official);
460    }
461
462    #[test]
463    fn test_key_info_serialization() -> Result<()> {
464        let key = KeyInfo {
465            key_id: "test-key".to_string(),
466            algorithm: SignatureAlgorithm::Ed25519,
467            created_at: "2026-02-10T00:00:00Z".to_string(),
468            expires_at: Some("2027-02-10T00:00:00Z".to_string()),
469            key_material: vec![1, 2, 3],
470            is_private: false,
471            fingerprint: "abc123".to_string(),
472        };
473
474        let json = serde_json::to_string(&key)?;
475        let deserialized: KeyInfo = serde_json::from_str(&json)?;
476        assert_eq!(key.key_id, deserialized.key_id);
477        Ok(())
478    }
479
480    #[test]
481    fn test_plugin_signature_serialization() -> Result<()> {
482        let sig = PluginSignature {
483            key_id: "key-1".to_string(),
484            algorithm: SignatureAlgorithm::Rsa2048,
485            signature: "sig-data".to_string(),
486            signed_at: "2026-02-10T00:00:00Z".to_string(),
487            payload_hash: "hash123".to_string(),
488        };
489
490        let json = serde_json::to_string(&sig)?;
491        let deserialized: PluginSignature = serde_json::from_str(&json)?;
492        assert_eq!(sig.key_id, deserialized.key_id);
493        Ok(())
494    }
495
496    #[test]
497    fn test_compute_fingerprint() {
498        let data1 = vec![1, 2, 3, 4];
499        let data2 = vec![1, 2, 3, 4];
500        let data3 = vec![1, 2, 3, 5];
501
502        let fp1 = compute_fingerprint(&data1);
503        let fp2 = compute_fingerprint(&data2);
504        let fp3 = compute_fingerprint(&data3);
505
506        assert_eq!(fp1, fp2);
507        assert_ne!(fp1, fp3);
508    }
509}