mirror of
https://github.com/avecms/AVE.cms.git
synced 2026-08-06 02:35:45 +00:00
78 lines
2.7 KiB
PHP
78 lines
2.7 KiB
PHP
<?php
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------------------
|
|
| AVE.cms
|
|
|--------------------------------------------------------------------------------------
|
|
| @package AVE.cms
|
|
| @file system/App/Common/SignedEventProof.php
|
|
| @author AVE.cms <support@ave-cms.ru>
|
|
| @copyright 2007-2026 (c) AVE.cms
|
|
| @link https://ave-cms.ru
|
|
| @version 3.3
|
|
*/
|
|
|
|
namespace App\Common;
|
|
|
|
defined('BASEPATH') || die('Direct access to this location is not allowed.');
|
|
|
|
/** Short-lived HMAC proof binding a public event to its server-side assignment. */
|
|
class SignedEventProof
|
|
{
|
|
protected static $secrets = array();
|
|
|
|
public static function issue($module, array $claims, $visitorHash, $ttl = 3600)
|
|
{
|
|
$claims['exp'] = time() + max(60, min(86400, (int) $ttl));
|
|
$claims['visitor'] = hash('sha256', (string) $visitorHash);
|
|
$payload = self::encode(json_encode($claims, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
|
return $payload . '.' . self::encode(hash_hmac('sha256', $payload, self::secret($module), true));
|
|
}
|
|
|
|
public static function verify($module, $proof, $visitorHash, array $expected = array())
|
|
{
|
|
$parts = explode('.', trim((string) $proof));
|
|
if (count($parts) !== 2 || !hash_equals(self::encode(hash_hmac('sha256', $parts[0], self::secret($module), true)), $parts[1])) {
|
|
return null;
|
|
}
|
|
|
|
$data = json_decode(self::decode($parts[0]), true);
|
|
if (!is_array($data) || empty($data['exp']) || (int) $data['exp'] < time()
|
|
|| empty($data['visitor']) || !hash_equals((string) $data['visitor'], hash('sha256', (string) $visitorHash))) {
|
|
return null;
|
|
}
|
|
|
|
foreach ($expected as $key => $value) {
|
|
if (!array_key_exists($key, $data) || (string) $data[$key] !== (string) $value) { return null; }
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
protected static function secret($module)
|
|
{
|
|
if (isset(self::$secrets[$module])) { return self::$secrets[$module]; }
|
|
self::$secrets[$module] = Lock::run('event-proof:' . $module, function () use ($module) {
|
|
$data = ModuleSecretStore::read($module);
|
|
if (empty($data['event_proof_key']) || !preg_match('/^[a-f0-9]{64}$/', (string) $data['event_proof_key'])) {
|
|
$data['event_proof_key'] = bin2hex(random_bytes(32));
|
|
ModuleSecretStore::write($module, $data);
|
|
}
|
|
|
|
return (string) $data['event_proof_key'];
|
|
});
|
|
return self::$secrets[$module];
|
|
}
|
|
|
|
protected static function encode($value)
|
|
{
|
|
return rtrim(strtr(base64_encode((string) $value), '+/', '-_'), '=');
|
|
}
|
|
|
|
protected static function decode($value)
|
|
{
|
|
$value = strtr((string) $value, '-_', '+/');
|
|
return (string) base64_decode($value . str_repeat('=', (4 - strlen($value) % 4) % 4), true);
|
|
}
|
|
}
|