parent
1f34b7bbfb
commit
3aa76d8054
@ -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…
Reference in new issue