diff --git a/app/Http/Middleware/JwtAuthMiddleware.php b/app/Http/Middleware/JwtAuthMiddleware.php index b5d9f7f..99fd569 100644 --- a/app/Http/Middleware/JwtAuthMiddleware.php +++ b/app/Http/Middleware/JwtAuthMiddleware.php @@ -109,10 +109,11 @@ class JwtAuthMiddleware throw new \RuntimeException('JWT public key is empty'); } + $this->flushOpenSslErrors(); $keyResource = openssl_pkey_get_public($publicKey); if ($keyResource === false) { - throw new \RuntimeException(openssl_error_string() ?: 'OpenSSL could not read JWT public key'); + throw new \RuntimeException($this->openSslErrorMessage('OpenSSL could not read JWT public key')); } $result = openssl_verify( @@ -123,7 +124,7 @@ class JwtAuthMiddleware ); if ($result === false) { - throw new \RuntimeException(openssl_error_string() ?: 'OpenSSL could not verify JWT signature'); + throw new \RuntimeException($this->openSslErrorMessage('OpenSSL could not verify JWT signature')); } return $result === 1; @@ -131,6 +132,15 @@ class JwtAuthMiddleware private function normalizePublicKey(string $publicKey): string { + $publicKey = trim($publicKey); + + if ( + (str_starts_with($publicKey, '"') && str_ends_with($publicKey, '"')) || + (str_starts_with($publicKey, "'") && str_ends_with($publicKey, "'")) + ) { + $publicKey = substr($publicKey, 1, -1); + } + $publicKey = trim(str_replace(['\\r\\n', '\\n', '\\r', "\r\n", "\r"], "\n", $publicKey)); if ($publicKey === '') { @@ -139,23 +149,52 @@ class JwtAuthMiddleware if ( preg_match( - '/-----BEGIN PUBLIC KEY-----(.*?)-----END PUBLIC KEY-----/s', + '/-----BEGIN ([A-Z ]*PUBLIC KEY)-----(.*?)-----END \1-----/s', $publicKey, $matches ) ) { - $body = preg_replace('/\s+/', '', $matches[1]); + $type = $matches[1]; + $body = preg_replace('/[^A-Za-z0-9+\/=]/', '', $matches[2]); if ($body === '') { return ''; } - return "-----BEGIN PUBLIC KEY-----\n" + return "-----BEGIN {$type}-----\n" . chunk_split($body, 64, "\n") - . "-----END PUBLIC KEY-----\n"; + . "-----END {$type}-----\n"; } - return $publicKey; + if (!str_contains($publicKey, '-----BEGIN')) { + $body = preg_replace('/[^A-Za-z0-9+\/=]/', '', $publicKey); + + if (strlen($body) > 100) { + return "-----BEGIN PUBLIC KEY-----\n" + . chunk_split($body, 64, "\n") + . "-----END PUBLIC KEY-----\n"; + } + } + + return trim($publicKey) . "\n"; + } + + private function flushOpenSslErrors(): void + { + while (openssl_error_string() !== false) { + // Clear stale OpenSSL errors before reading the next operation result. + } + } + + private function openSslErrorMessage(string $fallback): string + { + $errors = []; + + while (($error = openssl_error_string()) !== false) { + $errors[] = $error; + } + + return $errors === [] ? $fallback : implode(' | ', $errors); } private function tokenIsExpired(array $payload): bool diff --git a/routes/api.php b/routes/api.php index 88ade54..ade1adf 100644 --- a/routes/api.php +++ b/routes/api.php @@ -30,31 +30,61 @@ Route::get('/health', function () { Route::get('/health-check-key', function () { $rawPublicKey = (string) config('jwt.public_key'); - $formattedPublicKey = trim(str_replace(['\\r\\n', '\\n', '\\r', "\r\n", "\r"], "\n", $rawPublicKey)); + $formattedPublicKey = trim($rawPublicKey); if ( - preg_match( - '/-----BEGIN PUBLIC KEY-----(.*?)-----END PUBLIC KEY-----/s', - $formattedPublicKey, - $matches - ) + (str_starts_with($formattedPublicKey, '"') && str_ends_with($formattedPublicKey, '"')) || + (str_starts_with($formattedPublicKey, "'") && str_ends_with($formattedPublicKey, "'")) ) { - $body = preg_replace('/\s+/', '', $matches[1]); - $formattedPublicKey = "-----BEGIN PUBLIC KEY-----\n" + $formattedPublicKey = substr($formattedPublicKey, 1, -1); + } + + $formattedPublicKey = trim(str_replace(['\\r\\n', '\\n', '\\r', "\r\n", "\r"], "\n", $formattedPublicKey)); + $pemType = null; + $bodyLength = null; + + if (preg_match('/-----BEGIN ([A-Z ]*PUBLIC KEY)-----(.*?)-----END \1-----/s', $formattedPublicKey, $matches)) { + $pemType = $matches[1]; + $body = preg_replace('/[^A-Za-z0-9+\/=]/', '', $matches[2]); + $bodyLength = strlen($body); + $formattedPublicKey = "-----BEGIN {$pemType}-----\n" . chunk_split($body, 64, "\n") - . "-----END PUBLIC KEY-----\n"; + . "-----END {$pemType}-----\n"; + } elseif (!str_contains($formattedPublicKey, '-----BEGIN')) { + $body = preg_replace('/[^A-Za-z0-9+\/=]/', '', $formattedPublicKey); + $bodyLength = strlen($body); + + if ($bodyLength > 100) { + $pemType = 'PUBLIC KEY'; + $formattedPublicKey = "-----BEGIN PUBLIC KEY-----\n" + . chunk_split($body, 64, "\n") + . "-----END PUBLIC KEY-----\n"; + } + } + + while (openssl_error_string() !== false) { + // Clear stale OpenSSL errors before testing the current key. } $publicKeyResource = openssl_pkey_get_public($formattedPublicKey); + $openSslErrors = []; + + while (($error = openssl_error_string()) !== false) { + $openSslErrors[] = $error; + } return response()->json([ 'raw_key_empty' => $rawPublicKey === '', 'raw_key_length' => strlen($rawPublicKey), 'formatted_key_length' => strlen($formattedPublicKey), + 'pem_type' => $pemType, + 'pem_body_length' => $bodyLength, 'has_begin_marker' => str_contains($rawPublicKey, '-----BEGIN PUBLIC KEY-----'), + 'has_rsa_begin_marker' => str_contains($rawPublicKey, '-----BEGIN RSA PUBLIC KEY-----'), 'has_end_marker' => str_contains($rawPublicKey, '-----END PUBLIC KEY-----'), + 'has_rsa_end_marker' => str_contains($rawPublicKey, '-----END RSA PUBLIC KEY-----'), 'openssl_accepted' => $publicKeyResource !== false, - 'openssl_error' => openssl_error_string(), + 'openssl_errors' => $openSslErrors, ]); }); diff --git a/routes/web.php b/routes/web.php index 5b24608..fbba580 100644 --- a/routes/web.php +++ b/routes/web.php @@ -17,31 +17,61 @@ Route::get('/health', function () { Route::get('/health-check-key', function () { $rawPublicKey = (string) config('jwt.public_key'); - $formattedPublicKey = trim(str_replace(['\\r\\n', '\\n', '\\r', "\r\n", "\r"], "\n", $rawPublicKey)); + $formattedPublicKey = trim($rawPublicKey); if ( - preg_match( - '/-----BEGIN PUBLIC KEY-----(.*?)-----END PUBLIC KEY-----/s', - $formattedPublicKey, - $matches - ) + (str_starts_with($formattedPublicKey, '"') && str_ends_with($formattedPublicKey, '"')) || + (str_starts_with($formattedPublicKey, "'") && str_ends_with($formattedPublicKey, "'")) ) { - $body = preg_replace('/\s+/', '', $matches[1]); - $formattedPublicKey = "-----BEGIN PUBLIC KEY-----\n" + $formattedPublicKey = substr($formattedPublicKey, 1, -1); + } + + $formattedPublicKey = trim(str_replace(['\\r\\n', '\\n', '\\r', "\r\n", "\r"], "\n", $formattedPublicKey)); + $pemType = null; + $bodyLength = null; + + if (preg_match('/-----BEGIN ([A-Z ]*PUBLIC KEY)-----(.*?)-----END \1-----/s', $formattedPublicKey, $matches)) { + $pemType = $matches[1]; + $body = preg_replace('/[^A-Za-z0-9+\/=]/', '', $matches[2]); + $bodyLength = strlen($body); + $formattedPublicKey = "-----BEGIN {$pemType}-----\n" . chunk_split($body, 64, "\n") - . "-----END PUBLIC KEY-----\n"; + . "-----END {$pemType}-----\n"; + } elseif (!str_contains($formattedPublicKey, '-----BEGIN')) { + $body = preg_replace('/[^A-Za-z0-9+\/=]/', '', $formattedPublicKey); + $bodyLength = strlen($body); + + if ($bodyLength > 100) { + $pemType = 'PUBLIC KEY'; + $formattedPublicKey = "-----BEGIN PUBLIC KEY-----\n" + . chunk_split($body, 64, "\n") + . "-----END PUBLIC KEY-----\n"; + } + } + + while (openssl_error_string() !== false) { + // Clear stale OpenSSL errors before testing the current key. } $publicKeyResource = openssl_pkey_get_public($formattedPublicKey); + $openSslErrors = []; + + while (($error = openssl_error_string()) !== false) { + $openSslErrors[] = $error; + } return response()->json([ 'raw_key_empty' => $rawPublicKey === '', 'raw_key_length' => strlen($rawPublicKey), 'formatted_key_length' => strlen($formattedPublicKey), + 'pem_type' => $pemType, + 'pem_body_length' => $bodyLength, 'has_begin_marker' => str_contains($rawPublicKey, '-----BEGIN PUBLIC KEY-----'), + 'has_rsa_begin_marker' => str_contains($rawPublicKey, '-----BEGIN RSA PUBLIC KEY-----'), 'has_end_marker' => str_contains($rawPublicKey, '-----END PUBLIC KEY-----'), + 'has_rsa_end_marker' => str_contains($rawPublicKey, '-----END RSA PUBLIC KEY-----'), 'openssl_accepted' => $publicKeyResource !== false, - 'openssl_error' => openssl_error_string(), + 'openssl_errors' => $openSslErrors, ]); });