<?php
/**
 * Cipher — AES-256-CBC encryption for license.dat
 * 
 * This file handles encryption/decryption of the license data blob.
 * The license is stored as encrypted binary, not as a PHP file.
 * 
 * Key derivation: sha256(secret_salt_part1 + install_path + secret_salt_part2)
 */

if (!defined('BASEPATH')) { http_response_code(403); exit; }

// ─── Salt fragments (split across files for security) ───────────────
// Part 1 of the secret salt — Part 2 is in sentinel.php
define('_CIPHER_SALT_A', 'xK9#mQ2$vL7pRt');

/**
 * Derive the encryption key from environment-specific values.
 * Uses install path + split salts to make the key unique per installation.
 * 
 * @return string 32-byte binary key
 */
function _cipher_derive_key() {
    $installPath = defined('PATH') ? PATH : dirname(__DIR__);
    // Combine salt fragments from cipher.php and sentinel.php
    $saltA = _CIPHER_SALT_A;
    $saltB = defined('_SENTINEL_SALT_B') ? _SENTINEL_SALT_B : 'fallback';
    $raw = $saltA . '|' . $installPath . '|' . $saltB;
    return hash('sha256', $raw, true); // 32 bytes for AES-256
}

/**
 * Encrypt data using AES-256-CBC.
 * 
 * @param array $data The license data array to encrypt
 * @param string|null $key Optional key override (defaults to derived key)
 * @return string Binary blob: IV (16 bytes) || ciphertext
 */
function _cipher_encrypt($data, $key = null) {
    if ($key === null) {
        $key = _cipher_derive_key();
    }
    
    $json = json_encode($data, JSON_UNESCAPED_SLASHES);
    $method = 'aes-256-cbc';
    $ivLen = openssl_cipher_iv_length($method);
    $iv = openssl_random_pseudo_bytes($ivLen);
    
    $encrypted = openssl_encrypt($json, $method, $key, OPENSSL_RAW_DATA, $iv);
    
    if ($encrypted === false) {
        return false;
    }
    
    // Append HMAC for integrity verification
    $blob = $iv . $encrypted;
    $hmac = hash_hmac('sha256', $blob, $key, true);
    
    return $hmac . $blob; // HMAC (32) + IV (16) + ciphertext
}

/**
 * Decrypt data from AES-256-CBC blob.
 * 
 * @param string $blob Binary blob: HMAC (32) + IV (16) + ciphertext
 * @param string|null $key Optional key override (defaults to derived key)
 * @return array|false Decrypted data array, or false on failure/tampering
 */
function _cipher_decrypt($blob, $key = null) {
    if ($key === null) {
        $key = _cipher_derive_key();
    }
    
    if (strlen($blob) < 64) { // minimum: 32 HMAC + 16 IV + 16 block
        return false;
    }
    
    // Extract HMAC and verify integrity
    $storedHmac = substr($blob, 0, 32);
    $payload = substr($blob, 32);
    $calcHmac = hash_hmac('sha256', $payload, $key, true);
    
    if (!hash_equals($storedHmac, $calcHmac)) {
        return false; // Tampering detected
    }
    
    // Extract IV and ciphertext
    $method = 'aes-256-cbc';
    $ivLen = openssl_cipher_iv_length($method);
    $iv = substr($payload, 0, $ivLen);
    $ciphertext = substr($payload, $ivLen);
    
    $json = openssl_decrypt($ciphertext, $method, $key, OPENSSL_RAW_DATA, $iv);
    
    if ($json === false) {
        return false;
    }
    
    $data = json_decode($json, true);
    
    if (!is_array($data)) {
        return false;
    }
    
    return $data;
}

/**
 * Write encrypted license data to license.dat file.
 * 
 * @param string $filePath Full path to license.dat
 * @param array $data License data array
 * @return bool Success
 */
function _cipher_write_license($filePath, $data) {
    $blob = _cipher_encrypt($data);
    if ($blob === false) {
        return false;
    }
    
    $dir = dirname($filePath);
    if (!is_dir($dir)) {
        @mkdir($dir, 0755, true);
    }
    
    return @file_put_contents($filePath, $blob, LOCK_EX) !== false;
}

/**
 * Read and decrypt license data from license.dat file.
 * 
 * @param string $filePath Full path to license.dat
 * @return array|false Decrypted license data, or false on failure
 */
function _cipher_read_license($filePath) {
    if (!file_exists($filePath) || !is_readable($filePath)) {
        return false;
    }
    
    $blob = @file_get_contents($filePath);
    if ($blob === false || empty($blob)) {
        return false;
    }
    
    return _cipher_decrypt($blob);
}
