Skip to content

Commit f45fee4

Browse files
committed
Add OpenSSL TLS configurable session resumption support
This adds support for verious session options to stream ssl context. It allows setting new session callback and session data on client and get and delete session callbacks to server. The server also offers options to configure session cache parameters.
1 parent 275ec6f commit f45fee4

13 files changed

+1436
-2
lines changed
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
--TEST--
2+
TLS session resumption - server with cache disabled
3+
--EXTENSIONS--
4+
openssl
5+
--SKIPIF--
6+
<?php
7+
if (!function_exists("proc_open")) die("skip no proc_open");
8+
?>
9+
--FILE--
10+
<?php
11+
$certFile = __DIR__ . DIRECTORY_SEPARATOR . 'session_cache_disabled.pem.tmp';
12+
13+
$serverCode = <<<'CODE'
14+
$flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN;
15+
$ctx = stream_context_create(['ssl' => [
16+
'local_cert' => '%s',
17+
'session_cache' => false, /* Disable session caching */
18+
]]);
19+
20+
$server = stream_socket_server('tls://127.0.0.1:0', $errno, $errstr, $flags, $ctx);
21+
phpt_notify_server_start($server);
22+
23+
/* Accept two connections */
24+
for ($i = 0; $i < 2; $i++) {
25+
$client = @stream_socket_accept($server, 30);
26+
if ($client) {
27+
fwrite($client, "No cache connection " . ($i + 1) . "\n");
28+
fclose($client);
29+
}
30+
}
31+
32+
phpt_notify(message: "CACHE_DISABLED_TEST_DONE");
33+
CODE;
34+
$serverCode = sprintf($serverCode, $certFile);
35+
36+
$clientCode = <<<'CODE'
37+
$sessionData = null;
38+
39+
$flags = STREAM_CLIENT_CONNECT;
40+
$ctx = stream_context_create(['ssl' => [
41+
'verify_peer' => false,
42+
'verify_peer_name' => false,
43+
'session_new_cb' => function($stream, $sessionId, $data) use (&$sessionData) {
44+
$sessionData = $data;
45+
}
46+
]]);
47+
48+
/* First connection */
49+
$client1 = stream_socket_client("tls://{{ ADDR }}", $errno, $errstr, 30, $flags, $ctx);
50+
if ($client1) {
51+
echo trim(fgets($client1)) . "\n";
52+
fclose($client1);
53+
}
54+
55+
/* Second connection - server won't use cached session */
56+
$ctx2 = stream_context_create(['ssl' => [
57+
'verify_peer' => false,
58+
'verify_peer_name' => false,
59+
'session_data' => $sessionData,
60+
]]);
61+
62+
$client2 = stream_socket_client("tls://{{ ADDR }}", $errno, $errstr, 30, $flags, $ctx2);
63+
if ($client2) {
64+
echo trim(fgets($client2)) . "\n";
65+
fclose($client2);
66+
}
67+
68+
$result = phpt_wait();
69+
echo trim($result) . "\n";
70+
CODE;
71+
72+
include 'CertificateGenerator.inc';
73+
$certificateGenerator = new CertificateGenerator();
74+
$certificateGenerator->saveNewCertAsFileWithKey('session_disabled_test', $certFile);
75+
76+
include 'ServerClientTestCase.inc';
77+
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
78+
?>
79+
--CLEAN--
80+
<?php
81+
@unlink(__DIR__ . DIRECTORY_SEPARATOR . 'session_cache_disabled.pem.tmp');
82+
?>
83+
--EXPECTF--
84+
No cache connection 1
85+
No cache connection 2
86+
CACHE_DISABLED_TEST_DONE
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
--TEST--
2+
TLS session resumption - client basic resumption
3+
--EXTENSIONS--
4+
openssl
5+
--SKIPIF--
6+
<?php
7+
if (!function_exists("proc_open")) die("skip no proc_open");
8+
?>
9+
--FILE--
10+
<?php
11+
$certFile = __DIR__ . DIRECTORY_SEPARATOR . 'session_resumption_client.pem.tmp';
12+
13+
$serverCode = <<<'CODE'
14+
$flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN;
15+
$ctx = stream_context_create(['ssl' => [
16+
'local_cert' => '%s',
17+
]]);
18+
19+
$server = stream_socket_server('tls://127.0.0.1:0', $errno, $errstr, $flags, $ctx);
20+
phpt_notify_server_start($server);
21+
22+
/* Accept two connections */
23+
for ($i = 0; $i < 2; $i++) {
24+
$client = @stream_socket_accept($server, 30);
25+
if ($client) {
26+
fwrite($client, "Hello from server\n");
27+
fclose($client);
28+
}
29+
}
30+
CODE;
31+
$serverCode = sprintf($serverCode, $certFile);
32+
33+
$clientCode = <<<'CODE'
34+
$sessionData = '';
35+
$sessionReceived = false;
36+
37+
$flags = STREAM_CLIENT_CONNECT;
38+
$ctx = stream_context_create(['ssl' => [
39+
'verify_peer' => false,
40+
'verify_peer_name' => false,
41+
'session_new_cb' => function($stream, $sessionId, $sessionDataArg) use (&$sessionReceived, &$sessionData) {
42+
$sessionData = $sessionDataArg;
43+
$sessionReceived = true;
44+
}
45+
]]);
46+
47+
/* First connection - full handshake */
48+
$client1 = stream_socket_client("tls://{{ ADDR }}", $errno, $errstr, 30, $flags, $ctx);
49+
if ($client1) {
50+
$response = fgets($client1);
51+
echo "First connection: " . trim($response) . "\n";
52+
fclose($client1);
53+
}
54+
55+
var_dump($sessionReceived);
56+
var_dump(strlen($sessionData) > 0);
57+
58+
/* Second connection - resumed session */
59+
$ctx2 = stream_context_create(['ssl' => [
60+
'verify_peer' => false,
61+
'verify_peer_name' => false,
62+
'session_data' => $sessionData,
63+
]]);
64+
65+
$client2 = stream_socket_client("tls://{{ ADDR }}", $errno, $errstr, 30, $flags, $ctx2);
66+
if ($client2) {
67+
$response = fgets($client2);
68+
echo "Second connection: " . trim($response) . "\n";
69+
fclose($client2);
70+
}
71+
72+
echo "Session resumption test completed\n";
73+
CODE;
74+
75+
include 'CertificateGenerator.inc';
76+
$certificateGenerator = new CertificateGenerator();
77+
$certificateGenerator->saveNewCertAsFileWithKey('session_resumption_test', $certFile);
78+
79+
include 'ServerClientTestCase.inc';
80+
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
81+
?>
82+
--CLEAN--
83+
<?php
84+
@unlink(__DIR__ . DIRECTORY_SEPARATOR . 'session_resumption_client.pem.tmp');
85+
?>
86+
--EXPECTF--
87+
First connection: Hello from server
88+
bool(true)
89+
bool(true)
90+
Second connection: Hello from server
91+
Session resumption test completed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
--TEST--
2+
TLS session resumption - warning when trying to enable tickets with session_get_cb
3+
--EXTENSIONS--
4+
openssl
5+
--SKIPIF--
6+
<?php
7+
if (!function_exists("proc_open")) die("skip no proc_open");
8+
?>
9+
--FILE--
10+
<?php
11+
$certFile = __DIR__ . DIRECTORY_SEPARATOR . 'session_no_ticket.pem.tmp';
12+
13+
$serverCode = <<<'CODE'
14+
$flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN;
15+
16+
/* Trying to enable tickets with external cache - should warn */
17+
$ctx = stream_context_create(['ssl' => [
18+
'local_cert' => '%s',
19+
'session_context_id' => 'test-app',
20+
'no_ticket' => false, // Explicitly trying to enable tickets
21+
'session_new_cb' => function($stream, $sessionId, $sessionData) {
22+
// Store session
23+
},
24+
'session_get_cb' => function($stream, $sessionId) {
25+
return null;
26+
}
27+
]]);
28+
29+
$server = @stream_socket_server('tls://127.0.0.1:0', $errno, $errstr, $flags, $ctx);
30+
phpt_notify_server_start($server);
31+
32+
$client = @stream_socket_accept($server, 30);
33+
if ($client === false) {
34+
phpt_notify(message: "SERVER_FAILED_AS_EXPECTED");
35+
} else {
36+
phpt_notify(message: "SERVER_CREATED_UNEXPECTEDLY");
37+
fclose($server);
38+
}
39+
CODE;
40+
$serverCode = sprintf($serverCode, $certFile);
41+
42+
$clientCode = <<<'CODE'
43+
$flags = STREAM_CLIENT_CONNECT;
44+
45+
/* Try to use corrupted session data */
46+
$ctx = stream_context_create(['ssl' => [
47+
'verify_peer' => false,
48+
'verify_peer_name' => false,
49+
'session_data' => 'this_is_invalid_session_data',
50+
]]);
51+
52+
$client = @stream_socket_client("tls://{{ ADDR }}", $errno, $errstr, 30, $flags, $ctx);
53+
54+
if ($client === false) {
55+
echo "Connection failed as expected\n";
56+
}
57+
58+
$result = phpt_wait();
59+
echo trim($result) . "\n";
60+
CODE;
61+
62+
include 'CertificateGenerator.inc';
63+
$certificateGenerator = new CertificateGenerator();
64+
$certificateGenerator->saveNewCertAsFileWithKey('session_no_ticket_test', $certFile);
65+
66+
include 'ServerClientTestCase.inc';
67+
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
68+
?>
69+
--CLEAN--
70+
<?php
71+
@unlink(__DIR__ . DIRECTORY_SEPARATOR . 'session_no_ticket.pem.tmp');
72+
?>
73+
--EXPECT--
74+
Connection failed as expected
75+
SERVER_FAILED_AS_EXPECTED
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
--TEST--
2+
TLS session resumption - invalid callback throws TypeError
3+
--EXTENSIONS--
4+
openssl
5+
--SKIPIF--
6+
<?php
7+
if (!function_exists("proc_open")) die("skip no proc_open");
8+
?>
9+
--FILE--
10+
<?php
11+
$certFile = __DIR__ . DIRECTORY_SEPARATOR . 'session_invalid_cb.pem.tmp';
12+
13+
$serverCode = <<<'CODE'
14+
$flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN;
15+
$ctx = stream_context_create(['ssl' => [
16+
'local_cert' => '%s',
17+
]]);
18+
19+
$server = stream_socket_server('tls://127.0.0.1:0', $errno, $errstr, $flags, $ctx);
20+
phpt_notify_server_start($server);
21+
22+
$client = @stream_socket_accept($server, 30);
23+
if ($client) {
24+
fclose($client);
25+
}
26+
CODE;
27+
$serverCode = sprintf($serverCode, $certFile);
28+
29+
$clientCode = <<<'CODE'
30+
$flags = STREAM_CLIENT_CONNECT;
31+
32+
/* Try to use invalid callback */
33+
$ctx = stream_context_create(['ssl' => [
34+
'verify_peer' => false,
35+
'verify_peer_name' => false,
36+
'session_new_cb' => 'not_a_valid_function',
37+
]]);
38+
39+
try {
40+
$client = @stream_socket_client("tls://{{ ADDR }}", $errno, $errstr, 30, $flags, $ctx);
41+
echo "Should not reach here\n";
42+
} catch (TypeError $e) {
43+
echo "TypeError caught: " . (strpos($e->getMessage(), 'session_new_cb must be a valid callback') !== false ? "YES" : "NO");
44+
echo "\n";
45+
}
46+
CODE;
47+
48+
include 'CertificateGenerator.inc';
49+
$certificateGenerator = new CertificateGenerator();
50+
$certificateGenerator->saveNewCertAsFileWithKey('session_invalid_cb_test', $certFile);
51+
52+
include 'ServerClientTestCase.inc';
53+
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
54+
?>
55+
--CLEAN--
56+
<?php
57+
@unlink(__DIR__ . DIRECTORY_SEPARATOR . 'session_invalid_cb.pem.tmp');
58+
?>
59+
--EXPECTF--
60+
TypeError caught: YES
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
--TEST--
2+
TLS session resumption - invalid session data is fatal
3+
--EXTENSIONS--
4+
openssl
5+
--SKIPIF--
6+
<?php
7+
if (!function_exists("proc_open")) die("skip no proc_open");
8+
?>
9+
--FILE--
10+
<?php
11+
$certFile = __DIR__ . DIRECTORY_SEPARATOR . 'session_invalid.pem.tmp';
12+
13+
$serverCode = <<<'CODE'
14+
$flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN;
15+
$ctx = stream_context_create(['ssl' => [
16+
'local_cert' => '%s',
17+
]]);
18+
19+
$server = stream_socket_server('tls://127.0.0.1:0', $errno, $errstr, $flags, $ctx);
20+
phpt_notify_server_start($server);
21+
22+
$client = @stream_socket_accept($server, 30);
23+
if ($client) {
24+
fclose($client);
25+
}
26+
CODE;
27+
$serverCode = sprintf($serverCode, $certFile);
28+
29+
$clientCode = <<<'CODE'
30+
$flags = STREAM_CLIENT_CONNECT;
31+
32+
/* Try to use corrupted session data */
33+
$ctx = stream_context_create(['ssl' => [
34+
'verify_peer' => false,
35+
'verify_peer_name' => false,
36+
'session_data' => 'this_is_invalid_session_data',
37+
]]);
38+
39+
$client = stream_socket_client("tls://{{ ADDR }}", $errno, $errstr, 30, $flags, $ctx);
40+
41+
if ($client === false) {
42+
echo "Connection failed as expected\n";
43+
}
44+
CODE;
45+
46+
include 'CertificateGenerator.inc';
47+
$certificateGenerator = new CertificateGenerator();
48+
$certificateGenerator->saveNewCertAsFileWithKey('session_invalid_test', $certFile);
49+
50+
include 'ServerClientTestCase.inc';
51+
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
52+
?>
53+
--CLEAN--
54+
<?php
55+
@unlink(__DIR__ . DIRECTORY_SEPARATOR . 'session_invalid.pem.tmp');
56+
?>
57+
--EXPECTF--
58+
59+
Warning: stream_socket_client(): Invalid or corrupted session_data, falling back to full handshake in %s on line %d
60+
61+
Warning: stream_socket_client(): Failed to enable crypto in %s on line %d
62+
63+
Warning: stream_socket_client(): Unable to connect to %s in %s on line %d
64+
Connection failed as expected

0 commit comments

Comments
 (0)