Skip to main content

licenz_core/
generator.rs

1//! License generation functionality (server-side)
2//!
3//! This module provides license generation using the pluggable cryptographic
4//! architecture. It supports multiple signature algorithms (RSA-SHA256, Ed25519, etc.).
5//!
6//! # Example
7//!
8//! ```rust,ignore
9//! use licenz_core::generator::{CryptoGenerator, LicenseGenerator};
10//! use licenz_core::crypto::algorithm_ids;
11//!
12//! // New algorithm-agnostic generator (recommended)
13//! let generator = CryptoGenerator::new(private_key_pem, algorithm_ids::ED25519);
14//! let license = generator.generate(license_data).unwrap();
15//!
16//! // Legacy RSA-only generator (backward compatible)
17//! let generator = LicenseGenerator::new(rsa_private_key);
18//! ```
19
20use crate::crypto::CryptoRegistry;
21use crate::error::{LicenseError, Result};
22use crate::keys::parse_private_key;
23use crate::license::{LicenseData, SignedLicense, BINARY_MAGIC, BINARY_VERSION};
24use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
25use rsa::pkcs1v15::SigningKey;
26use rsa::signature::{RandomizedSigner, SignatureEncoding};
27use rsa::RsaPrivateKey;
28use sha2::Sha256;
29use std::io::Write;
30use std::path::Path;
31use zeroize::Zeroizing;
32
33/// License generator for creating and signing licenses
34pub struct LicenseGenerator {
35    private_key: RsaPrivateKey,
36}
37
38impl LicenseGenerator {
39    /// Create a new license generator with a private key
40    pub fn new(private_key: RsaPrivateKey) -> Self {
41        Self { private_key }
42    }
43
44    /// Create a new license generator from a PEM string
45    pub fn from_pem(pem: &str) -> Result<Self> {
46        let private_key = parse_private_key(pem)?;
47        Ok(Self::new(private_key))
48    }
49
50    /// Create a new license generator from a PEM file
51    pub fn from_pem_file(path: &Path) -> Result<Self> {
52        let pem = std::fs::read_to_string(path)?;
53        Self::from_pem(&pem)
54    }
55
56    /// Generate a signed license from license data
57    pub fn generate(&self, data: LicenseData) -> Result<SignedLicense> {
58        // Serialize the data to sign
59        let data_bytes = serde_json::to_vec(&data)
60            .map_err(|e| LicenseError::SerializationError(e.to_string()))?;
61
62        // Sign the data
63        let signature = self.sign(&data_bytes)?;
64
65        Ok(SignedLicense {
66            data,
67            signature: BASE64.encode(&signature),
68            algorithm: "RSA-SHA256".to_string(),
69        })
70    }
71
72    /// Sign arbitrary data
73    fn sign(&self, data: &[u8]) -> Result<Vec<u8>> {
74        let signing_key = SigningKey::<Sha256>::new(self.private_key.clone());
75        let mut rng = rand::rngs::OsRng;
76
77        let signature = signing_key.sign_with_rng(&mut rng, data);
78
79        Ok(signature.to_bytes().to_vec())
80    }
81
82    /// Export a signed license to binary format
83    pub fn export_binary(&self, license: &SignedLicense) -> Result<Vec<u8>> {
84        let mut output = Vec::new();
85
86        // Write magic header
87        output.write_all(BINARY_MAGIC)?;
88
89        // Write version
90        output.write_all(&[BINARY_VERSION])?;
91
92        // Serialize the license as JSON (more robust than bincode for complex types)
93        let encoded = serde_json::to_vec(license)
94            .map_err(|e| LicenseError::SerializationError(e.to_string()))?;
95
96        // Write length as u32 little-endian
97        let len = encoded.len() as u32;
98        output.write_all(&len.to_le_bytes())?;
99
100        // Write the encoded license
101        output.write_all(&encoded)?;
102
103        Ok(output)
104    }
105
106    /// Export a signed license to JSON format (legacy)
107    pub fn export_json(&self, license: &SignedLicense) -> Result<String> {
108        serde_json::to_string_pretty(license)
109            .map_err(|e| LicenseError::SerializationError(e.to_string()))
110    }
111
112    /// Save a license to a binary file
113    pub fn save_binary(&self, license: &SignedLicense, path: &Path) -> Result<()> {
114        let binary = self.export_binary(license)?;
115        std::fs::write(path, binary)?;
116        Ok(())
117    }
118
119    /// Save a license to a JSON file (legacy)
120    pub fn save_json(&self, license: &SignedLicense, path: &Path) -> Result<()> {
121        let json = self.export_json(license)?;
122        std::fs::write(path, json)?;
123        Ok(())
124    }
125}
126
127/// Algorithm-agnostic license generator that supports multiple signature algorithms
128///
129/// This generator uses the pluggable cryptographic architecture to support
130/// different signature algorithms (RSA-SHA256, Ed25519, etc.).
131///
132/// # Example
133///
134/// ```rust,ignore
135/// use licenz_core::generator::CryptoGenerator;
136/// use licenz_core::crypto::algorithm_ids;
137/// use licenz_core::keys::CryptoKeyPair;
138///
139/// // Generate keys
140/// let keypair = CryptoKeyPair::generate(algorithm_ids::ED25519).unwrap();
141///
142/// // Create generator
143/// let generator = CryptoGenerator::new(keypair.private_key_pem(), algorithm_ids::ED25519);
144///
145/// // Generate license
146/// let license = generator.generate(license_data).unwrap();
147/// assert_eq!(license.algorithm, "Ed25519");
148/// ```
149pub struct CryptoGenerator {
150    /// Private key in PEM format (zeroized on drop)
151    private_key_pem: Zeroizing<String>,
152    /// Algorithm identifier
153    algorithm_id: String,
154}
155
156impl std::fmt::Debug for CryptoGenerator {
157    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158        f.debug_struct("CryptoGenerator")
159            .field("private_key_pem", &"[REDACTED]")
160            .field("algorithm_id", &self.algorithm_id)
161            .finish()
162    }
163}
164
165impl CryptoGenerator {
166    /// Create a new crypto generator with the specified algorithm
167    ///
168    /// # Arguments
169    /// * `private_key_pem` - The private key in PEM format
170    /// * `algorithm_id` - The algorithm to use (e.g., "RSA-SHA256", "Ed25519")
171    pub fn new(private_key_pem: &str, algorithm_id: &str) -> Self {
172        Self {
173            private_key_pem: Zeroizing::new(private_key_pem.to_string()),
174            algorithm_id: algorithm_id.to_string(),
175        }
176    }
177
178    /// Create a generator from a PEM file
179    pub fn from_pem_file(path: &Path, algorithm_id: &str) -> Result<Self> {
180        let pem = std::fs::read_to_string(path)?;
181        Ok(Self::new(&pem, algorithm_id))
182    }
183
184    /// Create a generator from a CryptoKeyPair
185    pub fn from_keypair(keypair: &crate::keys::CryptoKeyPair) -> Self {
186        Self::new(keypair.private_key_pem(), &keypair.algorithm_id)
187    }
188
189    /// Get the algorithm ID
190    pub fn algorithm_id(&self) -> &str {
191        &self.algorithm_id
192    }
193
194    /// Generate a signed license from license data
195    pub fn generate(&self, data: LicenseData) -> Result<SignedLicense> {
196        // Serialize the data to sign
197        let data_bytes = serde_json::to_vec(&data)
198            .map_err(|e| LicenseError::SerializationError(e.to_string()))?;
199
200        // Get the algorithm and sign
201        let algorithm = CryptoRegistry::get_signature_algorithm(&self.algorithm_id)?;
202        let signature = algorithm.sign(&data_bytes, &self.private_key_pem)?;
203
204        Ok(SignedLicense {
205            data,
206            signature: BASE64.encode(&signature),
207            algorithm: self.algorithm_id.clone(),
208        })
209    }
210
211    /// Export a signed license to binary format
212    pub fn export_binary(&self, license: &SignedLicense) -> Result<Vec<u8>> {
213        let mut output = Vec::new();
214
215        // Write magic header
216        output.write_all(BINARY_MAGIC)?;
217
218        // Write version
219        output.write_all(&[BINARY_VERSION])?;
220
221        // Serialize the license as JSON
222        let encoded = serde_json::to_vec(license)
223            .map_err(|e| LicenseError::SerializationError(e.to_string()))?;
224
225        // Write length as u32 little-endian
226        let len = encoded.len() as u32;
227        output.write_all(&len.to_le_bytes())?;
228
229        // Write the encoded license
230        output.write_all(&encoded)?;
231
232        Ok(output)
233    }
234
235    /// Export a signed license to JSON format
236    pub fn export_json(&self, license: &SignedLicense) -> Result<String> {
237        serde_json::to_string_pretty(license)
238            .map_err(|e| LicenseError::SerializationError(e.to_string()))
239    }
240
241    /// Save a license to a binary file
242    pub fn save_binary(&self, license: &SignedLicense, path: &Path) -> Result<()> {
243        let binary = self.export_binary(license)?;
244        std::fs::write(path, binary)?;
245        Ok(())
246    }
247
248    /// Save a license to a JSON file
249    pub fn save_json(&self, license: &SignedLicense, path: &Path) -> Result<()> {
250        let json = self.export_json(license)?;
251        std::fs::write(path, json)?;
252        Ok(())
253    }
254}
255
256#[cfg(test)]
257mod tests {
258    use super::*;
259    use crate::algorithm_ids;
260    use crate::keys::KeyPair;
261    use crate::keys::KeySize;
262
263    #[test]
264    fn test_license_generation() {
265        let keypair = KeyPair::generate(KeySize::Bits2048).unwrap();
266        let generator = LicenseGenerator::new(keypair.into_private_key());
267
268        let data = LicenseData::builder()
269            .id("TEST-001")
270            .serial("SN-12345")
271            .customer_id("CUST-001")
272            .product_id("PROD-001")
273            .valid_days(365)
274            .feature("basic")
275            .build()
276            .unwrap();
277
278        let signed = generator.generate(data).unwrap();
279
280        assert!(!signed.signature.is_empty());
281        assert_eq!(signed.algorithm, "RSA-SHA256");
282    }
283
284    #[test]
285    fn test_binary_export() {
286        let keypair = KeyPair::generate(KeySize::Bits2048).unwrap();
287        let generator = LicenseGenerator::new(keypair.into_private_key());
288
289        let data = LicenseData::builder()
290            .id("TEST-001")
291            .serial("SN-12345")
292            .customer_id("CUST-001")
293            .product_id("PROD-001")
294            .valid_days(365)
295            .build()
296            .unwrap();
297
298        let signed = generator.generate(data).unwrap();
299        let binary = generator.export_binary(&signed).unwrap();
300
301        // Check magic header
302        assert_eq!(&binary[0..4], BINARY_MAGIC);
303        assert_eq!(binary[4], BINARY_VERSION);
304    }
305
306    #[test]
307    fn test_crypto_generator_rsa() {
308        use crate::keys::CryptoKeyPair;
309
310        let keypair = CryptoKeyPair::generate(algorithm_ids::RSA_SHA256).unwrap();
311        let generator = CryptoGenerator::from_keypair(&keypair);
312
313        let data = LicenseData::builder()
314            .id("CRYPTO-RSA-001")
315            .serial("SN-CRYPTO-RSA")
316            .customer_id("CUST-001")
317            .product_id("PROD-001")
318            .valid_days(365)
319            .feature("basic")
320            .build()
321            .unwrap();
322
323        let signed = generator.generate(data).unwrap();
324
325        assert!(!signed.signature.is_empty());
326        assert_eq!(signed.algorithm, algorithm_ids::RSA_SHA256);
327    }
328
329    #[test]
330    fn test_crypto_generator_ed25519() {
331        use crate::keys::CryptoKeyPair;
332
333        let keypair = CryptoKeyPair::generate(algorithm_ids::ED25519).unwrap();
334        let generator = CryptoGenerator::from_keypair(&keypair);
335
336        let data = LicenseData::builder()
337            .id("CRYPTO-ED25519-001")
338            .serial("SN-CRYPTO-ED25519")
339            .customer_id("CUST-001")
340            .product_id("PROD-001")
341            .valid_days(365)
342            .feature("basic")
343            .build()
344            .unwrap();
345
346        let signed = generator.generate(data).unwrap();
347
348        assert!(!signed.signature.is_empty());
349        assert_eq!(signed.algorithm, algorithm_ids::ED25519);
350
351        // Verify the signature is valid
352        let algorithm = CryptoRegistry::get_signature_algorithm(algorithm_ids::ED25519).unwrap();
353        let data_bytes = serde_json::to_vec(&signed.data).unwrap();
354        let sig_bytes = BASE64.decode(&signed.signature).unwrap();
355        assert!(algorithm
356            .verify(&data_bytes, &sig_bytes, &keypair.public_key_pem)
357            .is_ok());
358    }
359
360    #[test]
361    fn test_crypto_generator_binary_export() {
362        use crate::keys::CryptoKeyPair;
363
364        let keypair = CryptoKeyPair::generate(algorithm_ids::ED25519).unwrap();
365        let generator = CryptoGenerator::from_keypair(&keypair);
366
367        let data = LicenseData::builder()
368            .id("BINARY-ED25519-001")
369            .serial("SN-BINARY-ED25519")
370            .customer_id("CUST-001")
371            .product_id("PROD-001")
372            .valid_days(365)
373            .build()
374            .unwrap();
375
376        let signed = generator.generate(data).unwrap();
377        let binary = generator.export_binary(&signed).unwrap();
378
379        // Check magic header
380        assert_eq!(&binary[0..4], BINARY_MAGIC);
381        assert_eq!(binary[4], BINARY_VERSION);
382    }
383
384    // Post-quantum generator tests (feature-gated)
385    #[cfg(feature = "post-quantum")]
386    mod pq_tests {
387        use super::*;
388        use crate::crypto::algorithm_ids;
389        use crate::keys::CryptoKeyPair;
390
391        #[test]
392        fn test_crypto_generator_ml_dsa_65() {
393            let keypair = CryptoKeyPair::generate(algorithm_ids::ML_DSA_65).unwrap();
394            let generator = CryptoGenerator::from_keypair(&keypair);
395
396            let data = LicenseData::builder()
397                .id("PQ-ML-DSA-001")
398                .serial("SN-PQ-ML-DSA")
399                .customer_id("CUST-001")
400                .product_id("PROD-001")
401                .valid_days(365)
402                .feature("quantum-safe")
403                .build()
404                .unwrap();
405
406            let signed = generator.generate(data).unwrap();
407
408            assert!(!signed.signature.is_empty());
409            assert_eq!(signed.algorithm, algorithm_ids::ML_DSA_65);
410
411            // ML-DSA-65 signatures are 3309 bytes, base64 encoded is ~4412 chars
412            assert!(signed.signature.len() > 4000);
413        }
414
415        #[test]
416        fn test_crypto_generator_hybrid_ed25519_ml_dsa() {
417            let keypair = CryptoKeyPair::generate(algorithm_ids::HYBRID_ED25519_ML_DSA_65).unwrap();
418            let generator = CryptoGenerator::from_keypair(&keypair);
419
420            let data = LicenseData::builder()
421                .id("PQ-HYBRID-001")
422                .serial("SN-PQ-HYBRID")
423                .customer_id("CUST-001")
424                .product_id("PROD-001")
425                .valid_days(365)
426                .feature("hybrid-security")
427                .build()
428                .unwrap();
429
430            let signed = generator.generate(data).unwrap();
431
432            assert!(!signed.signature.is_empty());
433            assert_eq!(signed.algorithm, algorithm_ids::HYBRID_ED25519_ML_DSA_65);
434        }
435
436        #[test]
437        fn test_crypto_generator_hybrid_rsa_ml_dsa() {
438            let keypair = CryptoKeyPair::generate(algorithm_ids::HYBRID_RSA_ML_DSA_65).unwrap();
439            let generator = CryptoGenerator::from_keypair(&keypair);
440
441            let data = LicenseData::builder()
442                .id("PQ-HYBRID-RSA-001")
443                .serial("SN-PQ-HYBRID-RSA")
444                .customer_id("CUST-001")
445                .product_id("PROD-001")
446                .valid_days(365)
447                .feature("hybrid-rsa-security")
448                .build()
449                .unwrap();
450
451            let signed = generator.generate(data).unwrap();
452
453            assert!(!signed.signature.is_empty());
454            assert_eq!(signed.algorithm, algorithm_ids::HYBRID_RSA_ML_DSA_65);
455        }
456
457        #[test]
458        fn test_pq_license_binary_export() {
459            let keypair = CryptoKeyPair::generate(algorithm_ids::ML_DSA_65).unwrap();
460            let generator = CryptoGenerator::from_keypair(&keypair);
461
462            let data = LicenseData::builder()
463                .id("PQ-BINARY-001")
464                .serial("SN-PQ-BINARY")
465                .customer_id("CUST-001")
466                .product_id("PROD-001")
467                .valid_days(365)
468                .build()
469                .unwrap();
470
471            let signed = generator.generate(data).unwrap();
472            let binary = generator.export_binary(&signed).unwrap();
473
474            // Check magic header
475            assert_eq!(&binary[0..4], BINARY_MAGIC);
476            assert_eq!(binary[4], BINARY_VERSION);
477
478            // Binary should be larger due to PQ signature (~3309 bytes signature + overhead)
479            // ML-DSA-65 signature: 3309 bytes, base64 encoded: ~4412 chars
480            // License data + JSON overhead: ~350 bytes
481            // Total expected: ~4700+ bytes
482            assert!(
483                binary.len() > 4500,
484                "Expected binary > 4500 bytes, got {} bytes. Signature len: {}",
485                binary.len(),
486                signed.signature.len()
487            );
488        }
489    }
490}