63 lines
2.1 KiB
PHP
63 lines
2.1 KiB
PHP
<?php
|
|
/**
|
|
* HMAC Request Signature Middleware
|
|
*
|
|
* Verifies that incoming requests are signed with a shared secret,
|
|
* preventing replay attacks and ensuring request integrity.
|
|
*
|
|
* Client must send:
|
|
* X-Timestamp: Unix timestamp (seconds)
|
|
* X-HMAC-Signature: HMAC-SHA256(timestamp + "." + raw_body, HMAC_SECRET_KEY)
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Middleware;
|
|
|
|
use App\Core\Security;
|
|
|
|
final class HmacMiddleware
|
|
{
|
|
/**
|
|
* @param int $maxAgeSeconds Max age for replay attack window (default: 5 minutes)
|
|
*/
|
|
public static function verify(int $maxAgeSeconds = 300): void
|
|
{
|
|
$headers = getallheaders();
|
|
$signature = $headers['X-HMAC-Signature'] ?? $headers['x-hmac-signature'] ?? '';
|
|
$timestamp = $headers['X-Timestamp'] ?? $headers['x-timestamp'] ?? '';
|
|
|
|
// 1. Ensure both headers are present
|
|
if (empty($signature) || empty($timestamp)) {
|
|
json_error('Missing HMAC signature or timestamp', 401);
|
|
}
|
|
|
|
// 2. Validate timestamp is numeric
|
|
if (!ctype_digit((string)$timestamp)) {
|
|
json_error('Invalid timestamp format', 401);
|
|
}
|
|
|
|
// 3. Replay attack prevention — reject stale requests
|
|
$age = abs(time() - (int)$timestamp);
|
|
if ($age > $maxAgeSeconds) {
|
|
json_error('Request expired. Check your system clock.', 401);
|
|
}
|
|
|
|
// 4. Build the expected signature
|
|
$body = file_get_contents('php://input');
|
|
$payload = $timestamp . '.' . $body;
|
|
$secret = env('HMAC_SECRET_KEY');
|
|
|
|
if (!$secret || strlen($secret) < 32) {
|
|
error_log('FATAL: HMAC_SECRET_KEY is missing or too short in .env');
|
|
json_error('Server configuration error', 500);
|
|
}
|
|
|
|
// 5. Verify using constant-time comparison (prevents timing attacks)
|
|
if (!Security::verifySignature($payload, $signature, $secret)) {
|
|
error_log("HMAC verification failed for " . ($_SERVER['REQUEST_URI'] ?? ''));
|
|
json_error('Invalid request signature', 401);
|
|
}
|
|
}
|
|
}
|