Skip to content
Open
1 change: 1 addition & 0 deletions Zend/Optimizer/zend_func_infos.h
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ static const func_info_t func_infos[] = {
F1("openssl_get_curve_names", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_FALSE),
#endif
F1("openssl_get_cert_locations", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_STRING),
F1("openssl_get_channel_binding", MAY_BE_STRING),
FN("pcntl_signal_get_handler", MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_OBJECT|MAY_BE_LONG),
FN("preg_replace", MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_STRING|MAY_BE_NULL),
FN("preg_filter", MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_STRING|MAY_BE_NULL),
Expand Down
68 changes: 68 additions & 0 deletions ext/openssl/openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ ZEND_DECLARE_MODULE_GLOBALS(openssl)

zend_class_entry *php_openssl_exception_ce;

/* Openssl\UnsupportedChannelBindingException class */

static zend_class_entry *php_openssl_unsupported_channel_binding_exception_ce;

/* Openssl\ChannelBindingType enum */

static zend_class_entry *php_openssl_channel_binding_type_ce;

/* OpenSSLCertificate class */

zend_class_entry *php_openssl_certificate_ce;
Expand Down Expand Up @@ -757,6 +765,9 @@ PHP_INI_END()
PHP_MINIT_FUNCTION(openssl)
{
php_openssl_exception_ce = register_class_Openssl_OpensslException(zend_ce_exception);
php_openssl_unsupported_channel_binding_exception_ce =
register_class_Openssl_UnsupportedChannelBindingException(php_openssl_exception_ce);
php_openssl_channel_binding_type_ce = register_class_Openssl_ChannelBindingType();

php_openssl_certificate_ce = register_class_OpenSSLCertificate();
php_openssl_certificate_ce->create_object = php_openssl_certificate_create_object;
Expand Down Expand Up @@ -5104,3 +5115,60 @@ PHP_FUNCTION(openssl_random_pseudo_bytes)
}
}
/* }}} */

/* {{{ */
PHP_FUNCTION(openssl_get_channel_binding)
{
php_stream *stream = NULL;
zend_enum_Openssl_ChannelBindingType type;

ZEND_PARSE_PARAMETERS_START(2, 2)
PHP_Z_PARAM_STREAM(stream)
Z_PARAM_ENUM(type, php_openssl_channel_binding_type_ce)
ZEND_PARSE_PARAMETERS_END();

zend_string *result = NULL;
php_openssl_channel_binding_result ret = php_openssl_netstream_get_channel_binding(stream, type, &result);
switch (ret) {
case PHP_OPENSSL_CHANNEL_BINDING_RESULT_OK:
RETURN_STR(result);
case PHP_OPENSSL_CHANNEL_BINDING_RESULT_NOT_APPLICABLE:
switch (type) {
case ZEND_ENUM_Openssl_ChannelBindingType_TlsUnique:
zend_throw_exception(php_openssl_unsupported_channel_binding_exception_ce,
"Channel binding type \"tls-unique\" is not supported for TLS 1.3", 0);
break;
case ZEND_ENUM_Openssl_ChannelBindingType_TlsServerEndPoint:
zend_throw_exception(php_openssl_unsupported_channel_binding_exception_ce,
"Channel binding type \"tls-server-end-point\" requires a certificate", 0);
break;
default:
zend_throw_exception(php_openssl_unsupported_channel_binding_exception_ce,
"Channel binding type is not applicable to this connection", 0);
break;
}
RETURN_THROWS();
case PHP_OPENSSL_CHANNEL_BINDING_RESULT_NOT_TLS:
zend_throw_exception(zend_ce_value_error,
"Stream does not have transport encryption enabled", 0);
RETURN_THROWS();
case PHP_OPENSSL_CHANNEL_BINDING_RESULT_ERROR:
{
unsigned long err = ERR_peek_last_error();
if (err != 0) {
char errstr[256];

(void)ERR_error_string_n(err, errstr, sizeof(errstr));
zend_throw_exception_ex(php_openssl_exception_ce, 0,
"Failed to get channel binding data: %s", errstr);
} else {
zend_throw_exception_ex(php_openssl_exception_ce, 0,
"Failed to get channel binding data");
}
}
RETURN_THROWS();
default:
ZEND_UNREACHABLE();
}
}
/* }}} */
25 changes: 24 additions & 1 deletion ext/openssl/openssl.stub.php
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
<?php

/** @generate-class-entries */
/**
* @generate-class-entries
* @generate-c-enums
*/

namespace Openssl {

class OpensslException extends Exception
{
}

class UnsupportedChannelBindingException extends OpensslException
{
}

/**
* Channel binding type for openssl_get_channel_binding().
* The backing values are the IANA "Channel Binding" registry names.
*/
enum ChannelBindingType: string {
case TlsUnique = 'tls-unique';
case TlsServerEndPoint = 'tls-server-end-point';
case TlsExporter = 'tls-exporter';
}

/**
* @strict-properties
*/
Expand Down Expand Up @@ -776,4 +793,10 @@ function openssl_password_hash(string $algo, #[\SensitiveParameter] string $pass
function openssl_password_verify(string $algo, #[\SensitiveParameter] string $password, string $hash): bool {}
#endif

/**
* @param resource $stream
* @refcount 1
*/
function openssl_get_channel_binding($stream, Openssl\ChannelBindingType $channel_binding_type): string {}

}
43 changes: 42 additions & 1 deletion ext/openssl/openssl_arginfo.h

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

13 changes: 13 additions & 0 deletions ext/openssl/openssl_decl.h

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

11 changes: 11 additions & 0 deletions ext/openssl/php_openssl.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ extern zend_module_entry openssl_module_entry;
#include "php_version.h"
#define PHP_OPENSSL_VERSION PHP_VERSION

#include "openssl_decl.h"

#include <openssl/opensslconf.h>
#include <openssl/opensslv.h>
/* OpenSSL version check */
Expand Down Expand Up @@ -224,6 +226,14 @@ typedef struct _php_openssl_session_object {
zend_object std;
} php_openssl_session_object;

/* Result of php_openssl_netstream_get_channel_binding(). */
typedef enum php_openssl_channel_binding_result {
PHP_OPENSSL_CHANNEL_BINDING_RESULT_OK = 0, /* *out holds a zend_string with the data */
PHP_OPENSSL_CHANNEL_BINDING_RESULT_NOT_APPLICABLE, /* *out = NULL (e.g. tls-unique over TLS 1.3, or tls-server-end-point without a certificate) */
PHP_OPENSSL_CHANNEL_BINDING_RESULT_NOT_TLS, /* stream is not an active TLS stream */
PHP_OPENSSL_CHANNEL_BINDING_RESULT_ERROR, /* an OpenSSL-level failure occurred */
} php_openssl_channel_binding_result;

static inline php_openssl_session_object *php_openssl_session_from_obj(zend_object *obj) {
return (php_openssl_session_object *)((char *)(obj) - offsetof(php_openssl_session_object, std));
}
Expand All @@ -236,6 +246,7 @@ extern zend_class_entry *php_openssl_session_ce;
void php_openssl_session_object_init(zval *zv, SSL_SESSION *session);
bool php_openssl_is_session_ce(zval *val);
SSL_SESSION *php_openssl_session_from_zval(zval *zv);
php_openssl_channel_binding_result php_openssl_netstream_get_channel_binding(struct _php_stream *stream, zend_enum_Openssl_ChannelBindingType type, zend_string **out);

#if defined(HAVE_OPENSSL_ARGON2)

Expand Down
64 changes: 64 additions & 0 deletions ext/openssl/tests/openssl_get_channel_binding_errors.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
--TEST--
openssl_get_channel_binding(): argument and type error handling
--EXTENSIONS--
openssl
--FILE--
<?php
/* The enum exposes the three IANA names as backing values. */
foreach (Openssl\ChannelBindingType::cases() as $type) {
echo $type->name, '=', $type->value, "\n";
}

/* A string is not accepted; the parameter requires the enum. */
try {
openssl_get_channel_binding(fopen("php://memory", "r"), "tls-unique");
echo "no error\n";
} catch (Throwable $e) {
echo $e::class, ': ', $e->getMessage(), "\n";
}

/* An unknown string is not accepted either. */
try {
openssl_get_channel_binding(fopen("php://memory", "r"), "not-a-type");
echo "no error\n";
} catch (Throwable $e) {
echo $e::class, ': ', $e->getMessage(), "\n";
}

/* Non-stream argument -> TypeError. */
try {
openssl_get_channel_binding(123, Openssl\ChannelBindingType::TlsUnique);
echo "no error\n";
} catch (Throwable $e) {
echo $e::class, ': ', $e->getMessage(), "\n";
}

/* The unsupported-type exception extends the general OpenSSL exception. */
var_dump(is_subclass_of(
Openssl\UnsupportedChannelBindingException::class,
Openssl\OpensslException::class,
));

/* A stream without transport encryption -> ValueError. */
$plain = fopen("php://memory", "r");
foreach (Openssl\ChannelBindingType::cases() as $type) {
try {
openssl_get_channel_binding($plain, $type);
echo $type->value, ": no error\n";
} catch (Throwable $e) {
echo $type->value, ': ', $e::class, ': ', $e->getMessage(), "\n";
}
}
fclose($plain);
?>
--EXPECT--
TlsUnique=tls-unique
TlsServerEndPoint=tls-server-end-point
TlsExporter=tls-exporter
TypeError: openssl_get_channel_binding(): Argument #2 ($channel_binding_type) must be of type Openssl\ChannelBindingType, string given
TypeError: openssl_get_channel_binding(): Argument #2 ($channel_binding_type) must be of type Openssl\ChannelBindingType, string given
TypeError: openssl_get_channel_binding(): Argument #1 ($stream) must be of type resource, int given
bool(true)
tls-unique: ValueError: Stream does not have transport encryption enabled
tls-server-end-point: ValueError: Stream does not have transport encryption enabled
tls-exporter: ValueError: Stream does not have transport encryption enabled
71 changes: 71 additions & 0 deletions ext/openssl/tests/openssl_get_channel_binding_psk.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
--TEST--
openssl_get_channel_binding(): TLS 1.2 PSK, tls-server-end-point is not applicable
--EXTENSIONS--
openssl
--SKIPIF--
<?php
if (!function_exists("proc_open")) die("skip no proc_open");
if (OPENSSL_VERSION_NUMBER < 0x10101000) die("skip OpenSSL >= 1.1.1 required");
?>
--FILE--
<?php
$serverCode = <<<'CODE'
$serverCtx = stream_context_create(['ssl' => [
'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_SERVER,
'ciphers' => 'PSK',
'psk_server_cb' => function ($stream, string $identity): ?Openssl\Psk {
if ($identity === 'client_id') {
return new Openssl\Psk("\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff");
}
return null;
},
]]);
$server = stream_socket_server('tls://127.0.0.1:0', $errno, $errstr,
STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $serverCtx);
phpt_notify_server_start($server);
$conn = stream_socket_accept($server, 30);
fwrite($conn, "ready");
phpt_wait();
fclose($conn);
CODE;

$clientCode = <<<'CODE'
$clientCtx = stream_context_create(['ssl' => [
'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT,
'ciphers' => 'PSK',
'verify_peer' => false,
'verify_peer_name' => false,
'psk_client_cb' => function ($stream): ?Openssl\Psk {
return new Openssl\Psk(
"\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff",
'client_id'
);
},
]]);
$client = stream_socket_client('tls://{{ ADDR }}', $errno, $errstr,
30, STREAM_CLIENT_CONNECT, $clientCtx);
var_dump($client !== false);
var_dump(fread($client, 1024));

foreach (Openssl\ChannelBindingType::cases() as $type) {
try {
$v = openssl_get_channel_binding($client, $type);
printf("%s len=%d\n", $type->value, strlen($v));
} catch (Openssl\UnsupportedChannelBindingException $e) {
echo $e::class, ': ', $e->getMessage(), "\n";
}
}

phpt_notify('server');
fclose($client);
CODE;

include __DIR__ . '/ServerClientTestCase.inc';
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
?>
--EXPECTF--
bool(true)
string(5) "ready"
tls-unique len=%d
Openssl\UnsupportedChannelBindingException: Channel binding type "tls-server-end-point" requires a certificate
tls-exporter len=32
Loading
Loading