Skip to content
3 changes: 0 additions & 3 deletions app/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@
"ext-mcrypt": "*",
"cakephp/cakephp": "~2.9"
},
"require-dev": {
"phpunit/phpunit": "3.7.*"
},
"suggest": {
"cakephp/cakephp-codesniffer": "Easily check code formatting against the CakePHP coding standards."
},
Expand Down
18 changes: 16 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,26 @@
"ext-intl": "Required to use IntlDateFormatter instead of strftime, if not Symfony polyfill will be used."
},
"require-dev": {
"phpunit/phpunit": "^9.5",
"phpunit/phpunit": "^9.6.33",
"cakephp/cakephp-codesniffer": "^1.0.0"
},
"config": {
"vendor-dir": "vendors/",
"process-timeout": 0
"process-timeout": 0,
"policy": {
"advisories": {
"ignore-id": {
"PKSA-6vdd-n4sx-knhy": {
"on-audit": false,
"reason": "phpcs 1.x arbitrary shell execution (GHSA-mhfv-8rc9-w38c). squizlabs/php_codesniffer is pinned to 1.x by cakephp-codesniffer 1.x (CakePHP2 coding standard), a dev-only tool never shipped to production. Unblocks install only; still reported by composer audit. Temporary exception until a fix is backported."
},
"CVE-2026-67434": {
"on-audit": false,
"reason": "phpcs OS command injection (PKSA-rdkp-vv9z-mjkg / GHSA-hmqg-cxww-wqhq). Same rationale as PKSA-6vdd-n4sx-knhy."
}
}
}
}
},
"bin": [
"lib/Cake/Console/cake"
Expand Down
102 changes: 94 additions & 8 deletions lib/Cake/Test/Case/Network/Http/HttpSocketTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1832,21 +1832,107 @@ public function testPartialReset() {
}

/**
* Test that requests fail when peer verification fails.
* Test that requests fail when peer verification fails, using a local TLS server with a self-signed certificate.
*
* @return void
*/
public function testVerifyPeer() {
$this->skipIf(!extension_loaded('openssl'), 'OpenSSL is not enabled cannot test SSL.');
$socket = new HttpSocket();
$this->skipIf(!function_exists('proc_open'), 'proc_open is not available, cannot start the TLS fixture server.');

$descriptorSpec = array(
1 => array('pipe', 'w'),
2 => array('pipe', 'w'),
);
$process = null;
$pipes = array();
$configFile = null;
$pemFile = null;
$fixtureInteractionsComplete = false;
$processExitCode = null;

try {
$socket->get('https://tv.eurosport.com/');
$this->markTestSkipped('Found valid certificate, was expecting invalid certificate.');
} catch (SocketException $e) {
$message = $e->getMessage();
$this->skipIf(strpos($message, 'Invalid HTTP') !== false, 'Invalid HTTP Response received, skipping.');
$this->assertStringContainsString('Failed to enable crypto', $message);
$configFile = tempnam(sys_get_temp_dir(), 'cake_tls_config_');
$pemFile = tempnam(sys_get_temp_dir(), 'cake_tls_');
if ($configFile === false || $pemFile === false) {
$this->fail('Unable to create the TLS fixture temporary files.');
}

$process = proc_open(
array(
PHP_BINARY,
CAKE . 'Test' . DS . 'test_app' . DS . 'tls_server.php',
$configFile,
$pemFile,
),
$descriptorSpec,
$pipes
);
if (!is_resource($process)) {
$this->fail('Unable to start the TLS fixture server.');
}

stream_set_timeout($pipes[1], 10);
stream_set_blocking($pipes[2], false);
$fixture = json_decode(trim((string)fgets($pipes[1])), true);
if (!is_array($fixture) || !isset($fixture['port'])) {
$stderr = trim((string)stream_get_contents($pipes[2]));
$message = 'TLS fixture server did not start.';
if ($stderr !== '') {
$message .= ' ' . $stderr;
}
$this->fail($message);
}

$port = (int)$fixture['port'];
clearstatcache(true, $pemFile);
if ($port < 1 || $port > 65535 || !is_file($pemFile) || filesize($pemFile) < 1) {
$this->fail('TLS fixture server returned invalid startup information.');
}

$url = 'https://127.0.0.1:' . $port . '/';
$allowSelfSignedSocket = new HttpSocket(array(
'timeout' => 10,
'ssl_allow_self_signed' => true,
));
$response = $allowSelfSignedSocket->get($url);
$this->assertEquals(200, $response->code, 'The TLS fixture certificate must be valid for 127.0.0.1.');

$socket = new HttpSocket(array('timeout' => 10));
try {
$socket->get($url);
$this->fail('Peer verification must reject the self-signed certificate, but the request succeeded.');
} catch (SocketException $e) {
$this->assertStringContainsString('Failed to enable crypto', $e->getMessage());
}
$fixtureInteractionsComplete = true;
} finally {
foreach ($pipes as $pipe) {
if (is_resource($pipe)) {
fclose($pipe);
}
}
if (is_resource($process)) {
if (!$fixtureInteractionsComplete) {
proc_terminate($process);
}
$processExitCode = proc_close($process);
}
foreach (array($configFile, $pemFile) as $temporaryFile) {
if (is_string($temporaryFile)) {
clearstatcache(true, $temporaryFile);
if (is_file($temporaryFile)) {
@unlink($temporaryFile);
}
}
}
}

$this->assertEquals(0, $processExitCode, 'TLS fixture server exited with an error.');
clearstatcache(true, $configFile);
$this->assertFalse(is_file($configFile), 'TLS fixture OpenSSL config file was not removed.');
clearstatcache(true, $pemFile);
$this->assertFalse(is_file($pemFile), 'TLS fixture certificate file was not removed.');
}

/**
Expand Down
2 changes: 1 addition & 1 deletion lib/Cake/Test/Case/Utility/DebuggerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public function testExcerpt() {
$this->assertTrue(is_array($result));
$this->assertEquals(4, count($result));

$pattern = '/<code>.*?<span style\="color\: \#\d+">.*?&lt;\?php/';
$pattern = '/<code(?:\s[^>]*)?>.*?<span style="color: #[0-9A-Fa-f]{6}">.*?&lt;\?php/';
$this->assertMatchesRegularExpression($pattern, $result[0]);

$result = Debugger::excerpt(__FILE__, 11, 2);
Expand Down
151 changes: 151 additions & 0 deletions lib/Cake/Test/test_app/tls_server.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
<?php
/**
* TLS server fixture
*
* Helper for HttpSocketTest::testVerifyPeer(). Starts a TLS server on
* 127.0.0.1 using a runtime-generated self-signed certificate, so that
* peer verification deterministically fails when a client connects to it.
*
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @package Cake.Test.TestApp
* @since CakePHP(tm) v 2.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/

$server = null;
$exitCode = 0;

try {
if (!isset($argv[1], $argv[2])) {
throw new RuntimeException('TLS fixture temporary file paths were not provided.');
}
$configFile = $argv[1];
$pemFile = $argv[2];

$config = "[ req ]\n" .
"distinguished_name = req_distinguished_name\n" .
"req_extensions = v3_req\n" .
"prompt = no\n" .
"\n" .
"[ req_distinguished_name ]\n" .
"CN = 127.0.0.1\n" .
"\n" .
"[ v3_req ]\n" .
"basicConstraints = CA:FALSE\n" .
"keyUsage = critical, digitalSignature, keyEncipherment\n" .
"extendedKeyUsage = serverAuth\n" .
"subjectAltName = IP:127.0.0.1\n";
if (file_put_contents($configFile, $config) === false) {
throw new RuntimeException('Unable to write the TLS fixture OpenSSL config file.');
}

$pkey = openssl_pkey_new(array(
'config' => $configFile,
'private_key_bits' => 2048,
'private_key_type' => OPENSSL_KEYTYPE_RSA,
));
if ($pkey === false) {
throw new RuntimeException('Unable to generate the TLS fixture private key.');
}

$csr = openssl_csr_new(
array('commonName' => '127.0.0.1'),
$pkey,
array(
'config' => $configFile,
'digest_alg' => 'sha256',
'req_extensions' => 'v3_req',
)
);
if ($csr === false) {
throw new RuntimeException('Unable to generate the TLS fixture certificate request.');
}

$cert = openssl_csr_sign(
$csr,
null,
$pkey,
1,
array(
'config' => $configFile,
'digest_alg' => 'sha256',
'x509_extensions' => 'v3_req',
)
);
if ($cert === false) {
throw new RuntimeException('Unable to sign the TLS fixture certificate.');
}

if (!openssl_x509_export($cert, $certPem) || !openssl_pkey_export($pkey, $keyPem)) {
throw new RuntimeException('Unable to export the TLS fixture certificate.');
}

if (file_put_contents($pemFile, $certPem . $keyPem) === false) {
throw new RuntimeException('Unable to write the TLS fixture certificate.');
}

$context = stream_context_create(array(
'ssl' => array(
'local_cert' => $pemFile,
),
));
$server = @stream_socket_server(
'tls://127.0.0.1:0',
$errNo,
$errStr,
STREAM_SERVER_BIND | STREAM_SERVER_LISTEN,
$context
);
if ($server === false) {
throw new RuntimeException('Unable to start the TLS fixture server: ' . $errStr);
}

$name = stream_socket_get_name($server, false);
if ($name === false) {
throw new RuntimeException('Unable to read the TLS fixture server address.');
}
$port = substr($name, strrpos($name, ':') + 1);
$startupInfo = json_encode(array(
'port' => (int)$port,
));
if ($startupInfo === false) {
throw new RuntimeException('Unable to encode the TLS fixture startup information.');
}
fwrite(STDOUT, $startupInfo . "\n");
fflush(STDOUT);

for ($i = 0; $i < 2; $i++) {
$conn = @stream_socket_accept($server, 10);
if (is_resource($conn)) {
stream_set_timeout($conn, 10);
$request = '';
while (!feof($conn) && strpos($request, "\r\n\r\n") === false) {
$chunk = fread($conn, 1024);
if ($chunk === false) {
break;
}
$request .= $chunk;
}
fwrite($conn, "HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: close\r\n\r\n");
fflush($conn);
fclose($conn);
}
}
} catch (Throwable $e) {
fwrite(STDERR, $e->getMessage() . "\n");
$exitCode = 1;
} finally {
if (is_resource($server)) {
fclose($server);
}
}

exit($exitCode);
Loading