keygen-rs 0.11.1

Unofficial Rust SDK for integrating with the keygen.sh licensing service.
Documentation
//! Decryption for encrypted license and machine files.
//!
//! This module provides AES-256-GCM decryption for license files and machine files
//! that have been encrypted by the Keygen API.

use aes_gcm::aead::Aead;
use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
use base64::engine::general_purpose;
use base64::Engine;
use sha2::Digest;
use sha2::Sha256;
use zeroize::{Zeroize, ZeroizeOnDrop};

use crate::certificate::Certificate;
use crate::errors::Error;

#[derive(Zeroize, ZeroizeOnDrop)]
pub struct Decryptor {
    secret: String,
}

impl Decryptor {
    pub fn new(secret: String) -> Self {
        Self { secret }
    }

    pub fn decrypt_certificate(&self, cert: &Certificate) -> Result<Vec<u8>, Error> {
        let parts: Vec<&str> = cert.enc.split('.').collect();
        if parts.len() != 3 {
            return Err(Error::DecryptionError(
                "Invalid encrypted data format".into(),
            ));
        }

        let ciphertext = general_purpose::STANDARD
            .decode(parts[0])
            .map_err(|_| Error::DecryptionError("Failed to decode ciphertext".into()))?;
        let iv = general_purpose::STANDARD
            .decode(parts[1])
            .map_err(|_| Error::DecryptionError("Failed to decode IV".into()))?;
        let tag = general_purpose::STANDARD
            .decode(parts[2])
            .map_err(|_| Error::DecryptionError("Failed to decode tag".into()))?;

        let mut hasher = Sha256::new();
        hasher.update(self.secret.as_bytes());
        let key = hasher.finalize();

        let cipher = Aes256Gcm::new_from_slice(&key)
            .map_err(|_| Error::DecryptionError("Invalid key length".into()))?;
        let nonce = Nonce::from_slice(&iv);

        let mut encrypted_data = ciphertext;
        encrypted_data.extend_from_slice(&tag);

        let plaintext = cipher
            .decrypt(nonce, encrypted_data.as_ref())
            .map_err(|_| Error::DecryptionError("Decryption failed".into()))?;

        Ok(plaintext)
    }
}