use crate::{
crypto::{KeyPair, PublicKey},
error::LicenseError,
license::{License, LicensePayload},
};
use chrono::Utc;
use ed25519_dalek::Signature;
use serde_cbor;
use std::convert::TryInto;
use tracing::{debug, error, info, instrument, warn};
#[derive(Debug, Clone)]
pub struct ValidationResult {
pub violations: Vec<String>,
pub warnings: Vec<String>,
pub expires_at: Option<chrono::DateTime<Utc>>,
pub days_remaining: Option<i64>,
pub features: Vec<String>,
}
impl ValidationResult {
pub fn new() -> Self {
Self {
violations: Vec::new(),
warnings: Vec::new(),
expires_at: None,
days_remaining: None,
features: Vec::new(),
}
}
pub fn add_violation(&mut self, msg: String) {
self.violations.push(msg);
}
pub fn add_warning(&mut self, msg: String) {
self.warnings.push(msg);
}
pub fn is_valid(&self) -> bool {
self.violations.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct LicenseConfig {
pub allow_offline: bool,
pub grace_period_days: u32,
pub max_clock_skew_seconds: i64,
pub require_hardware_binding: bool,
pub enable_revocation_check: bool,
}
impl Default for LicenseConfig {
fn default() -> Self {
Self {
allow_offline: true,
grace_period_days: 7,
max_clock_skew_seconds: 300,
require_hardware_binding: false,
enable_revocation_check: true,
}
}
}
pub struct LicenseEngine {
config: LicenseConfig,
}
impl LicenseEngine {
pub fn new(config: LicenseConfig) -> Self {
info!("Initializing LicenseEngine with config: {:?}", config);
Self { config }
}
#[instrument(skip(self, keypair, payload), fields(license_id = %payload.license_id))]
pub fn create_license(
&self,
keypair: &KeyPair,
payload: LicensePayload,
) -> Result<License, LicenseError> {
info!("Creating license for customer: {}", payload.customer_id);
debug!("License payload: {:?}", payload);
self.validate_payload(&payload)?;
let serialized = serde_cbor::to_vec(&payload).map_err(|e| {
error!("Failed to serialize license payload: {}", e);
LicenseError::SerializationError(e.to_string())
})?;
debug!("Serialized payload size: {} bytes", serialized.len());
let signature = keypair.private.sign(&serialized)?;
let license = License {
version: 1,
payload,
signature: signature.to_bytes().to_vec(),
algorithm: "ed25519".to_string(),
};
info!("License created successfully: {}", license.license_id());
Ok(license)
}
#[instrument(skip(self, public_key, license), fields(license_id = %license.license_id()))]
pub fn verify_license(
&self,
public_key: &PublicKey,
license: &License,
) -> Result<ValidationResult, LicenseError> {
info!("Verifying license: {}", license.license_id());
debug!("Using public key: {}", hex::encode(public_key.as_bytes()));
let serialized = serde_cbor::to_vec(&license.payload)
.map_err(|e| LicenseError::SerializationError(e.to_string()))?;
let sig_bytes: [u8; 64] = license
.signature
.as_slice()
.try_into()
.map_err(|_| LicenseError::SignatureValidationFailed)?;
let signature = Signature::from_bytes(&sig_bytes);
public_key.verify(&serialized, &signature)?;
let result = self.validate_license_content(license)?;
if result.is_valid() {
info!("License validation successful: {}", license.license_id());
} else {
warn!("License validation failed: {:?}", result.violations);
}
Ok(result)
}
fn validate_payload(&self, payload: &LicensePayload) -> Result<(), LicenseError> {
debug!("Validating license payload");
if payload.expires_at <= payload.issued_at {
error!("License expiration must be after issue date");
return Err(LicenseError::InvalidLicense(
"Expiration date must be after issue date".to_string(),
));
}
if payload.max_activations == 0 {
error!("License must allow at least 1 activation");
return Err(LicenseError::InvalidLicense(
"Max activations must be at least 1".to_string(),
));
}
Ok(())
}
fn validate_license_content(
&self,
license: &License,
) -> Result<ValidationResult, LicenseError> {
let mut result = ValidationResult::new();
let now = Utc::now();
if license.payload.expires_at < now {
result.add_violation("License has expired".to_string());
} else {
result.expires_at = Some(license.payload.expires_at);
result.days_remaining = Some((license.payload.expires_at - now).num_days());
}
if self.config.allow_offline {
let grace_end = license.payload.expires_at
+ chrono::Duration::days(self.config.grace_period_days as i64);
if grace_end < now {
result.add_violation("Grace period has ended".to_string());
}
}
let skew = (now - license.payload.issued_at).num_seconds().abs();
if skew > self.config.max_clock_skew_seconds {
warn!("Large clock skew detected: {} seconds", skew);
result.add_warning(format!("Large system clock skew: {} seconds", skew));
}
result.features = license.payload.features.clone();
Ok(result)
}
pub fn generate_keypair(&self) -> Result<KeyPair, LicenseError> {
info!("Generating new key pair");
let keypair = KeyPair::generate();
debug!(
"Generated public key: {}",
hex::encode(keypair.public.as_bytes())
);
Ok(keypair)
}
pub fn license_to_json(&self, license: &License) -> Result<String, LicenseError> {
serde_json::to_string_pretty(license)
.map_err(|e| LicenseError::SerializationError(e.to_string()))
}
pub fn license_from_json(&self, json: &str) -> Result<License, LicenseError> {
serde_json::from_str(json).map_err(|e| LicenseError::DeserializationError(e.to_string()))
}
pub fn license_to_bytes(&self, license: &License) -> Result<Vec<u8>, LicenseError> {
serde_cbor::to_vec(license).map_err(|e| LicenseError::SerializationError(e.to_string()))
}
pub fn license_from_bytes(&self, bytes: &[u8]) -> Result<License, LicenseError> {
serde_cbor::from_slice(bytes).map_err(|e| LicenseError::DeserializationError(e.to_string()))
}
}