Update: 2026-06-16 01:17:28

This commit is contained in:
Hamza-Ayed
2026-06-16 01:17:29 +03:00
parent 04943e3d52
commit fc58529b09
56 changed files with 1149 additions and 1314 deletions

View File

@@ -36,18 +36,18 @@ class JwtService
public function __construct(?Redis $redis = null)
{
$this->secretKey = trim(file_get_contents('/home/siro-api/.secret_key'));
// ✅ FIX C-02: استخدام getenv بدلاً من file_get_contents الثابت
$keyPath = getenv('JWT_SECRET_KEY_PATH');
if ($keyPath && file_exists($keyPath)) {
$this->secretKey = trim(file_get_contents($keyPath));
} else {
$this->secretKey = getenv('JWT_SECRET_KEY') ?: '';
}
$this->hmacSecret = getenv('SECRET_KEY_HMAC') ?: '';
$this->fpPepper = getenv('FP_PEPPER') ?: '';
$this->issuer = (string)(getenv('APP_ISSUER') ?: '');
$this->redis = $redis;
// Debugging fpPepper
if (empty($this->fpPepper)) {
error_log("[JWT_DEBUG] fpPepper is EMPTY in constructor");
} else {
error_log("[JWT_DEBUG] fpPepper is SET (length: " . strlen($this->fpPepper) . ")");
}
}
@@ -144,19 +144,8 @@ class JwtService
} catch (ExpiredException $e) {
self::abort(401, 'Token expired');
} catch (SignatureInvalidException $e) {
// محاولة فك التشفير بمفتاح المحفظة (Wallet secret fallback)
$payKeyPath = '/home/siro-api/.secret_key_pay';
$payKey = file_exists($payKeyPath) ? trim(file_get_contents($payKeyPath)) : '';
if ($payKey) {
try {
$decoded = JWT::decode($token, new Key($payKey, self::ALGO));
} catch (Exception $e2) {
self::abort(401, 'Invalid token signature');
}
} else {
self::abort(401, 'Invalid token signature');
}
// ممنوع استخدام أي مفتاح آخر - مفتاح JWT واحد فقط
self::abort(401, 'Invalid token signature');
} catch (BeforeValidException $e) {
self::abort(401, 'Token not yet valid');
} catch (Exception $e) {
@@ -262,12 +251,11 @@ class JwtService
$expectedHmac = hash_hmac('sha256', $payloadToSign, $userSecret);
if (!hash_equals($expectedHmac, $hmacHeader)) {
$debugMsg = "User: $userId | Expected: $expectedHmac | Got: $hmacHeader | DerivedSecret: $userSecret | MasterSecret(4): " . substr($this->hmacSecret, 0, 4) . " | Body($bodyLen): '$body' | TS: '$timestamp' | Nonce: '$nonce'";
$bodyLen = strlen($body);
error_log("[SECURITY] HMAC mismatch | " . $debugMsg);
// TEMPORARY: expose debug in response for diagnosis
error_log("[SECURITY] HMAC mismatch | User: $userId | BodyLen: $bodyLen | TS: '$timestamp'");
// ✅ FIX H-02: إزالة معلومات الـ Debug من الاستجابة
http_response_code(403);
echo json_encode(['error' => 'HMAC_DEBUG', 'debug' => $debugMsg]);
echo json_encode(['error' => 'Request verification failed']);
exit;
}
}
@@ -288,7 +276,13 @@ class JwtService
{
$keyPath = getenv('INTERNAL_SOCKET_KEY_PATH');
$sent = $_SERVER['HTTP_X_INTERNAL_KEY'] ?? '';
$expected = (file_exists($keyPath) ? trim(file_get_contents($keyPath)) : '') ?: 'Siro_Secure_Bridge_Key_2026_@!socket';
$expected = '';
if ($keyPath && file_exists($keyPath)) {
$expected = trim(file_get_contents($keyPath));
}
if (!$expected) {
$expected = getenv('INTERNAL_SOCKET_KEY');
}
if (!$expected || !hash_equals($expected, $sent)) {
error_log('[SECURITY] Invalid internal key from: ' . ($_SERVER['REMOTE_ADDR'] ?? '?'));

View File

@@ -8,7 +8,7 @@ declare(strict_types=1);
// 1. إعدادات الأخطاء والـ Headers الأساسية
// اجعل القيمة true لتفعيل عرض الأخطاء (التطوير)، أو false لإخفائها (التشغيل الفعلي)
$debugMode = true;
$debugMode = getenv('APP_DEBUG') === 'true';
if ($debugMode) {
error_reporting(E_ALL);
@@ -26,10 +26,16 @@ if (!file_exists(dirname($logPath)) || !is_writable(dirname($logPath))) {
}
ini_set('error_log', $logPath);
header_remove('X-Powered-By');
header('Content-Type: application/json; charset=UTF-8');
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY');
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
header("Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; frame-ancestors 'none'");
header("Referrer-Policy: strict-origin-when-cross-origin");
header("Permissions-Policy: geolocation=(), microphone=(), camera=()");
header("X-XSS-Protection: 1; mode=block");
// CORS (يجب تخصيصه في endpoints مخصصة إن لزم، لكن هذا افتراضي)
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');

View File

@@ -76,7 +76,15 @@ function result(int $count): void
}
function sendEmail(string $from, string $to, string $title, string $body): void
{
$header = "From: $from\nCC: $from";
$from = str_replace(["\r", "\n", "\r\n"], '', $from);
$to = str_replace(["\r", "\n", "\r\n"], '', $to);
$title = str_replace(["\r", "\n", "\r\n"], '', $title);
$header = "From: $from\r\n";
$header .= "Reply-To: $from\r\n";
$header .= "MIME-Version: 1.0\r\n";
$header .= "Content-Type: text/html; charset=UTF-8\r\n";
mail($to, $title, $body, $header);
}
@@ -155,7 +163,7 @@ function securityLog(string $message, array $context = []): void
{
$logDir = __DIR__ . '/../logs';
if (!is_dir($logDir)) {
@mkdir($logDir, 0777, true);
@mkdir($logDir, 0750, true);
}
$entry = date('Y-m-d H:i:s') . ' [SECURITY] ' . $message;
if ($context) $entry .= ' | ' . json_encode($context, JSON_UNESCAPED_UNICODE);
@@ -166,7 +174,7 @@ function appLog(string $message, string $level = 'INFO'): void
{
$logDir = __DIR__ . '/../logs';
if (!is_dir($logDir)) {
@mkdir($logDir, 0777, true);
@mkdir($logDir, 0750, true);
}
$entry = date('Y-m-d H:i:s') . " [$level] " . $message;
@error_log($entry . PHP_EOL, 3, $logDir . '/app.log');
@@ -176,7 +184,7 @@ function uploadLog(string $message, string $level = 'INFO', array $context = [])
{
$logDir = __DIR__ . '/../logs';
if (!is_dir($logDir)) {
@mkdir($logDir, 0777, true);
@mkdir($logDir, 0750, true);
}
if (!isset($context['ip'])) {
@@ -212,3 +220,17 @@ function debugLog(string $message): void
{
appLog($message, 'DEBUG');
}
function getInternalSocketKey(): string
{
$key = getenv('INTERNAL_SOCKET_KEY');
if ($key) {
return trim($key);
}
$path = getenv('INTERNAL_SOCKET_KEY_PATH') ?: '/home/siro-api/.internal_socket_key';
if (file_exists($path)) {
return trim((string)@file_get_contents($path));
}
return '';
}