Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ext/openssl/config.w32
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ if (PHP_OPENSSL != "no") {
var ret = SETUP_OPENSSL("openssl", PHP_OPENSSL);

if (ret >= 2) {
EXTENSION("openssl", "openssl.c openssl_pwhash.c openssl_backend_common.c openssl_backend_v1.c openssl_backend_v3.c xp_ssl.c");
EXTENSION("openssl", "openssl.c openssl_pwhash.c openssl_backend_common.c openssl_backend_v1.c openssl_backend_v3.c xp_bio.c xp_ssl.c xp_dtls.c");
AC_DEFINE("HAVE_OPENSSL_EXT", 1, "Define to 1 if the PHP extension 'openssl' is available.");
if (PHP_OPENSSL_LEGACY_PROVIDER != "no") {
AC_DEFINE("LOAD_OPENSSL_LEGACY_PROVIDER", 1, "Define to 1 to load the OpenSSL legacy algorithm provider in addition to the default provider.");
Expand Down
2 changes: 1 addition & 1 deletion ext/openssl/config0.m4
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ PHP_ARG_WITH([openssl-argon2],

if test "$PHP_OPENSSL" != "no"; then
PHP_NEW_EXTENSION([openssl],
[openssl.c openssl_pwhash.c openssl_backend_common.c openssl_backend_v1.c openssl_backend_v3.c xp_ssl.c],
[openssl.c openssl_pwhash.c openssl_backend_common.c openssl_backend_v1.c openssl_backend_v3.c xp_bio.c xp_ssl.c xp_dtls.c],
[$ext_shared])
PHP_SUBST([OPENSSL_SHARED_LIBADD])
PHP_SETUP_OPENSSL([OPENSSL_SHARED_LIBADD],
Expand Down
26 changes: 25 additions & 1 deletion ext/openssl/openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,8 @@ PHP_MINIT_FUNCTION(openssl)
* OpenSSL callbacks */
ssl_stream_data_index = SSL_get_ex_new_index(0, "PHP stream index", NULL, NULL, NULL);

php_openssl_bio_minit();

php_stream_xport_register("ssl", php_openssl_ssl_socket_factory);
#if OPENSSL_VERSION_NUMBER < 0x40000000L && !defined(OPENSSL_NO_SSL3)
php_stream_xport_register("sslv3", php_openssl_ssl_socket_factory);
Expand All @@ -825,6 +827,16 @@ PHP_MINIT_FUNCTION(openssl)
php_stream_xport_register("tlsv1.2", php_openssl_ssl_socket_factory);
php_stream_xport_register("tlsv1.3", php_openssl_ssl_socket_factory);

#if !defined(OPENSSL_NO_DTLS) && !defined(OPENSSL_NO_DTLS1_2)
php_stream_xport_register("dtls", php_openssl_ssl_socket_factory);
php_stream_xport_register("dtlsv1.2", php_openssl_ssl_socket_factory);
#if defined(DTLS1_3_VERSION) && !defined(OPENSSL_NO_DTLS1_3)
php_stream_xport_register("dtlsv1.3", php_openssl_ssl_socket_factory);
#endif
/* override the default udp socket provider */
php_stream_xport_register("udp", php_openssl_ssl_socket_factory);
#endif

/* override the default tcp socket provider */
php_stream_xport_register("tcp", php_openssl_ssl_socket_factory);

Expand Down Expand Up @@ -899,6 +911,18 @@ PHP_MSHUTDOWN_FUNCTION(openssl)
php_stream_xport_unregister("tlsv1.2");
php_stream_xport_unregister("tlsv1.3");

#if !defined(OPENSSL_NO_DTLS) && !defined(OPENSSL_NO_DTLS1_2)
php_stream_xport_unregister("dtls");
php_stream_xport_unregister("dtlsv1.2");
#if defined(DTLS1_3_VERSION) && !defined(OPENSSL_NO_DTLS1_3)
php_stream_xport_unregister("dtlsv1.3");
#endif
/* reinstate the default udp handler */
php_stream_xport_register("udp", php_stream_generic_socket_factory);
#endif

php_openssl_bio_mshutdown();

/* reinstate the default tcp handler */
php_stream_xport_register("tcp", php_stream_generic_socket_factory);

Expand Down Expand Up @@ -2742,7 +2766,7 @@ PHP_FUNCTION(openssl_pkey_get_details)
array_init(return_value);
add_assoc_long(return_value, "bits", EVP_PKEY_bits(pkey));
add_assoc_stringl(return_value, "key", pbio, pbio_len);

zend_long ktype = php_openssl_pkey_get_details(return_value, pkey);

add_assoc_long(return_value, "type", ktype);
Expand Down
2 changes: 2 additions & 0 deletions ext/openssl/php_openssl.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ ZEND_TSRMLS_CACHE_EXTERN();
#endif

php_stream_transport_factory_func php_openssl_ssl_socket_factory;
void php_openssl_bio_minit(void);
void php_openssl_bio_mshutdown(void);

void php_openssl_store_errors(void);
void php_openssl_errors_set_mark(void);
Expand Down
51 changes: 51 additions & 0 deletions ext/openssl/tests/dtls13_basic.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
--TEST--
dtls:// DTLS 1.3 is negotiated by default between a PHP server and a PHP client
--EXTENSIONS--
openssl
--SKIPIF--
<?php
require __DIR__ . '/dtls_skipif.inc';
if (!function_exists('proc_open')) die('skip proc_open() not available');
if (!in_array('dtlsv1.3', stream_get_transports(), true)) die('skip DTLS 1.3 not available');
?>
--FILE--
<?php
$certFile = __DIR__ . DIRECTORY_SEPARATOR . 'dtls13_basic.pem.tmp';
include 'CertificateGenerator.inc';
(new CertificateGenerator())->saveNewCertAsFileWithKey('dtls-server', $certFile);

$serverCode = <<<'CODE'
$ctx = stream_context_create(['ssl' => ['local_cert' => '%s']]);
$server = stream_socket_server('dtls://127.0.0.1:0', $errno, $errstr,
STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctx);
phpt_notify_server_start($server);

$peer = stream_socket_accept($server, 5);
fwrite($peer, strtoupper(fread($peer, 8192)) . ' ' . stream_get_meta_data($peer)['crypto']['protocol']);
fclose($peer);
fclose($server);
CODE;
$serverCode = sprintf($serverCode, $certFile);

$clientCode = <<<'CODE'
$ctx = stream_context_create(['ssl' => ['verify_peer' => false, 'verify_peer_name' => false]]);
$client = stream_socket_client('dtls://{{ ADDR }}', $errno, $errstr, 5,
STREAM_CLIENT_CONNECT, $ctx);
var_dump($client !== false);
echo "client: ", stream_get_meta_data($client)['crypto']['protocol'], "\n";
fwrite($client, "ping");
var_dump(fread($client, 8192));
fclose($client);
CODE;

include 'ServerClientTestCase.inc';
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
?>
--CLEAN--
<?php
@unlink(__DIR__ . DIRECTORY_SEPARATOR . 'dtls13_basic.pem.tmp');
?>
--EXPECT--
bool(true)
client: DTLSv1.3
string(13) "PING DTLSv1.3"
87 changes: 87 additions & 0 deletions ext/openssl/tests/dtls13_session_resume.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
--TEST--
dtls:// DTLS 1.3 session resumption with the ticket the client received
--EXTENSIONS--
openssl
--SKIPIF--
<?php
require __DIR__ . '/dtls_skipif.inc';
if (!function_exists('proc_open')) die('skip proc_open() not available');
if (!in_array('dtlsv1.3', stream_get_transports(), true)) die('skip DTLS 1.3 not available');
?>
--FILE--
<?php
$certFile = __DIR__ . DIRECTORY_SEPARATOR . 'dtls13_session_resume.pem.tmp';
include 'CertificateGenerator.inc';
(new CertificateGenerator())->saveNewCertAsFileWithKey('dtls-server', $certFile);

$serverCode = <<<'CODE'
$ctx = stream_context_create(['ssl' => [
'local_cert' => '%s',
'session_id_context' => 'dtls13-resume',
]]);
$server = stream_socket_server('dtls://127.0.0.1:0', $errno, $errstr,
STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctx);
phpt_notify_server_start($server);

$peer1 = stream_socket_accept($server, 10);
fwrite($peer1, strtoupper(fread($peer1, 64)));
fread($peer1, 1);
fclose($peer1);

phpt_notify();
$peer2 = stream_socket_accept($server, 10);
$crypto = stream_get_meta_data($peer2)['crypto'];
fwrite($peer2, strtoupper(fread($peer2, 64)));
fread($peer2, 1);
fclose($peer2);
fclose($server);

phpt_notify(message: $crypto['protocol'] . " reused=" . ($crypto['session_reused'] ? "yes" : "no"));
CODE;
$serverCode = sprintf($serverCode, $certFile);

$clientCode = <<<'CODE'
$session = null;
$ctx = stream_context_create(['ssl' => [
'verify_peer' => false,
'verify_peer_name' => false,
'session_new_cb' => function ($stream, $s) use (&$session) { $session = $s; },
]]);
$c1 = stream_socket_client("dtls://{{ ADDR }}", $errno, $errstr, 10, STREAM_CLIENT_CONNECT, $ctx);
fwrite($c1, "ping");
// The ticket follows the handshake: it is there once the reply was read
var_dump(fread($c1, 64));
$meta = stream_get_meta_data($c1)['crypto'];
echo "captured=" . ($session instanceof Openssl\Session ? "yes" : "no") . "\n";
echo "first_reused=" . ($meta['session_reused'] ? "yes" : "no") . "\n";
fclose($c1);

phpt_wait();
$ctx2 = stream_context_create(['ssl' => [
'verify_peer' => false,
'verify_peer_name' => false,
'session_data' => $session,
]]);
$c2 = stream_socket_client("dtls://{{ ADDR }}", $errno, $errstr, 10, STREAM_CLIENT_CONNECT, $ctx2);
fwrite($c2, "ping");
$meta = stream_get_meta_data($c2)['crypto'];
echo "second: " . $meta['protocol'] . " reused=" . ($meta['session_reused'] ? "yes" : "no") . "\n";
fread($c2, 64);
fclose($c2);

echo "server: " . trim(phpt_wait()) . "\n";
CODE;

include 'ServerClientTestCase.inc';
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
?>
--CLEAN--
<?php
@unlink(__DIR__ . DIRECTORY_SEPARATOR . 'dtls13_session_resume.pem.tmp');
?>
--EXPECT--
string(4) "PING"
captured=yes
first_reused=no
second: DTLSv1.3 reused=yes
server: DTLSv1.3 reused=yes
110 changes: 110 additions & 0 deletions ext/openssl/tests/dtls_client_basic.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
--TEST--
dtls:// client: DTLS 1.2 handshake and data round trip against openssl s_server
--EXTENSIONS--
openssl
--SKIPIF--
<?php
require __DIR__ . '/dtls_skipif.inc';
if (!function_exists('proc_open')) die('skip proc_open() not available');
if (substr(PHP_OS, 0, 3) === 'WIN') die('skip not for Windows');
exec('openssl version', $out, $code);
if ($code !== 0) die('skip openssl binary not available');
?>
--FILE--
<?php
include 'CertificateGenerator.inc';

$certFile = __DIR__ . DIRECTORY_SEPARATOR . 'dtls_client_basic.pem.tmp';
(new CertificateGenerator())->saveNewCertAsFileWithKey('dtls-server', $certFile);

// Grab a free UDP port, then hand it to s_server.
$probe = stream_socket_server('udp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND);
$port = (int) substr(strrchr(stream_socket_get_name($probe, false), ':'), 1);
fclose($probe);

// Plain relay mode: data from the client is printed to s_server's stdout, and
// s_server's stdin is sent to the client.
$cmd = ['openssl', 's_server', '-dtls1_2', '-accept', (string) $port,
'-cert', $certFile, '-key', $certFile];
$descriptors = [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']];
$server = proc_open($cmd, $descriptors, $pipes);
if (!is_resource($server)) {
die("skip-like: could not start s_server\n");
}

// s_server prints "ACCEPT" once it is bound and listening; wait for it so the
// client does not fire its ClientHello at an unbound port.
stream_set_blocking($pipes[1], false);
stream_set_blocking($pipes[2], false);
$ready = false;
$deadline = microtime(true) + 10;
$buf = '';
while (microtime(true) < $deadline) {
$r = [$pipes[1], $pipes[2]]; $w = $e = [];
if (stream_select($r, $w, $e, 1)) {
foreach ($r as $pipe) {
$buf .= (string) fread($pipe, 8192);
}
if (strpos($buf, 'ACCEPT') !== false) { $ready = true; break; }
}
}

// Self-signed s_server cert: skip verification for this round-trip scenario.
$ctx = stream_context_create(['ssl' => ['verify_peer' => false, 'verify_peer_name' => false]]);
$client = $ready
? stream_socket_client("dtls://127.0.0.1:$port", $errno, $errstr, 10, STREAM_CLIENT_CONNECT, $ctx)
: false;
var_dump($client !== false);

if ($client !== false) {
var_dump(stream_get_meta_data($client)['crypto']['protocol'] === 'DTLSv1.2');
stream_set_timeout($client, 10);

// client -> server: our datagram should show up on s_server's stdout.
fwrite($client, "PING\n");
// server -> client: feed s_server's stdin, it relays to the client.
fwrite($pipes[0], "PONG\n");

// stream_select() on the dtls:// stream exercises the transport cast op.
$r = [$client]; $w = $e = [];
var_dump(stream_select($r, $w, $e, 10) === 1);
var_dump(rtrim((string) fread($client, 8192)) === 'PONG');

$srvOut = '';
$deadline = microtime(true) + 5;
while (microtime(true) < $deadline && strpos($srvOut, 'PING') === false) {
$srvOut .= (string) fread($pipes[1], 8192);
usleep(50000);
}
var_dump(strpos($srvOut, 'PING') !== false);

// A read with nothing pending times out; the metadata reflects it.
stream_set_timeout($client, 0, 200000);
fread($client, 8192);
var_dump(stream_get_meta_data($client)['timed_out']);

// A non-blocking read with nothing pending returns '' (would block), not false.
stream_set_blocking($client, false);
var_dump(fread($client, 8192));

fclose($client);
}

proc_terminate($server);
foreach ($pipes as $pipe) {
if (is_resource($pipe)) fclose($pipe);
}
proc_close($server);
?>
--CLEAN--
<?php
@unlink(__DIR__ . DIRECTORY_SEPARATOR . 'dtls_client_basic.pem.tmp');
?>
--EXPECT--
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
string(0) ""
Loading
Loading