use crate::crypto::CryptoRegistry;
use crate::error::{LicenseError, Result};
use crate::keys::parse_private_key;
use crate::license::{LicenseData, SignedLicense, BINARY_MAGIC, BINARY_VERSION};
use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
use rsa::pkcs1v15::SigningKey;
use rsa::signature::{RandomizedSigner, SignatureEncoding};
use rsa::RsaPrivateKey;
use sha2::Sha256;
use std::io::Write;
use std::path::Path;
use zeroize::Zeroizing;
pub struct LicenseGenerator {
private_key: RsaPrivateKey,
}
impl LicenseGenerator {
pub fn new(private_key: RsaPrivateKey) -> Self {
Self { private_key }
}
pub fn from_pem(pem: &str) -> Result<Self> {
let private_key = parse_private_key(pem)?;
Ok(Self::new(private_key))
}
pub fn from_pem_file(path: &Path) -> Result<Self> {
let pem = std::fs::read_to_string(path)?;
Self::from_pem(&pem)
}
pub fn generate(&self, data: LicenseData) -> Result<SignedLicense> {
let data_bytes = serde_json::to_vec(&data)
.map_err(|e| LicenseError::SerializationError(e.to_string()))?;
let signature = self.sign(&data_bytes)?;
Ok(SignedLicense {
data,
signature: BASE64.encode(&signature),
algorithm: "RSA-SHA256".to_string(),
})
}
fn sign(&self, data: &[u8]) -> Result<Vec<u8>> {
let signing_key = SigningKey::<Sha256>::new(self.private_key.clone());
let mut rng = rand::rngs::OsRng;
let signature = signing_key.sign_with_rng(&mut rng, data);
Ok(signature.to_bytes().to_vec())
}
pub fn export_binary(&self, license: &SignedLicense) -> Result<Vec<u8>> {
let mut output = Vec::new();
output.write_all(BINARY_MAGIC)?;
output.write_all(&[BINARY_VERSION])?;
let encoded = serde_json::to_vec(license)
.map_err(|e| LicenseError::SerializationError(e.to_string()))?;
let len = encoded.len() as u32;
output.write_all(&len.to_le_bytes())?;
output.write_all(&encoded)?;
Ok(output)
}
pub fn export_json(&self, license: &SignedLicense) -> Result<String> {
serde_json::to_string_pretty(license)
.map_err(|e| LicenseError::SerializationError(e.to_string()))
}
pub fn save_binary(&self, license: &SignedLicense, path: &Path) -> Result<()> {
let binary = self.export_binary(license)?;
std::fs::write(path, binary)?;
Ok(())
}
pub fn save_json(&self, license: &SignedLicense, path: &Path) -> Result<()> {
let json = self.export_json(license)?;
std::fs::write(path, json)?;
Ok(())
}
}
pub struct CryptoGenerator {
private_key_pem: Zeroizing<String>,
algorithm_id: String,
}
impl std::fmt::Debug for CryptoGenerator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CryptoGenerator")
.field("private_key_pem", &"[REDACTED]")
.field("algorithm_id", &self.algorithm_id)
.finish()
}
}
impl CryptoGenerator {
pub fn new(private_key_pem: &str, algorithm_id: &str) -> Self {
Self {
private_key_pem: Zeroizing::new(private_key_pem.to_string()),
algorithm_id: algorithm_id.to_string(),
}
}
pub fn from_pem_file(path: &Path, algorithm_id: &str) -> Result<Self> {
let pem = std::fs::read_to_string(path)?;
Ok(Self::new(&pem, algorithm_id))
}
pub fn from_keypair(keypair: &crate::keys::CryptoKeyPair) -> Self {
Self::new(keypair.private_key_pem(), &keypair.algorithm_id)
}
pub fn algorithm_id(&self) -> &str {
&self.algorithm_id
}
pub fn generate(&self, data: LicenseData) -> Result<SignedLicense> {
let data_bytes = serde_json::to_vec(&data)
.map_err(|e| LicenseError::SerializationError(e.to_string()))?;
let algorithm = CryptoRegistry::get_signature_algorithm(&self.algorithm_id)?;
let signature = algorithm.sign(&data_bytes, &self.private_key_pem)?;
Ok(SignedLicense {
data,
signature: BASE64.encode(&signature),
algorithm: self.algorithm_id.clone(),
})
}
pub fn export_binary(&self, license: &SignedLicense) -> Result<Vec<u8>> {
let mut output = Vec::new();
output.write_all(BINARY_MAGIC)?;
output.write_all(&[BINARY_VERSION])?;
let encoded = serde_json::to_vec(license)
.map_err(|e| LicenseError::SerializationError(e.to_string()))?;
let len = encoded.len() as u32;
output.write_all(&len.to_le_bytes())?;
output.write_all(&encoded)?;
Ok(output)
}
pub fn export_json(&self, license: &SignedLicense) -> Result<String> {
serde_json::to_string_pretty(license)
.map_err(|e| LicenseError::SerializationError(e.to_string()))
}
pub fn save_binary(&self, license: &SignedLicense, path: &Path) -> Result<()> {
let binary = self.export_binary(license)?;
std::fs::write(path, binary)?;
Ok(())
}
pub fn save_json(&self, license: &SignedLicense, path: &Path) -> Result<()> {
let json = self.export_json(license)?;
std::fs::write(path, json)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::algorithm_ids;
use crate::keys::KeyPair;
use crate::keys::KeySize;
#[test]
fn test_license_generation() {
let keypair = KeyPair::generate(KeySize::Bits2048).unwrap();
let generator = LicenseGenerator::new(keypair.into_private_key());
let data = LicenseData::builder()
.id("TEST-001")
.serial("SN-12345")
.customer_id("CUST-001")
.product_id("PROD-001")
.valid_days(365)
.feature("basic")
.build()
.unwrap();
let signed = generator.generate(data).unwrap();
assert!(!signed.signature.is_empty());
assert_eq!(signed.algorithm, "RSA-SHA256");
}
#[test]
fn test_binary_export() {
let keypair = KeyPair::generate(KeySize::Bits2048).unwrap();
let generator = LicenseGenerator::new(keypair.into_private_key());
let data = LicenseData::builder()
.id("TEST-001")
.serial("SN-12345")
.customer_id("CUST-001")
.product_id("PROD-001")
.valid_days(365)
.build()
.unwrap();
let signed = generator.generate(data).unwrap();
let binary = generator.export_binary(&signed).unwrap();
assert_eq!(&binary[0..4], BINARY_MAGIC);
assert_eq!(binary[4], BINARY_VERSION);
}
#[test]
fn test_crypto_generator_rsa() {
use crate::keys::CryptoKeyPair;
let keypair = CryptoKeyPair::generate(algorithm_ids::RSA_SHA256).unwrap();
let generator = CryptoGenerator::from_keypair(&keypair);
let data = LicenseData::builder()
.id("CRYPTO-RSA-001")
.serial("SN-CRYPTO-RSA")
.customer_id("CUST-001")
.product_id("PROD-001")
.valid_days(365)
.feature("basic")
.build()
.unwrap();
let signed = generator.generate(data).unwrap();
assert!(!signed.signature.is_empty());
assert_eq!(signed.algorithm, algorithm_ids::RSA_SHA256);
}
#[test]
fn test_crypto_generator_ed25519() {
use crate::keys::CryptoKeyPair;
let keypair = CryptoKeyPair::generate(algorithm_ids::ED25519).unwrap();
let generator = CryptoGenerator::from_keypair(&keypair);
let data = LicenseData::builder()
.id("CRYPTO-ED25519-001")
.serial("SN-CRYPTO-ED25519")
.customer_id("CUST-001")
.product_id("PROD-001")
.valid_days(365)
.feature("basic")
.build()
.unwrap();
let signed = generator.generate(data).unwrap();
assert!(!signed.signature.is_empty());
assert_eq!(signed.algorithm, algorithm_ids::ED25519);
let algorithm = CryptoRegistry::get_signature_algorithm(algorithm_ids::ED25519).unwrap();
let data_bytes = serde_json::to_vec(&signed.data).unwrap();
let sig_bytes = BASE64.decode(&signed.signature).unwrap();
assert!(algorithm
.verify(&data_bytes, &sig_bytes, &keypair.public_key_pem)
.is_ok());
}
#[test]
fn test_crypto_generator_binary_export() {
use crate::keys::CryptoKeyPair;
let keypair = CryptoKeyPair::generate(algorithm_ids::ED25519).unwrap();
let generator = CryptoGenerator::from_keypair(&keypair);
let data = LicenseData::builder()
.id("BINARY-ED25519-001")
.serial("SN-BINARY-ED25519")
.customer_id("CUST-001")
.product_id("PROD-001")
.valid_days(365)
.build()
.unwrap();
let signed = generator.generate(data).unwrap();
let binary = generator.export_binary(&signed).unwrap();
assert_eq!(&binary[0..4], BINARY_MAGIC);
assert_eq!(binary[4], BINARY_VERSION);
}
#[cfg(feature = "post-quantum")]
mod pq_tests {
use super::*;
use crate::crypto::algorithm_ids;
use crate::keys::CryptoKeyPair;
#[test]
fn test_crypto_generator_ml_dsa_65() {
let keypair = CryptoKeyPair::generate(algorithm_ids::ML_DSA_65).unwrap();
let generator = CryptoGenerator::from_keypair(&keypair);
let data = LicenseData::builder()
.id("PQ-ML-DSA-001")
.serial("SN-PQ-ML-DSA")
.customer_id("CUST-001")
.product_id("PROD-001")
.valid_days(365)
.feature("quantum-safe")
.build()
.unwrap();
let signed = generator.generate(data).unwrap();
assert!(!signed.signature.is_empty());
assert_eq!(signed.algorithm, algorithm_ids::ML_DSA_65);
assert!(signed.signature.len() > 4000);
}
#[test]
fn test_crypto_generator_hybrid_ed25519_ml_dsa() {
let keypair = CryptoKeyPair::generate(algorithm_ids::HYBRID_ED25519_ML_DSA_65).unwrap();
let generator = CryptoGenerator::from_keypair(&keypair);
let data = LicenseData::builder()
.id("PQ-HYBRID-001")
.serial("SN-PQ-HYBRID")
.customer_id("CUST-001")
.product_id("PROD-001")
.valid_days(365)
.feature("hybrid-security")
.build()
.unwrap();
let signed = generator.generate(data).unwrap();
assert!(!signed.signature.is_empty());
assert_eq!(signed.algorithm, algorithm_ids::HYBRID_ED25519_ML_DSA_65);
}
#[test]
fn test_crypto_generator_hybrid_rsa_ml_dsa() {
let keypair = CryptoKeyPair::generate(algorithm_ids::HYBRID_RSA_ML_DSA_65).unwrap();
let generator = CryptoGenerator::from_keypair(&keypair);
let data = LicenseData::builder()
.id("PQ-HYBRID-RSA-001")
.serial("SN-PQ-HYBRID-RSA")
.customer_id("CUST-001")
.product_id("PROD-001")
.valid_days(365)
.feature("hybrid-rsa-security")
.build()
.unwrap();
let signed = generator.generate(data).unwrap();
assert!(!signed.signature.is_empty());
assert_eq!(signed.algorithm, algorithm_ids::HYBRID_RSA_ML_DSA_65);
}
#[test]
fn test_pq_license_binary_export() {
let keypair = CryptoKeyPair::generate(algorithm_ids::ML_DSA_65).unwrap();
let generator = CryptoGenerator::from_keypair(&keypair);
let data = LicenseData::builder()
.id("PQ-BINARY-001")
.serial("SN-PQ-BINARY")
.customer_id("CUST-001")
.product_id("PROD-001")
.valid_days(365)
.build()
.unwrap();
let signed = generator.generate(data).unwrap();
let binary = generator.export_binary(&signed).unwrap();
assert_eq!(&binary[0..4], BINARY_MAGIC);
assert_eq!(binary[4], BINARY_VERSION);
assert!(
binary.len() > 4500,
"Expected binary > 4500 bytes, got {} bytes. Signature len: {}",
binary.len(),
signed.signature.len()
);
}
}
}