Skip to content

Commit df1f287

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 and number of session tickets.
1 parent 4f3c28a commit df1f287

15 files changed

+1627
-0
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: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
--TEST--
2+
TLS session resumption - num_tickets controls ticket generation (TLS 1.3)
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_num_tickets.pem.tmp';
12+
13+
$serverCode = <<<'CODE'
14+
$flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN;
15+
16+
// Test with num_tickets = 3 (should issue 3 tickets)
17+
$ctx = stream_context_create(['ssl' => [
18+
'local_cert' => '%s',
19+
'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_3_SERVER,
20+
'num_tickets' => 3, // Issue 3 tickets per connection
21+
]]);
22+
23+
$server = stream_socket_server('tlsv1.3://127.0.0.1:0', $errno, $errstr, $flags, $ctx);
24+
phpt_notify_server_start($server);
25+
26+
// Accept one connection
27+
$client = @stream_socket_accept($server, 30);
28+
if ($client) {
29+
fwrite($client, "Ticket test\n");
30+
// Keep connection open briefly to allow tickets to be sent
31+
usleep(100000); // 100ms
32+
fclose($client);
33+
}
34+
35+
phpt_notify(message: "SERVER_DONE");
36+
CODE;
37+
$serverCode = sprintf($serverCode, $certFile);
38+
39+
$clientCode = <<<'CODE'
40+
$ticketCount = 0;
41+
42+
$flags = STREAM_CLIENT_CONNECT;
43+
$ctx = stream_context_create(['ssl' => [
44+
'verify_peer' => false,
45+
'verify_peer_name' => false,
46+
'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT,
47+
'session_new_cb' => function($stream, $sessionId, $data) use (&$ticketCount) {
48+
$ticketCount++;
49+
}
50+
]]);
51+
52+
$client = stream_socket_client("tls://{{ ADDR }}", $errno, $errstr, 30, $flags, $ctx);
53+
if ($client) {
54+
$response = fgets($client);
55+
echo trim($response) . "\n";
56+
57+
// Keep connection open briefly to receive all tickets
58+
usleep(150000); // 150ms
59+
fclose($client);
60+
}
61+
62+
echo "Tickets received: $ticketCount\n";
63+
64+
$result = phpt_wait();
65+
echo trim($result) . "\n";
66+
CODE;
67+
68+
include 'CertificateGenerator.inc';
69+
$certificateGenerator = new CertificateGenerator();
70+
$certificateGenerator->saveNewCertAsFileWithKey('session_num_tickets_test', $certFile);
71+
72+
include 'ServerClientTestCase.inc';
73+
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
74+
?>
75+
--CLEAN--
76+
<?php
77+
@unlink(__DIR__ . DIRECTORY_SEPARATOR . 'session_num_tickets.pem.tmp');
78+
?>
79+
--EXPECTF--
80+
Ticket test
81+
Tickets received: 3
82+
SERVER_DONE

0 commit comments

Comments
 (0)