Skip to main content

licenz_core/crypto/
mod.rs

1//! Pluggable cryptographic architecture using the strategy pattern.
2//!
3//! This module provides a flexible crypto system that allows switching between
4//! different signature algorithms (RSA-SHA256, Ed25519, and post-quantum algorithms).
5//!
6//! # Classical Algorithms
7//!
8//! - **RSA-SHA256**: Traditional RSA signatures with SHA-256 hashing
9//! - **Ed25519**: Fast, modern elliptic curve signatures
10//!
11//! # Post-Quantum Algorithms (feature: `post-quantum`)
12//!
13//! - **ML-DSA-65**: NIST FIPS 204 lattice-based signatures
14//! - **ML-KEM-768**: NIST FIPS 203 key encapsulation mechanism
15//!
16//! # Hybrid Algorithms (feature: `post-quantum`)
17//!
18//! - **Hybrid-RSA-ML-DSA-65**: RSA + ML-DSA-65 dual signatures
19//! - **Hybrid-Ed25519-ML-DSA-65**: Ed25519 + ML-DSA-65 dual signatures
20//!
21//! Hybrid modes require BOTH signatures to verify, providing security even if
22//! one algorithm is compromised (defense in depth).
23//!
24//! # Example
25//!
26//! ```rust,no_run
27//! use licenz_core::crypto::{SignatureAlgorithm, CryptoRegistry};
28//!
29//! // Get an algorithm by ID
30//! let algorithm = CryptoRegistry::get_signature_algorithm("RSA-SHA256").unwrap();
31//! println!("Using algorithm: {}", algorithm.algorithm_id());
32//!
33//! // List all supported algorithms
34//! for alg_id in CryptoRegistry::supported_signature_algorithms() {
35//!     println!("Supported: {}", alg_id);
36//! }
37//! ```
38
39pub mod ed25519;
40pub mod rsa;
41
42// Post-quantum cryptography modules (feature-gated)
43#[cfg(feature = "post-quantum")]
44pub mod hybrid;
45#[cfg(feature = "post-quantum")]
46pub mod ml_dsa;
47#[cfg(feature = "post-quantum")]
48pub mod ml_kem;
49
50// Post-quantum tests
51#[cfg(all(test, feature = "post-quantum"))]
52mod pq_tests;
53
54use crate::error::{LicenseError, Result};
55use std::collections::HashMap;
56use std::sync::LazyLock;
57
58/// Algorithm identifiers for signature algorithms
59pub mod algorithm_ids {
60    /// RSA with SHA-256 (PKCS#1 v1.5)
61    pub const RSA_SHA256: &str = "RSA-SHA256";
62    /// Ed25519 signature scheme
63    pub const ED25519: &str = "Ed25519";
64
65    // Post-quantum algorithm identifiers (available when feature enabled)
66
67    /// ML-DSA-65 (FIPS 204) post-quantum signature scheme
68    #[cfg(feature = "post-quantum")]
69    pub const ML_DSA_65: &str = "ML-DSA-65";
70
71    /// ML-KEM-768 (FIPS 203) post-quantum key encapsulation
72    /// Note: This is a KEM, not a signature algorithm
73    #[cfg(feature = "post-quantum")]
74    pub const ML_KEM_768: &str = "ML-KEM-768";
75
76    /// Hybrid RSA + ML-DSA-65 dual signature scheme
77    #[cfg(feature = "post-quantum")]
78    pub const HYBRID_RSA_ML_DSA_65: &str = "Hybrid-RSA-ML-DSA-65";
79
80    /// Hybrid Ed25519 + ML-DSA-65 dual signature scheme
81    #[cfg(feature = "post-quantum")]
82    pub const HYBRID_ED25519_ML_DSA_65: &str = "Hybrid-Ed25519-ML-DSA-65";
83}
84
85/// Trait for signature algorithms (strategy pattern)
86///
87/// This trait defines the interface for cryptographic signature operations,
88/// allowing different algorithms to be plugged in interchangeably.
89pub trait SignatureAlgorithm: Send + Sync {
90    /// Returns the unique identifier for this algorithm (e.g., "RSA-SHA256", "Ed25519")
91    fn algorithm_id(&self) -> &'static str;
92
93    /// Sign data with the private key
94    fn sign(&self, data: &[u8], private_key_pem: &str) -> Result<Vec<u8>>;
95
96    /// Verify a signature against data and public key
97    fn verify(&self, data: &[u8], signature: &[u8], public_key_pem: &str) -> Result<()>;
98
99    /// Generate a new key pair
100    ///
101    /// # Returns
102    /// A tuple of (private_key_pem, public_key_pem)
103    fn generate_keypair(&self) -> Result<(String, String)>;
104
105    /// Extract the public key from a private key
106    fn extract_public_key(&self, private_key_pem: &str) -> Result<String>;
107}
108
109/// Trait for symmetric encryption algorithms
110pub trait EncryptionAlgorithm: Send + Sync {
111    /// Returns the unique identifier for this algorithm (e.g., "AES-256-GCM")
112    fn algorithm_id(&self) -> &'static str;
113
114    /// Encrypt data with the given key
115    fn encrypt(&self, data: &[u8], key: &[u8]) -> Result<Vec<u8>>;
116
117    /// Decrypt data with the given key
118    fn decrypt(&self, data: &[u8], key: &[u8]) -> Result<Vec<u8>>;
119
120    /// Returns the required key size in bytes
121    fn key_size(&self) -> usize;
122
123    /// Returns the nonce/IV size in bytes
124    fn nonce_size(&self) -> usize;
125}
126
127/// A boxed signature algorithm for dynamic dispatch
128pub type BoxedSignatureAlgorithm = Box<dyn SignatureAlgorithm>;
129
130/// A boxed encryption algorithm for dynamic dispatch
131pub type BoxedEncryptionAlgorithm = Box<dyn EncryptionAlgorithm>;
132
133/// Registry for cryptographic algorithms
134///
135/// Provides lookup functionality to get algorithms by their identifier.
136/// This enables runtime selection of algorithms based on license metadata.
137pub struct CryptoRegistry;
138
139/// Static registry of signature algorithms
140static SIGNATURE_ALGORITHMS: LazyLock<HashMap<&'static str, BoxedSignatureAlgorithm>> =
141    LazyLock::new(|| {
142        let mut map: HashMap<&'static str, BoxedSignatureAlgorithm> = HashMap::new();
143
144        // Classical algorithms (always available)
145        map.insert(algorithm_ids::RSA_SHA256, Box::new(rsa::RsaSigner::new()));
146        map.insert(
147            algorithm_ids::ED25519,
148            Box::new(ed25519::Ed25519Signer::new()),
149        );
150
151        // Post-quantum algorithms (feature-gated)
152        #[cfg(feature = "post-quantum")]
153        {
154            map.insert(
155                algorithm_ids::ML_DSA_65,
156                Box::new(ml_dsa::MlDsa65Signer::new()),
157            );
158            map.insert(
159                algorithm_ids::HYBRID_RSA_ML_DSA_65,
160                Box::new(hybrid::HybridRsaMlDsaSigner::new()),
161            );
162            map.insert(
163                algorithm_ids::HYBRID_ED25519_ML_DSA_65,
164                Box::new(hybrid::HybridEd25519MlDsaSigner::new()),
165            );
166        }
167
168        map
169    });
170
171impl CryptoRegistry {
172    /// Get a signature algorithm by its identifier
173    pub fn get_signature_algorithm(algorithm_id: &str) -> Result<&'static dyn SignatureAlgorithm> {
174        SIGNATURE_ALGORITHMS
175            .get(algorithm_id)
176            .map(|boxed| boxed.as_ref())
177            .ok_or_else(|| {
178                LicenseError::InvalidKeyFormat(format!(
179                    "Unknown signature algorithm: {}. Supported: {:?}",
180                    algorithm_id,
181                    Self::supported_signature_algorithms()
182                ))
183            })
184    }
185
186    /// Get a list of all supported signature algorithm IDs
187    pub fn supported_signature_algorithms() -> Vec<&'static str> {
188        SIGNATURE_ALGORITHMS.keys().copied().collect()
189    }
190
191    /// Check if a signature algorithm is supported
192    pub fn is_signature_algorithm_supported(algorithm_id: &str) -> bool {
193        SIGNATURE_ALGORITHMS.contains_key(algorithm_id)
194    }
195
196    /// Get the default signature algorithm (RSA-SHA256 for backward compatibility)
197    pub fn default_signature_algorithm() -> &'static dyn SignatureAlgorithm {
198        SIGNATURE_ALGORITHMS
199            .get(algorithm_ids::RSA_SHA256)
200            .map(|boxed| boxed.as_ref())
201            .expect("RSA-SHA256 should always be available")
202    }
203
204    /// Check if post-quantum algorithms are available
205    pub fn is_post_quantum_available() -> bool {
206        #[cfg(feature = "post-quantum")]
207        {
208            true
209        }
210        #[cfg(not(feature = "post-quantum"))]
211        {
212            false
213        }
214    }
215
216    /// Get a list of post-quantum signature algorithm IDs
217    #[cfg(feature = "post-quantum")]
218    pub fn post_quantum_signature_algorithms() -> Vec<&'static str> {
219        vec![
220            algorithm_ids::ML_DSA_65,
221            algorithm_ids::HYBRID_RSA_ML_DSA_65,
222            algorithm_ids::HYBRID_ED25519_ML_DSA_65,
223        ]
224    }
225
226    /// Get a list of post-quantum signature algorithm IDs
227    #[cfg(not(feature = "post-quantum"))]
228    pub fn post_quantum_signature_algorithms() -> Vec<&'static str> {
229        vec![]
230    }
231
232    /// Get a list of classical (non-PQ) signature algorithm IDs
233    pub fn classical_signature_algorithms() -> Vec<&'static str> {
234        vec![algorithm_ids::RSA_SHA256, algorithm_ids::ED25519]
235    }
236
237    /// Get a list of hybrid signature algorithm IDs
238    #[cfg(feature = "post-quantum")]
239    pub fn hybrid_signature_algorithms() -> Vec<&'static str> {
240        vec![
241            algorithm_ids::HYBRID_RSA_ML_DSA_65,
242            algorithm_ids::HYBRID_ED25519_ML_DSA_65,
243        ]
244    }
245
246    /// Get a list of hybrid signature algorithm IDs
247    #[cfg(not(feature = "post-quantum"))]
248    pub fn hybrid_signature_algorithms() -> Vec<&'static str> {
249        vec![]
250    }
251
252    /// Get the recommended algorithm for maximum security
253    pub fn recommended_algorithm() -> &'static dyn SignatureAlgorithm {
254        #[cfg(feature = "post-quantum")]
255        {
256            SIGNATURE_ALGORITHMS
257                .get(algorithm_ids::HYBRID_ED25519_ML_DSA_65)
258                .map(|boxed| boxed.as_ref())
259                .expect("Hybrid-Ed25519-ML-DSA-65 should be available with post-quantum feature")
260        }
261        #[cfg(not(feature = "post-quantum"))]
262        {
263            SIGNATURE_ALGORITHMS
264                .get(algorithm_ids::ED25519)
265                .map(|boxed| boxed.as_ref())
266                .expect("Ed25519 should always be available")
267        }
268    }
269}
270
271// Re-export CryptoKeyPair from keys module (single source of truth)
272pub use crate::keys::CryptoKeyPair;
273
274#[cfg(test)]
275mod tests {
276    use super::*;
277
278    #[test]
279    fn test_registry_get_rsa() {
280        let alg = CryptoRegistry::get_signature_algorithm(algorithm_ids::RSA_SHA256).unwrap();
281        assert_eq!(alg.algorithm_id(), algorithm_ids::RSA_SHA256);
282    }
283
284    #[test]
285    fn test_registry_get_ed25519() {
286        let alg = CryptoRegistry::get_signature_algorithm(algorithm_ids::ED25519).unwrap();
287        assert_eq!(alg.algorithm_id(), algorithm_ids::ED25519);
288    }
289
290    #[test]
291    fn test_registry_unknown_algorithm() {
292        let result = CryptoRegistry::get_signature_algorithm("UNKNOWN-ALG");
293        assert!(result.is_err());
294    }
295
296    #[test]
297    fn test_supported_algorithms() {
298        let supported = CryptoRegistry::supported_signature_algorithms();
299        assert!(supported.contains(&algorithm_ids::RSA_SHA256));
300        assert!(supported.contains(&algorithm_ids::ED25519));
301    }
302
303    #[test]
304    fn test_default_algorithm() {
305        let alg = CryptoRegistry::default_signature_algorithm();
306        assert_eq!(alg.algorithm_id(), algorithm_ids::RSA_SHA256);
307    }
308
309    #[test]
310    fn test_crypto_keypair_rsa() {
311        let keypair = CryptoKeyPair::generate(algorithm_ids::RSA_SHA256).unwrap();
312        assert_eq!(keypair.algorithm_id, algorithm_ids::RSA_SHA256);
313        assert!(keypair.private_key_pem().contains("PRIVATE KEY"));
314        assert!(keypair.public_key_pem.contains("PUBLIC KEY"));
315
316        let data = b"test message";
317        let signature = keypair.sign(data).unwrap();
318        assert!(keypair.verify(data, &signature).is_ok());
319    }
320
321    #[test]
322    fn test_crypto_keypair_ed25519() {
323        let keypair = CryptoKeyPair::generate(algorithm_ids::ED25519).unwrap();
324        assert_eq!(keypair.algorithm_id, algorithm_ids::ED25519);
325        assert!(keypair.private_key_pem().contains("PRIVATE KEY"));
326        assert!(keypair.public_key_pem.contains("PUBLIC KEY"));
327
328        let data = b"test message";
329        let signature = keypair.sign(data).unwrap();
330        assert!(keypair.verify(data, &signature).is_ok());
331    }
332}