use crate::ty::*;
use aes_gcm::{aead::Aead, Aes256Gcm, KeyInit as _, Nonce};
use crc32fast::hash as crc32;
use e_utils::chrono::{DateTime, Duration, FixedOffset, Utc};
const KEY_SIZE: usize = 32;
const NONCE_SIZE: usize = 12;
pub struct AesGcmProtocol {
secret_key: [u8; KEY_SIZE],
}
impl AesGcmProtocol {
pub fn new(secret_key: [u8; KEY_SIZE]) -> Self {
Self { secret_key }
}
}
impl LicenseProtocol for AesGcmProtocol {
fn generate(&self, days: u64) -> String {
let nonce = e_utils::algorithm!([u8; NONCE_SIZE]);
let expire_time = (Utc::now() + Duration::hours(days as i64)).timestamp();
let mut data = expire_time.to_le_bytes().to_vec();
let checksum = crc32(&data) ^ 0xDEADBEEF;
data.extend_from_slice(&checksum.to_le_bytes());
let cipher = Aes256Gcm::new_from_slice(&self.secret_key).unwrap();
let ciphertext = cipher.encrypt(&nonce.into(), &*data).unwrap();
let mut output = Vec::with_capacity(NONCE_SIZE + ciphertext.len());
output.extend_from_slice(&nonce);
output.extend(ciphertext);
e_utils::algorithm::base64::encode(output).trim_end_matches('=').to_string()
}
fn verify(&self, license: &str) -> e_utils::Result<DateTime<FixedOffset>> {
let padded_license = {
let padding_len = (4 - license.len() % 4) % 4;
let mut padded = String::with_capacity(license.len() + padding_len);
padded.push_str(license);
padded.extend(std::iter::repeat('=').take(padding_len));
padded
};
let data = e_utils::algorithm::base64::decode(&padded_license).map_err(|_| "Invalid format")?;
if data.len() < NONCE_SIZE + 12 {
return Err("Invalid license".into());
}
let (nonce, ciphertext) = data.split_at(NONCE_SIZE);
let cipher = Aes256Gcm::new_from_slice(&self.secret_key).unwrap();
let plaintext = cipher.decrypt(Nonce::from_slice(nonce), ciphertext).map_err(|_| "Decryption failed")?;
if plaintext.len() != 12 {
return Err("Invalid data length".into());
}
let (time_bytes, checksum_bytes) = plaintext.split_at(8);
let expire_time = i64::from_le_bytes(time_bytes.try_into().unwrap());
let stored_checksum = u32::from_le_bytes(checksum_bytes.try_into().unwrap());
let calculated_checksum = crc32(time_bytes) ^ 0xDEADBEEF;
if stored_checksum != calculated_checksum {
return Err("Checksum mismatch".into());
}
let timezone = FixedOffset::east_opt(8 * 3600).unwrap();
let expire = DateTime::from_timestamp(expire_time, 0).ok_or("Invalid timestamp")?.with_timezone(&timezone);
if Utc::now().with_timezone(&timezone) > expire {
return Err("授权已过期".into());
}
Ok(expire)
}
}