PHP 8.4+ Framework
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

207 lines
6.4 KiB

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 = rnd_q();
if r === false {
return false;
}
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;
}
}