ldc-legaulois/app/Lib/Axepta/AxeptaSignatureVerifier.php
2026-09-25 16:25:31 +02:00

107 lines
3.2 KiB
PHP

<?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;
private const string USING_API_VERSION = 'v1';
public function __construct(
private readonly AxeptaConfig $config,
private readonly LoggerInterface $logger
) {
}
public function verify(string $version, string $signature, string $timestamp, string $rawPayload): bool
{
$time = Carbon::createFromTimestampUTC($timestamp);
return $this->isValidSignatureVersion($version) && $this->requestTimestampIsAcceptable($time) && $this->isValidSignature($signature, $time, $rawPayload);
}
private function isValidSignatureVersion(string $version): bool
{
if ($version !== self::USING_API_VERSION) {
$this->logger->error('Version is not accepted', [
'request_version' => $version,
'accepted_version' => self::USING_API_VERSION,
]);
return false;
}
return true;
}
private function requestTimestampIsAcceptable(CarbonInterface $timestamp): bool
{
$calculatedDiff = Carbon::now('UTC')->diffInMinutes($timestamp, true);
if ($calculatedDiff > self::PERMITTED_TIMESTAMP_SKEW_IN_MINUTES) {
$this->logger->error('request timestamp skew is not matched', [
'request_timestamp' => $timestamp,
'diff' => $calculatedDiff,
'max_skew_in_minutes' => self::PERMITTED_TIMESTAMP_SKEW_IN_MINUTES,
]);
return false;
}
return true;
}
private function isValidSignature(
#[\SensitiveParameter] string $signature,
CarbonInterface $timestamp,
#[\SensitiveParameter] string $rawPayload
): bool {
$providedSignature = $this->extractSignatureFromCurrentVersion($signature);
if (! $providedSignature) {
$this->logger->error("Could not extract signature from '{$signature}'");
return false;
}
$expectedSignature = hash_hmac('sha256', "{$timestamp->timestamp}.{$rawPayload}", $this->config->hmac);
$signatureIsValid = hash_equals(
$expectedSignature,
$providedSignature
);
if (! $signatureIsValid) {
$this->logger->debug('Could not validate expected signature', [
'request_signature' => $signature,
'expected' => $expectedSignature,
]);
}
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;
}
}