2026-06-23 17:53:34 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
namespace App\Lib\Axepta;
|
|
|
|
|
|
|
|
|
|
use Carbon\Carbon;
|
|
|
|
|
use Carbon\CarbonInterface;
|
|
|
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
|
|
|
|
|
|
class AxeptaSignatureVerifier
|
|
|
|
|
{
|
|
|
|
|
private const int PERMITTED_TIMESTAMP_SKEW_IN_MINUTES = 5;
|
2026-09-25 16:25:31 +02:00
|
|
|
|
2026-06-23 17:53:34 +02:00
|
|
|
private const string USING_API_VERSION = 'v1';
|
|
|
|
|
|
|
|
|
|
public function __construct(
|
|
|
|
|
private readonly AxeptaConfig $config,
|
|
|
|
|
private readonly LoggerInterface $logger
|
2026-09-25 16:25:31 +02:00
|
|
|
) {
|
|
|
|
|
}
|
2026-06-23 17:53:34 +02:00
|
|
|
|
|
|
|
|
public function verify(string $version, string $signature, string $timestamp, string $rawPayload): bool
|
|
|
|
|
{
|
|
|
|
|
$time = Carbon::createFromTimestampUTC($timestamp);
|
|
|
|
|
|
2026-09-25 16:25:31 +02:00
|
|
|
return $this->isValidSignatureVersion($version) && $this->requestTimestampIsAcceptable($time) && $this->isValidSignature($signature, $time, $rawPayload);
|
2026-06-23 17:53:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function isValidSignatureVersion(string $version): bool
|
|
|
|
|
{
|
|
|
|
|
if ($version !== self::USING_API_VERSION) {
|
2026-09-25 16:25:31 +02:00
|
|
|
$this->logger->error('Version is not accepted', [
|
|
|
|
|
'request_version' => $version,
|
|
|
|
|
'accepted_version' => self::USING_API_VERSION,
|
2026-06-23 17:53:34 +02:00
|
|
|
]);
|
2026-09-25 16:25:31 +02:00
|
|
|
|
2026-06-23 17:53:34 +02:00
|
|
|
return false;
|
|
|
|
|
}
|
2026-09-25 16:25:31 +02:00
|
|
|
|
2026-06-23 17:53:34 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function requestTimestampIsAcceptable(CarbonInterface $timestamp): bool
|
|
|
|
|
{
|
|
|
|
|
$calculatedDiff = Carbon::now('UTC')->diffInMinutes($timestamp, true);
|
|
|
|
|
if ($calculatedDiff > self::PERMITTED_TIMESTAMP_SKEW_IN_MINUTES) {
|
2026-09-25 16:25:31 +02:00
|
|
|
$this->logger->error('request timestamp skew is not matched', [
|
|
|
|
|
'request_timestamp' => $timestamp,
|
|
|
|
|
'diff' => $calculatedDiff,
|
|
|
|
|
'max_skew_in_minutes' => self::PERMITTED_TIMESTAMP_SKEW_IN_MINUTES,
|
2026-06-23 17:53:34 +02:00
|
|
|
]);
|
2026-09-25 16:25:31 +02:00
|
|
|
|
2026-06-23 17:53:34 +02:00
|
|
|
return false;
|
|
|
|
|
}
|
2026-09-25 16:25:31 +02:00
|
|
|
|
2026-06-23 17:53:34 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function isValidSignature(
|
|
|
|
|
#[\SensitiveParameter] string $signature,
|
|
|
|
|
CarbonInterface $timestamp,
|
|
|
|
|
#[\SensitiveParameter] string $rawPayload
|
|
|
|
|
): bool {
|
|
|
|
|
$providedSignature = $this->extractSignatureFromCurrentVersion($signature);
|
|
|
|
|
|
2026-09-25 16:25:31 +02:00
|
|
|
if (! $providedSignature) {
|
2026-06-23 17:53:34 +02:00
|
|
|
$this->logger->error("Could not extract signature from '{$signature}'");
|
2026-09-25 16:25:31 +02:00
|
|
|
|
2026-06-23 17:53:34 +02:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$expectedSignature = hash_hmac('sha256', "{$timestamp->timestamp}.{$rawPayload}", $this->config->hmac);
|
|
|
|
|
$signatureIsValid = hash_equals(
|
|
|
|
|
$expectedSignature,
|
|
|
|
|
$providedSignature
|
|
|
|
|
);
|
|
|
|
|
|
2026-09-25 16:25:31 +02:00
|
|
|
if (! $signatureIsValid) {
|
|
|
|
|
$this->logger->debug('Could not validate expected signature', [
|
2026-06-23 17:53:34 +02:00
|
|
|
'request_signature' => $signature,
|
2026-09-25 16:25:31 +02:00
|
|
|
'expected' => $expectedSignature,
|
2026-06-23 17:53:34 +02:00
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $signatureIsValid;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function extractSignatureFromCurrentVersion(#[\SensitiveParameter] string $signature): ?string
|
|
|
|
|
{
|
|
|
|
|
$signatures = explode(',', $signature);
|
|
|
|
|
|
|
|
|
|
foreach ($signatures as $sig) {
|
|
|
|
|
$parts = explode('=', trim($sig));
|
|
|
|
|
|
|
|
|
|
if (count($parts) === 2 && trim($parts[0]) === self::USING_API_VERSION) {
|
|
|
|
|
$hex = trim($parts[1]);
|
|
|
|
|
|
|
|
|
|
if (strlen($hex) === 64) {
|
|
|
|
|
return strtolower($hex);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|