service add APP_SIGNATURE_SERVICE 3
This commit is contained in:
@@ -168,33 +168,30 @@ class JwtService
|
|||||||
self::abort(401, 'Invalid token issuer: expected ' . $this->issuer . ' but got ' . ($decoded->iss ?? 'none'));
|
self::abort(401, 'Invalid token issuer: expected ' . $this->issuer . ' but got ' . ($decoded->iss ?? 'none'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3.1 App Signature Verification (Security Layer)
|
// 3.1 App Signature Verification (Service Only)
|
||||||
$appSignature = $_SERVER['HTTP_X_APP_SIGNATURE'] ?? null;
|
|
||||||
if ($appSignature === null && function_exists('getallheaders')) {
|
|
||||||
$headers = array_change_key_case(getallheaders(), CASE_LOWER);
|
|
||||||
$appSignature = $headers['x-app-signature'] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// قائمة البصمات المعتمدة لكل تطبيق (يجب تعبئتها من ملف .env)
|
|
||||||
// APP_SIGNATURE_SERVICE, APP_SIGNATURE_DRIVER, APP_SIGNATURE_PASSENGER
|
|
||||||
$role = $decoded->role ?? 'unknown';
|
$role = $decoded->role ?? 'unknown';
|
||||||
$envKey = 'APP_SIGNATURE_' . strtoupper($role);
|
if ($role === 'service') {
|
||||||
$expectedSignature = getenv($envKey) ?: getenv('APP_SIGNATURE_HASH');
|
$appSignature = $_SERVER['HTTP_X_APP_SIGNATURE'] ?? null;
|
||||||
|
if ($appSignature === null && function_exists('getallheaders')) {
|
||||||
if (!empty($expectedSignature)) {
|
$headers = array_change_key_case(getallheaders(), CASE_LOWER);
|
||||||
if ($appSignature === null || !hash_equals($expectedSignature, $appSignature)) {
|
$appSignature = $headers['x-app-signature'] ?? null;
|
||||||
error_log("[SECURITY_ERROR] App Signature Mismatch/Missing! Role: $role | Expected: $expectedSignature | Got: " . ($appSignature ?? 'NONE') . " | User: $userId");
|
}
|
||||||
|
|
||||||
// الحظر النهائي: إذا كانت البصمة خاطئة، نرفض الطلب فوراً
|
// نقبل بصمة الـ Release أو الـ Debug
|
||||||
self::abort(403, 'App integrity check failed. Please update your app.');
|
$allowedSignatures = array_filter([
|
||||||
|
getenv('APP_SIGNATURE_SERVICE_RELEASE'),
|
||||||
|
getenv('APP_SIGNATURE_SERVICE_DEBUG'),
|
||||||
|
getenv('APP_SIGNATURE_HASH') // Fallback
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!empty($allowedSignatures)) {
|
||||||
|
if ($appSignature === null || !in_array($appSignature, $allowedSignatures)) {
|
||||||
|
error_log("[SECURITY_ERROR] App Signature Mismatch! Role: $role | Got: " . ($appSignature ?? 'NONE') . " | User: " . ($decoded->user_id ?? 'unknown'));
|
||||||
|
self::abort(403, 'App integrity check failed. Please use the official app.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// في حال لم يتم ضبط البصمة لهذا النوع من المستخدمين بعد، نسجلها فقط لتسهيل الإعداد
|
|
||||||
error_log("[SECURITY_INFO] Incoming App Signature for $role: " . ($appSignature ?? 'NONE') . " | User: $userId");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 4. User ID
|
// 4. User ID
|
||||||
$userId = $decoded->user_id ?? $decoded->sub ?? null;
|
$userId = $decoded->user_id ?? $decoded->sub ?? null;
|
||||||
if (!$userId) {
|
if (!$userId) {
|
||||||
@@ -238,13 +235,11 @@ class JwtService
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($fpInToken === null || $fpHeader === null) {
|
if ($fpInToken === null || $fpHeader === null) {
|
||||||
|
|
||||||
$allHeaders = json_encode(getallheaders());
|
$allHeaders = json_encode(getallheaders());
|
||||||
error_log("[SECURITY] Fingerprint missing | user: $userId | fpInToken: " . ($fpInToken ?? 'NULL') . " | fpHeader: " . ($fpHeader ?? 'NULL') . " | Headers: $allHeaders");
|
error_log("[SECURITY] Fingerprint missing | user: $userId | fpInToken: " . ($fpInToken ?? 'NULL') . " | fpHeader: " . ($fpHeader ?? 'NULL') . " | Headers: $allHeaders");
|
||||||
self::abort(403, 'Device verification required');
|
self::abort(403, 'Device verification required');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$expected = hash('sha256', $fpHeader . $this->fpPepper);
|
$expected = hash('sha256', $fpHeader . $this->fpPepper);
|
||||||
if (!hash_equals($expected, $fpInToken)) {
|
if (!hash_equals($expected, $fpInToken)) {
|
||||||
error_log("[SECURITY] Device mismatch | user: $userId | IP: " . ($_SERVER['REMOTE_ADDR'] ?? '?'));
|
error_log("[SECURITY] Device mismatch | user: $userId | IP: " . ($_SERVER['REMOTE_ADDR'] ?? '?'));
|
||||||
@@ -252,27 +247,25 @@ class JwtService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 8. HMAC — مطلوب للعمليات الحساسة (Wallet/Logout)
|
// 8. HMAC Verification (Derived Secret for Service)
|
||||||
$hmacHeader = $_SERVER['HTTP_X_HMAC_AUTH'] ?? null;
|
$hmacHeader = $_SERVER['HTTP_X_HMAC_AUTH'] ?? null;
|
||||||
if ($hmacHeader !== null) {
|
if ($hmacHeader !== null) {
|
||||||
$timestamp = $_SERVER['HTTP_X_TIMESTAMP'] ?? '';
|
$timestamp = $_SERVER['HTTP_X_TIMESTAMP'] ?? '';
|
||||||
$nonce = $_SERVER['HTTP_X_NONCE'] ?? '';
|
$nonce = $_SERVER['HTTP_X_NONCE'] ?? '';
|
||||||
$body = file_get_contents('php://input') ?: '';
|
$body = file_get_contents('php://input') ?: '';
|
||||||
|
|
||||||
// نشتق مفتاح الـ HMAC الخاص بهذا المستخدم (نفس المعادلة في login.php)
|
// اشتقاق مفتاح الـ HMAC الخاص بهذا المستخدم
|
||||||
$derivedSecret = hash_hmac('sha256', (string)$userId, $this->hmacSecret);
|
$userSecret = hash_hmac('sha256', (string)$userId, $this->hmacSecret);
|
||||||
|
|
||||||
// التوقيع يضم الـ Body + Timestamp + Nonce لمنع التكرار والتلاعب
|
// المعادلة الموحدة: Body + Timestamp + Nonce
|
||||||
$payloadToSign = $body . $timestamp . $nonce;
|
$payloadToSign = $body . $timestamp . $nonce;
|
||||||
$expectedHmac = hash_hmac('sha256', $payloadToSign, $derivedSecret);
|
$expectedHmac = hash_hmac('sha256', $payloadToSign, $userSecret);
|
||||||
|
|
||||||
|
|
||||||
if (!hash_equals($expectedHmac, $hmacHeader)) {
|
if (!hash_equals($expectedHmac, $hmacHeader)) {
|
||||||
error_log("[SECURITY] HMAC mismatch | user: $userId | IP: " . ($_SERVER['REMOTE_ADDR'] ?? '?'));
|
$debugMsg = "User: $userId | Expected: $expectedHmac | Got: $hmacHeader | DerivedSecret: $userSecret | PayloadToSign: " . strlen($payloadToSign) . " bytes | Body: '$body' | TS: '$timestamp' | Nonce: '$nonce'";
|
||||||
|
error_log("[SECURITY] HMAC mismatch | " . $debugMsg);
|
||||||
self::abort(403, 'Invalid HMAC signature');
|
self::abort(403, 'Invalid HMAC signature');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $decoded;
|
return $decoded;
|
||||||
@@ -307,7 +300,6 @@ class JwtService
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static function abort(int $code, string $message)
|
private static function abort(int $code, string $message)
|
||||||
|
|
||||||
{
|
{
|
||||||
error_log("[JWT_AUTH_FAILED] Code: $code | Message: $message | IP: " . ($_SERVER['REMOTE_ADDR'] ?? '?') . " | URI: " . ($_SERVER['REQUEST_URI'] ?? '?'));
|
error_log("[JWT_AUTH_FAILED] Code: $code | Message: $message | IP: " . ($_SERVER['REMOTE_ADDR'] ?? '?') . " | URI: " . ($_SERVER['REQUEST_URI'] ?? '?'));
|
||||||
http_response_code($code);
|
http_response_code($code);
|
||||||
|
|||||||
Reference in New Issue
Block a user