main
Robert 5 months ago
parent 1f34b7bbfb
commit 3aa76d8054
  1. 6
      .gitignore
  2. 69
      Zephir/aes-license/hydraterlicense/config.json
  3. 71
      Zephir/aes-license/hydraterlicense/hydraterlicense/keygenerator.zep
  4. 95
      Zephir/aes-license/hydraterlicense/hydraterlicense/licensewriter.zep
  5. 148
      Zephir/aes-license/hydraterlicense/hydraterlicense/makelicense.zep
  6. 69
      Zephir/aes-loader/hydraterbootloader/config.json
  7. 204
      Zephir/aes-loader/hydraterbootloader/hydraterbootloader/licenseverifier.zep
  8. 125
      Zephir/aes-loader/hydraterbootloader/hydraterbootloader/loader.zep

6
.gitignore vendored

@ -0,0 +1,6 @@
Zephir/aes-loader/hydraterbootloader/ext
Zephir/aes-loader/hydraterbootloader/.zephir
Zephir/aes-license/hydraterlicense/ext
Zephir/aes-license/hydraterlicense/.zephir
*.log
*.swp

@ -0,0 +1,69 @@
{
"stubs": {
"path": "ide\/%version%\/%namespace%\/",
"stubs-run-after-generate": false,
"banner": ""
},
"api": {
"path": "doc\/%version%",
"theme": {
"name": "zephir",
"options": {
"github": null,
"analytics": null,
"main_color": "#3E6496",
"link_color": "#3E6496",
"link_hover_color": "#5F9AE7"
}
}
},
"warnings": {
"unused-variable": true,
"unused-variable-external": false,
"possible-wrong-parameter": true,
"possible-wrong-parameter-undefined": false,
"nonexistent-function": true,
"nonexistent-class": true,
"non-valid-isset": true,
"non-array-update": true,
"non-valid-objectupdate": true,
"non-valid-fetch": true,
"invalid-array-index": true,
"non-array-append": true,
"invalid-return-type": true,
"unreachable-code": true,
"nonexistent-constant": true,
"not-supported-magic-constant": true,
"non-valid-decrement": true,
"non-valid-increment": true,
"non-valid-clone": true,
"non-valid-new": true,
"non-array-access": true,
"invalid-reference": true,
"invalid-typeof-comparison": true,
"conditional-initialization": true
},
"optimizations": {
"static-type-inference": true,
"static-type-inference-second-pass": true,
"local-context-pass": true,
"constant-folding": true,
"static-constant-class-folding": true,
"call-gatherer-pass": true,
"check-invalid-reads": false,
"internal-call-transformation": false
},
"extra": {
"indent": "spaces",
"export-classes": false
},
"namespace": "hydraterlicense",
"name": "hydraterlicense",
"description": "",
"author": "Phalcon Team",
"version": "0.0.1",
"verbose": false,
"requires": {
"extensions": ["openssl"]
}
}

@ -0,0 +1,71 @@
namespace HydraterLicense;
class KeyGenerator
{
/**
* Generates an RSA private key and saves it to a file.
*
* @param string path Output path for private key (e.g., "private.pem")
* @param int bits RSA key size (default: 2048)
* @return bool
*/
public function generatePrivateKey(string path, int bits = 2048) -> bool
{
if unlikely !extension_loaded("openssl") {
echo "OpenSSL extension not loaded";
return false;
}
var config, privateKey, exported, result;
let config = [
"private_key_type": OPENSSL_KEYTYPE_RSA,
"private_key_bits": bits,
"digest_alg": "sha256"
];
let privateKey = openssl_pkey_new(config);
let exported = "";
if unlikely !openssl_pkey_export(privateKey, exported) {
echo "Failed to export pkey";
return false;
}
let result = file_put_contents(path, exported);
if !result {
echo "Unable to save";
}
return result !== false;
}
/**
* Extracts public key from private key and saves it to file.
*
* @param string privatePath Path to private.pem
* @param string publicPath Output path for public.pem
* @return bool
*/
public function generatePublicKey(string privatePath, string publicPath) -> bool
{
var privatePem, privateKey, pubKeyDetails, pubKey, written;
let privatePem = file_get_contents(privatePath);
if privatePem === false {
return false;
}
let privateKey = openssl_pkey_get_private(privatePem);
let pubKeyDetails = openssl_pkey_get_details(privateKey);
if typeof pubKeyDetails !== "array" || !isset pubKeyDetails["key"] {
return false;
}
let pubKey = pubKeyDetails["key"];
let written = file_put_contents(publicPath, pubKey);
return written !== false;
}
}

@ -0,0 +1,95 @@
namespace HydraterLicense;
class LicenseWriter
{
/**
* Create a digitally signed license.json file
*
* @param array fileSettings Array of [filename => ["enabled": bool, "expires": string ISO 8601 expiration date, "password": string]]
* @param array domains Allowed domain names
* @param string privateKeyPem Private key in PEM format
* @param string aesKey 32-byte encryption key
* @param string aesIV 16-byte initialization vector
*/
public function createLicenseJson(array fileSettings, array domains, string privateKeyPem, string aesKey, string aesIV, string licenseFile)
{
var features = [], filename, setting, feature, plainPassword;
var license, licenseJson, signature, finalPayload;
var encrypted, encryptedB64, finalJson, enabled, expires;
var fileHandle, myfeature;
// Build feature list
for filename, setting in fileSettings {
if typeof setting !== "array" {
continue;
}
if !isset setting["feature"] || !isset setting["enabled"] || !isset setting["password"] {
continue;
}
let myfeature = setting["feature"];
if !isset setting["expires"] {
let expires = "*"; // Never Expires
} else {
let expires = (string) setting["expires"];
}
if ends_with(filename, ".aes") {
let plainPassword = (string) setting["password"];
// Encrypt password with AES-256-CBC
let encrypted = openssl_encrypt(
plainPassword,
"aes-256-cbc",
aesKey,
1, // OPENSSL_RAW_DATA
aesIV
);
// Base64 encode encrypted output
let encryptedB64 = base64_encode(encrypted);
if setting["enabled"] == true || setting["enabled"] == 1 {
let enabled = true;
} else {
let enabled = false;
}
let feature = [
"file": filename,
"feature": myfeature,
"enabled": enabled,
"expires": expires,
"password": encryptedB64
];
let features[] = feature;
}
}
let license = [
"features": features,
"domains": domains
];
// JSON encode license (pretty format)
let licenseJson = json_encode(license, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
// Sign using openssl_sign via PHP
let signature = "";
openssl_sign(licenseJson, signature, privateKeyPem, "sha256");
// Wrap license + signature into final JSON
let finalPayload = [
"license": json_decode(licenseJson),
"signature": base64_encode(signature)
];
let finalJson = json_encode(finalPayload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
// Save to license.json
let fileHandle = fopen(licenseFile, "w");
if fileHandle !== false {
fwrite(fileHandle, finalJson);
fclose(fileHandle);
}
}
}

@ -0,0 +1,148 @@
use HydraterLicense\KeyGenerator;
use HydraterLicense\LicenseWriter;
namespace HydraterLicense;
class MakeLicense
{
public function makePassword(int size = 16)->string {
var r;
let r = this->generateCryptoKey(size);
return bin2hex(r);
}
public function generateLicense(
array Array_For_Files,
array AllowedDomains,
string PrivatePEM,
string PublicPEM,
string AESKeysFile,
string LicenseFile
) {
var Key, KeyHex, iv, ivHex, aes, privateKey, password;
var secret_php_file, writer, file;
var aesKey, aesIV, gen, v, e, r;
try {
if !file_exists(AESKeysFile) {
// Generate a 32-byte (256-bit) AES key
let Key = this->generateCryptoKey(32);
let KeyHex = bin2hex(Key);
// Generate a 16-byte (128-bit) IV
let iv = this->generateCryptoKey(16);
let ivHex = bin2hex(iv);
// Output the results
let aes = "<?php \n";
let aes .= "function getAES_master_keys() { \n";
let aes .= "\t // Generated AES-256 Key (32 bytes): \n";
let aes .= "\t $aesKey = hex2bin(\"". KeyHex ."\"); \n";
let aes .= "\t // Generated IV (16 bytes): \n";
let aes .= "\t $aesIV = hex2bin(\"". ivHex ."\"); \n";
let aes .= "\t return [\$aesKey, \$aesIV]; \n";
let aes .= "}";
file_put_contents(AESKeysFile, aes);
let aesKey = Key;
let aesIV = iv;
} else {
require AESKeysFile;
let r = getAES_master_keys();
let aesKey = r[0];
let aesIV = r[1];
}
if file_exists(PrivatePEM) {
let privateKey = file_get_contents(PrivatePEM);
} else {
let gen = new KeyGenerator();
if gen->generatePrivateKey(PrivatePEM) {
echo "Private key generated.\n";
} else {
echo "Private key generation failed.\n";
exit(1);
}
if gen->generatePublicKey(PrivatePEM, PublicPEM) {
echo "Public key extracted.\n";
} else {
echo "Public key extraction failed.\n";
exit(1);
}
let privateKey = file_get_contents(PrivatePEM);
}
// Go ahead and make encypted AES files from PHP files
for file, v in Array_For_Files {
let password = v["password"];
let secret_php_file = str_replace(".aes", ".php", file);
let secret_php_file = str_replace("/aes/", "/secret_php_files/", secret_php_file);
this->encryptPhpFile(secret_php_file, file, password);
}
// Make new License File with new AES files
let writer = new LicenseWriter();
writer->createLicenseJson(
Array_For_Files,
AllowedDomains,
privateKey,
aesKey,
aesIV,
LicenseFile
);
echo "Encrypted and signed license.json created.\n";
return true;
} catch RuntimeException, e {
echo "Runtime error occurred: ", e->getMessage(), "\n";
} catch Exception, e {
echo "Generic error: ", e->getMessage(), "\n";
}
}
protected function generateCryptoKey(int length) {
if (function_exists("random_bytes")) {
return random_bytes(length);
} elseif (function_exists("openssl_random_pseudo_bytes")) {
return openssl_random_pseudo_bytes(length);
} else {
throw new \RuntimeException("No cryptographically secure random function available");
}
}
protected function evpBytesToKey(string password, string salt, int keyLen = 32, int ivLen = 16) {
var dtot, d, key, iv;
let dtot = "";
let d = "";
while (strlen(dtot) < (keyLen + ivLen)) {
let d = md5(d . password . salt, true);
let dtot .= d;
}
let key = substr(dtot, 0, keyLen);
let iv = substr(dtot, keyLen, ivLen);
return [key, iv];
}
protected function encryptPhpFile(string inFile, string outFile, string password) {
var data, salt, key, iv, ciphertext, prefix, r;
let data = file_get_contents(inFile);
if data === false {
die("Cannot read input file.\n");
}
let salt = random_bytes(8);
let r = this->evpBytesToKey(password, salt);
let key = r[0];
let iv = r[1];
let ciphertext = openssl_encrypt(data, "AES-256-CBC", key, OPENSSL_RAW_DATA, iv);
if ciphertext === false {
die("Encryption failed.\n");
}
let prefix = "Salted__" . salt;
file_put_contents(outFile, prefix . ciphertext);
echo "Encrypted file written to: " . outFile ."\n";
}
}

@ -0,0 +1,69 @@
{
"stubs": {
"path": "ide\/%version%\/%namespace%\/",
"stubs-run-after-generate": false,
"banner": ""
},
"api": {
"path": "doc\/%version%",
"theme": {
"name": "zephir",
"options": {
"github": null,
"analytics": null,
"main_color": "#3E6496",
"link_color": "#3E6496",
"link_hover_color": "#5F9AE7"
}
}
},
"warnings": {
"unused-variable": true,
"unused-variable-external": false,
"possible-wrong-parameter": true,
"possible-wrong-parameter-undefined": false,
"nonexistent-function": true,
"nonexistent-class": true,
"non-valid-isset": true,
"non-array-update": true,
"non-valid-objectupdate": true,
"non-valid-fetch": true,
"invalid-array-index": true,
"non-array-append": true,
"invalid-return-type": true,
"unreachable-code": true,
"nonexistent-constant": true,
"not-supported-magic-constant": true,
"non-valid-decrement": true,
"non-valid-increment": true,
"non-valid-clone": true,
"non-valid-new": true,
"non-array-access": true,
"invalid-reference": true,
"invalid-typeof-comparison": true,
"conditional-initialization": true
},
"optimizations": {
"static-type-inference": true,
"static-type-inference-second-pass": true,
"local-context-pass": true,
"constant-folding": true,
"static-constant-class-folding": true,
"call-gatherer-pass": true,
"check-invalid-reads": false,
"internal-call-transformation": false
},
"extra": {
"indent": "spaces",
"export-classes": false
},
"namespace": "hydraterbootloader",
"name": "hydraterbootloader",
"description": "Hydrater bootloader for encrypted PHP files",
"author": "Robert Strutts",
"version": "0.0.1",
"verbose": false,
"requires": {
"extensions": ["openssl"]
}
}

@ -0,0 +1,204 @@
namespace HydraterBootloader;
use HydraterBootloader\Loader;
class LicenseVerifier
{
public function tryEnabledItem(string licenseFile, string keyFile, string featureName, string publicKeyPem, bool useInclude = true, bool forceRefresh = false) {
var l, f, found, password, filePath, r, aesKey, aesIV, data;
if featureName == "" || licenseFile == "" || publicKeyPem == "" {
return false;
}
if ! this->verifySignature(licenseFile, publicKeyPem) {
return false;
}
let l = new Loader();
if ! file_exists(keyFile) {
return false;
}
require keyFile;
let r = getAES_master_keys();
let aesKey = r[0];
let aesIV = r[1];
let data = this->getEnabledFeatures(licenseFile, aesKey, aesIV);
let found = false;
for f in data {
if ! isset f["feature"] || f["feature"] != featureName {
continue;
}
let filePath = f["file"];
if isset f["enabled"] && f["enabled"] == true {
if isset f["password"] {
let password = f["password"];
let found = true;
break;
}
}
}
if found {
return l->run(filePath, password, useInclude, forceRefresh);
}
}
/**
* Verify digital signature using public key
*/
protected function verifySignature(string licensePath, string publicKeyPem) -> bool
{
var contents, parsed, licensePart, licenseJson, signatureB64, signature, result, PEMContent;
if licensePath == "" || publicKeyPem == "" {
return false;
}
let PEMContent = file_get_contents(publicKeyPem);
if PEMContent === false || PEMContent == "" {
return false;
}
let contents = file_get_contents(licensePath);
if contents === false {
throw new \Exception("No License Data!");
}
let parsed = json_decode(contents, true);
if typeof parsed !== "array" || !isset parsed["license"] || !isset parsed["signature"] {
throw new \Exception("No Signature!");
}
let licensePart = parsed["license"];
let licenseJson = json_encode(licensePart, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
let signatureB64 = parsed["signature"];
let signature = base64_decode(signatureB64);
let result = openssl_verify(licenseJson, signature, PEMContent, "sha256");
return result === 1;
}
/**
* Return enabled features with decrypted passwords
*/
protected function getEnabledFeatures(string licensePath, string aesKey, string aesIV) -> array
{
var contents, parsed, features, feature, result = [], encryptedB64, encryptedRaw, decrypted, expires;
var i, total;
let contents = file_get_contents(licensePath);
if contents === false {
return [];
}
let parsed = json_decode(contents, true);
if typeof parsed !== "array" || !isset parsed["license"]["features"] {
return [];
}
let features = parsed["license"]["features"];
let total = count(features);
for i in range(0, total - 1) {
let feature = features[i];
if isset feature["enabled"] && feature["enabled"] == true {
if isset feature["file"] && isset feature["feature"] && isset feature["password"] {
if ! isset feature["expires"] {
let expires = "*";
} else {
let expires = feature["expires"];
}
if this->isExpired(expires) {
let result[] = [
"file": feature["file"],
"feature": feature["feature"],
"enabled": false,
"status": "⏳ License expired"
];
continue; // Skip granting Feature as its expired!
}
let encryptedB64 = feature["password"];
let encryptedRaw = base64_decode(encryptedB64);
let decrypted = openssl_decrypt(encryptedRaw, "aes-256-cbc", aesKey, 1, aesIV);
let result[] = [
"file": feature["file"],
"feature": feature["feature"],
"password": decrypted,
"enabled": true,
"status": "License valid"
];
}
}
}
return result;
}
/**
* Check if license is expired
*/
protected function isExpired(string expires) -> bool
{
var now, licenseTime;
// Grant unlimited access time on a star!
if expires == "*" {
return false;
}
// Compare current timestamp with expiration
let now = time();
let licenseTime = strtotime(expires);
if unlikely licenseTime === false {
throw new \Exception("Invalid date format: " . expires);
}
return now > licenseTime;
}
/**
* Check if a domain is allowed (exact or wildcard match)
*/
public function isDomainAllowed(string licensePath, string domain) -> bool
{
var contents, parsed, allowed, i, rule, suffix;
let contents = file_get_contents(licensePath);
if contents === false {
return false;
}
let parsed = json_decode(contents, true);
if typeof parsed !== "array" || !isset parsed["license"]["domains"] {
return false;
}
let allowed = parsed["license"]["domains"];
for i in range(0, count(allowed) - 1) {
let rule = allowed[i];
// Wildcard match: *.example.com
if starts_with(rule, "*.") {
let suffix = substr(rule, 1); // remove *
if ends_with(domain, suffix) {
return true;
}
} else {
// Exact match
if domain == rule {
return true;
}
}
}
return false;
}
}

@ -0,0 +1,125 @@
namespace HydraterBootloader;
class Loader
{
protected function evpBytesToKey(string password, string salt) -> array
{
var key, iv, d, d_i, result;
let d = "",
result = "";
while strlen(result) < 48 {
let d_i = md5(d . password . salt, true);
let d = d_i;
let result .= d_i;
}
let key = substr(result, 0, 32);
let iv = substr(result, 32, 16);
return [key, iv];
}
protected function getCacheDir() -> string
{
var path;
if is_writable("/dev/shm") {
let path = "/dev/shm/tank";
} else {
let path = "/tmp/tank"; // fallback
}
if !is_dir(path) {
mkdir(path, 0700, true);
}
return path;
}
protected function isExpired(string path, int maxAgeSeconds) -> bool
{
var mtime, now;
let mtime = filemtime(path);
if mtime === false {
return true;
}
let now = time();
return (now - mtime) > maxAgeSeconds;
}
protected function decrypt(string ciphertext, string password) -> string
{
var salt, key, iv, body, decrypted, args, result;
if substr(ciphertext, 0, 8) != "Salted__" {
throw new \Exception("Missing OpenSSL salt header.");
}
let salt = substr(ciphertext, 8, 8);
let body = substr(ciphertext, 16);
let result = this->evpBytesToKey(password, salt);
let key = result[0];
let iv = result[1];
let args = [
body,
"AES-256-CBC",
key,
1,
iv
];
let decrypted = call_user_func_array("openssl_decrypt", args);
if typeof decrypted != "string" {
throw new \Exception("Decryption failed.");
}
return decrypted;
}
public function run(string filePath, string password, bool useInclude = true, bool forceRefresh = false)
{
var ciphertext, plaintext, tmpPath, hash, cacheDir, expired;
if !file_exists(filePath) {
throw new \Exception("File does not exist: " . filePath);
}
let cacheDir = this->getCacheDir();
let hash = hash("sha256", filePath);
let tmpPath = cacheDir . "/aesphp_" . hash . ".php";
if useInclude && file_exists(tmpPath) && !forceRefresh {
let expired = this->isExpired(tmpPath, 28800); // 8 hours = 28800 seconds
if !expired {
require tmpPath;
return;
} else {
unlink(tmpPath); // Remove expired cache
}
}
let ciphertext = file_get_contents(filePath);
if !ciphertext {
throw new \Exception("Unable to read file: " . filePath);
}
let plaintext = this->decrypt(ciphertext, password);
if useInclude {
file_put_contents(tmpPath, plaintext);
if forceRefresh {
register_shutdown_function("unlink", tmpPath);
}
return require tmpPath;
} else {
return eval(plaintext);
}
}
}
Loading…
Cancel
Save