use base64::{engine::general_purpose, Engine as _};
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
use thiserror::Error;
use crate::types::{LicensePayload, ValidLicenseInfo};
#[derive(Debug, Error)]
pub enum LicenseError {
#[error("Invalid license format: {0}")]
InvalidFormat(String),
#[error("Invalid base64 encoding: {0}")]
InvalidEncoding(#[from] base64::DecodeError),
#[error("Invalid signature")]
InvalidSignature,
#[error("Invalid JSON payload: {0}")]
InvalidJson(#[from] serde_json::Error),
#[error("HWID mismatch: expected {expected}, got {actual}")]
HwidMismatch { expected: String, actual: String },
#[error("License expired on {0}")]
Expired(chrono::DateTime<chrono::Utc>),
#[error("Invalid public key: {0}")]
InvalidPublicKey(String),
}
pub struct LicenseValidator {
public_key: VerifyingKey,
}
impl LicenseValidator {
pub fn new(public_key_hex: &str) -> Result<Self, LicenseError> {
let public_key_bytes = hex::decode(public_key_hex)
.map_err(|e| LicenseError::InvalidPublicKey(format!("Invalid hex: {}", e)))?;
let key_bytes: [u8; 32] = public_key_bytes
.try_into()
.map_err(|_| LicenseError::InvalidPublicKey("Public key must be 32 bytes".into()))?;
let public_key = VerifyingKey::from_bytes(&key_bytes)
.map_err(|e| LicenseError::InvalidPublicKey(format!("Invalid Ed25519 key: {}", e)))?;
Ok(LicenseValidator { public_key })
}
pub fn validate(
&self,
license_string: &str,
current_hwid: &str,
) -> Result<ValidLicenseInfo, LicenseError> {
let parts: Vec<&str> = license_string.split('.').collect();
if parts.len() != 2 {
return Err(LicenseError::InvalidFormat(format!(
"Expected 2 parts separated by '.', got {}",
parts.len()
)));
}
let payload_bytes = general_purpose::STANDARD.decode(parts[0])?;
let signature_bytes = general_purpose::STANDARD.decode(parts[1])?;
let signature_array: [u8; 64] = signature_bytes
.try_into()
.map_err(|_| LicenseError::InvalidFormat("Signature must be 64 bytes".into()))?;
let signature = Signature::from_bytes(&signature_array);
self.public_key
.verify(&payload_bytes, &signature)
.map_err(|_| LicenseError::InvalidSignature)?;
let payload: LicensePayload = serde_json::from_slice(&payload_bytes)?;
if payload.hwid != current_hwid {
return Err(LicenseError::HwidMismatch {
expected: payload.hwid.clone(),
actual: current_hwid.to_string(),
});
}
let now = chrono::Utc::now();
if now > payload.expires_at {
return Err(LicenseError::Expired(payload.expires_at));
}
Ok(payload.into())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_invalid_format() {
let validator = LicenseValidator::new(
"913d5e19269699e51bcdb5c5a7106c278ef0e0fe92d31b76b6daf5bb00594fcf",
)
.unwrap();
let result = validator.validate("invalid-format", "test-hwid");
assert!(matches!(result, Err(LicenseError::InvalidFormat(_))));
}
#[test]
fn test_invalid_public_key() {
let result = LicenseValidator::new("invalid-hex");
assert!(matches!(result, Err(LicenseError::InvalidPublicKey(_))));
}
}