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
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;
|
|
}
|
|
}
|
|
|
|
|