Skip to content
Open
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
22 changes: 22 additions & 0 deletions ext/openssl/tests/stream_socket_get_crypto_status_basic.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
--TEST--
stream_socket_get_crypto_status(): constants and behavior on a non-crypto stream
--EXTENSIONS--
openssl
--FILE--
<?php
/* The status constants. */
var_dump(STREAM_CRYPTO_STATUS_NONE);
var_dump(STREAM_CRYPTO_STATUS_WANT_READ);
var_dump(STREAM_CRYPTO_STATUS_WANT_WRITE);

/* A plain (non-SSL) socket has no pending crypto operation, so the status
* is STREAM_CRYPTO_STATUS_NONE. */
$server = stream_socket_server('tcp://127.0.0.1:0', $errno, $errstr);
var_dump(@stream_socket_get_crypto_status($server) === STREAM_CRYPTO_STATUS_NONE);
fclose($server);
?>
--EXPECT--
int(0)
int(1)
int(2)
bool(true)
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
--TEST--
stream_socket_get_crypto_status(): reports WANT_READ/WANT_WRITE during a non-blocking handshake
--EXTENSIONS--
openssl
--SKIPIF--
<?php
if (!function_exists("proc_open")) die("skip no proc_open");
?>
--FILE--
<?php
$certFile = __DIR__ . DIRECTORY_SEPARATOR . 'crypto_status_handshake.pem.tmp';
$peerName = 'crypto-status-handshake';

/* Plain blocking TLS server. */
$serverCode = <<<'CODE'
$ctx = stream_context_create(['ssl' => ['local_cert' => '%s']]);
$flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN;
$server = stream_socket_server("tls://127.0.0.1:0", $errno, $errstr, $flags, $ctx);
phpt_notify_server_start($server);

$conn = stream_socket_accept($server, 30);
if ($conn) {
fwrite($conn, "ok\n");
phpt_wait();
fclose($conn);
}
CODE;
$serverCode = sprintf($serverCode, $certFile);

/* Client connects over plain TCP, then completes the TLS handshake in non-blocking mode, using
* the reported crypto status to select the right direction to wait on. */
$clientCode = <<<'CODE'
$ctx = stream_context_create(['ssl' => [
'verify_peer' => false,
'verify_peer_name' => false,
'peer_name' => '%s',
]]);

$client = stream_socket_client("tcp://{{ ADDR }}", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $ctx);
stream_set_blocking($client, false);

$sawWant = false;
$pendingAlwaysWant = true;

do {
$r = stream_socket_enable_crypto($client, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
if ($r === 0) {
$status = stream_socket_get_crypto_status($client);
if ($status === STREAM_CRYPTO_STATUS_WANT_READ
|| $status === STREAM_CRYPTO_STATUS_WANT_WRITE) {
$sawWant = true;
} else {
/* must never be NONE while the handshake is still pending */
$pendingAlwaysWant = false;
}

/* Wait on the direction the engine actually asked for. */
$read = $write = $except = null;
if ($status === STREAM_CRYPTO_STATUS_WANT_WRITE) {
$write = [$client];
} else {
$read = [$client];
}
stream_select($read, $write, $except, 1);
}
} while ($r === 0);

var_dump($r);
var_dump($sawWant);
var_dump($pendingAlwaysWant);
/* After a completed handshake the status is reset to NONE. */
var_dump(stream_socket_get_crypto_status($client) === STREAM_CRYPTO_STATUS_NONE);

stream_set_blocking($client, true);
echo trim(fgets($client)), "\n";
phpt_notify();
fclose($client);
CODE;
$clientCode = sprintf($clientCode, $peerName);

include 'CertificateGenerator.inc';
$certificateGenerator = new CertificateGenerator();
$certificateGenerator->saveNewCertAsFileWithKey($peerName, $certFile);

include 'ServerClientTestCase.inc';
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
?>
--CLEAN--
<?php
@unlink(__DIR__ . DIRECTORY_SEPARATOR . 'crypto_status_handshake.pem.tmp');
?>
--EXPECT--
bool(true)
bool(true)
bool(true)
bool(true)
ok
95 changes: 95 additions & 0 deletions ext/openssl/tests/stream_socket_get_crypto_status_read.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
--TEST--
stream_socket_get_crypto_status(): reports WANT_READ on a non-blocking read with no application data
--EXTENSIONS--
openssl
--SKIPIF--
<?php
if (!function_exists("proc_open")) die("skip no proc_open");
?>
--FILE--
<?php
$certFile = __DIR__ . DIRECTORY_SEPARATOR . 'crypto_status_read.pem.tmp';
$peerName = 'crypto-status-read';

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

$conn = stream_socket_accept($server, 30);

/* Do not send anything until the client has performed its first read, so that the read is
* guaranteed to find no application data. */
phpt_wait();
fwrite($conn, "hello\n");

phpt_wait();
fclose($conn);
CODE;
$serverCode = sprintf($serverCode, $certFile);

$clientCode = <<<'CODE'
$ctx = stream_context_create(['ssl' => [
'verify_peer' => false,
'verify_peer_name' => false,
'peer_name' => '%s',
]]);

$client = stream_socket_client("tls://{{ ADDR }}", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $ctx);
stream_set_blocking($client, false);

/* No application data has been sent yet - a non-blocking read returns nothing and the crypto
* status reflects that the OpenSSL wants to read. */
$data = fread($client, 100);
var_dump($data === '' || $data === false);
var_dump(stream_socket_get_crypto_status($client) === STREAM_CRYPTO_STATUS_WANT_READ);

/* Now let the server send. */
phpt_notify();

$buf = '';
$read = [$client];
$write = $except = null;
while (stream_select($read, $write, $except, 5)) {
$chunk = fread($client, 100);
if ($chunk === '' || $chunk === false) {
/* A non-application record (e.g. a TLS 1.3 session ticket) may arrive first. */
if (feof($client)) {
break;
}
} else {
$buf .= $chunk;
if (strlen($buf) >= 6) {
break;
}
}
$read = [$client];
$write = $except = null;
}

echo trim($buf), "\n";
/* A successful read clears the pending status back to NONE. */
var_dump(stream_socket_get_crypto_status($client) === STREAM_CRYPTO_STATUS_NONE);

phpt_notify();
fclose($client);
CODE;
$clientCode = sprintf($clientCode, $peerName);

include 'CertificateGenerator.inc';
$certificateGenerator = new CertificateGenerator();
$certificateGenerator->saveNewCertAsFileWithKey($peerName, $certFile);

include 'ServerClientTestCase.inc';
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
?>
--CLEAN--
<?php
@unlink(__DIR__ . DIRECTORY_SEPARATOR . 'crypto_status_read.pem.tmp');
?>
--EXPECT--
bool(true)
bool(true)
hello
bool(true)
15 changes: 15 additions & 0 deletions ext/openssl/xp_ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ typedef struct _php_openssl_netstream_data_t {
int enable_on_connect;
int is_client;
int ssl_active;
int last_status;
php_stream_xport_crypt_method_t method;
php_openssl_handshake_bucket_t *reneg;
php_openssl_sni_cert_t *sni_certs;
Expand Down Expand Up @@ -271,6 +272,10 @@ static int php_openssl_handle_ssl_error(php_stream *stream, int nr_bytes, bool i
* packets: retry in next iteration */
errno = EAGAIN;
retry = is_init ? true : sslsock->s.is_blocked;
if (!retry) {
sslsock->last_status = err == SSL_ERROR_WANT_READ ?
STREAM_CRYPTO_STATUS_WANT_READ : STREAM_CRYPTO_STATUS_WANT_WRITE;
}
break;
case SSL_ERROR_SYSCALL:
if (ERR_peek_error() == 0) {
Expand Down Expand Up @@ -2684,6 +2689,8 @@ static int php_openssl_enable_crypto(php_stream *stream,
int cert_captured = 0;
X509 *peer_cert;

sslsock->last_status = STREAM_CRYPTO_STATUS_NONE;

if (cparam->inputs.activate && !sslsock->ssl_active) {
struct timeval start_time, *timeout;
bool blocked = sslsock->s.is_blocked, has_timeout = false;
Expand Down Expand Up @@ -2883,6 +2890,7 @@ static ssize_t php_openssl_sockop_io(int read, php_stream *stream, char *buf, si

/* Now, do the IO operation. Don't block if we can't complete... */
ERR_clear_error();
sslsock->last_status = STREAM_CRYPTO_STATUS_NONE;
if (read) {
nr_bytes = SSL_read(sslsock->ssl_handle, buf, (int)count);

Expand Down Expand Up @@ -2957,6 +2965,10 @@ static ssize_t php_openssl_sockop_io(int read, php_stream *stream, char *buf, si
php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ?
(POLLIN|POLLPRI) : (POLLOUT|POLLPRI), has_timeout ? &left_time : NULL);
}
} else if (err == SSL_ERROR_WANT_READ) {
sslsock->last_status = STREAM_CRYPTO_STATUS_WANT_READ;
} else if (err == SSL_ERROR_WANT_WRITE) {
sslsock->last_status = STREAM_CRYPTO_STATUS_WANT_WRITE;
}
}

Expand Down Expand Up @@ -3417,6 +3429,9 @@ static int php_openssl_sockop_set_option(php_stream *stream, int option, int val
case STREAM_XPORT_CRYPTO_OP_ENABLE:
cparam->outputs.returncode = php_openssl_enable_crypto(stream, sslsock, cparam);
return PHP_STREAM_OPTION_RETURN_OK;
case STREAM_XPORT_CRYPTO_OP_GET_STATUS:
cparam->outputs.returncode = sslsock->last_status;
return PHP_STREAM_OPTION_RETURN_OK;
default:
/* fall through */
break;
Expand Down
5 changes: 5 additions & 0 deletions ext/standard/basic_functions.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -3484,6 +3484,11 @@ function stream_socket_sendto($socket, string $data, int $flags = 0, string $add
*/
function stream_socket_enable_crypto($stream, bool $enable, ?int $crypto_method = null, $session_stream = null): int|bool {}

/**
* @param resource $stream
*/
function stream_socket_get_crypto_status($stream): int {}

#ifdef HAVE_SHUTDOWN
/** @param resource $stream */
function stream_socket_shutdown($stream, int $mode): bool {}
Expand Down
6 changes: 5 additions & 1 deletion ext/standard/basic_functions_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions ext/standard/basic_functions_decl.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions ext/standard/file.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,23 @@
*/
const STREAM_CRYPTO_PROTO_TLSv1_3 = UNKNOWN;

/**
* @var int
* @cvalue STREAM_CRYPTO_STATUS_NONE
*/
const STREAM_CRYPTO_STATUS_NONE = UNKNOWN;
/**
* @var int
* @cvalue STREAM_CRYPTO_STATUS_WANT_READ
*/
const STREAM_CRYPTO_STATUS_WANT_READ = UNKNOWN;
/**
* @var int
* @cvalue STREAM_CRYPTO_STATUS_WANT_WRITE
*/
const STREAM_CRYPTO_STATUS_WANT_WRITE = UNKNOWN;


/**
* @var int
* @cvalue STREAM_SHUT_RD
Expand Down
5 changes: 4 additions & 1 deletion ext/standard/file_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions ext/standard/streamsfuncs.c
Original file line number Diff line number Diff line change
Expand Up @@ -1638,6 +1638,18 @@ PHP_FUNCTION(stream_socket_enable_crypto)
}
/* }}} */

/* Get crypto status */
PHP_FUNCTION(stream_socket_get_crypto_status)
{
php_stream *stream;

ZEND_PARSE_PARAMETERS_START(1, 1)
PHP_Z_PARAM_STREAM(stream)
ZEND_PARSE_PARAMETERS_END();

RETURN_LONG(php_stream_xport_crypto_get_status(stream));
}

/* {{{ Determine what file will be opened by calls to fopen() with a relative path */
PHP_FUNCTION(stream_resolve_include_path)
{
Expand Down
Loading
Loading