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
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|MAY_BE_NULL),
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
60 changes: 60 additions & 0 deletions ext/openssl/openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -5104,3 +5104,63 @@ PHP_FUNCTION(openssl_random_pseudo_bytes)
}
}
/* }}} */

/* {{{ */
PHP_FUNCTION(openssl_get_channel_binding)
{
php_stream *stream = NULL;
zend_string *type = NULL;
zend_string *result = NULL;
int type_code;
int ret;

ZEND_PARSE_PARAMETERS_START(2, 2)
PHP_Z_PARAM_STREAM(stream)
Z_PARAM_STR(type)
ZEND_PARSE_PARAMETERS_END();

if (zend_string_equals_literal(type, "tls-unique")) {
type_code = PHP_OSSL_CB_TLS_UNIQUE;
} else if (zend_string_equals_literal(type, "tls-server-end-point")) {
type_code = PHP_OSSL_CB_TLS_SERVER_ENDPOINT;
} else if (zend_string_equals_literal(type, "tls-exporter")) {
type_code = PHP_OSSL_CB_TLS_EXPORTER;
} else {
zend_value_error(
"%s(): argument #2 ($channel_binding_type) \"%s\" is not a known "
"channel binding type, expected \"tls-unique\", "
"\"tls-server-end-point\" or \"tls-exporter\"",
get_active_function_name(), ZSTR_VAL(type));
RETURN_THROWS();
}

ret = php_openssl_netstream_get_channel_binding(stream, type_code, &result);
switch (ret) {
case PHP_OSSL_CB_OK:
RETURN_STR(result);
case PHP_OSSL_CB_NOT_APPLICABLE:
RETURN_NULL();
case PHP_OSSL_CB_NOT_TLS:
zend_throw_exception_ex(php_openssl_exception_ce, 0,
"Stream does not have transport encryption enabled");
RETURN_THROWS();
case PHP_OSSL_CB_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();
}
}
/* }}} */
6 changes: 6 additions & 0 deletions ext/openssl/openssl.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -776,4 +776,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, string $channel_binding_type): ?string {}

}
9 changes: 8 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.

19 changes: 19 additions & 0 deletions ext/openssl/php_openssl.h
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,25 @@ 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);
int php_openssl_netstream_get_channel_binding(struct _php_stream *stream, int type, zend_string **out);

/* Channel binding data types (RFC 5929, RFC 9266), as used for SASL
* channel binding (e.g. SCRAM-SHA-*-PLUS, RFC 5802 / RFC 5801). The string
* values accepted by stream_get_channel_binding() are the IANA "Channel
* Binding" registry names. */
enum php_openssl_channel_binding_type {
PHP_OSSL_CB_TLS_UNIQUE = 0,
PHP_OSSL_CB_TLS_SERVER_ENDPOINT,
PHP_OSSL_CB_TLS_EXPORTER,
};

/* Result of php_openssl_netstream_get_channel_binding(). */
enum php_openssl_channel_binding_result {
PHP_OSSL_CB_OK = 0, /* *out holds a zend_string with the data */
PHP_OSSL_CB_NOT_APPLICABLE, /* *out = NULL (e.g. tls-unique over TLS 1.3) */
PHP_OSSL_CB_NOT_TLS, /* stream is not an active TLS stream */
PHP_OSSL_CB_ERROR, /* an OpenSSL-level failure occurred */
};

#if defined(HAVE_OPENSSL_ARGON2)

Expand Down
49 changes: 49 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,49 @@
--TEST--
openssl_get_channel_binding(): argument and type error handling
--EXTENSIONS--
openssl
--FILE--
<?php
/* Unknown channel binding type -> ValueError (checked before the stream). */
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";
}

/* Case-sensitivity: a mismatched case is also unknown. */
try {
openssl_get_channel_binding(fopen("php://memory", "r"), "TLS-UNIQUE");
echo "no error\n";
} catch (Throwable $e) {
echo $e::class, ': ', $e->getMessage(), "\n";
}

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

/* A stream without transport encryption -> RuntimeException. */
$plain = fopen("php://memory", "r");
foreach (["tls-unique", "tls-server-end-point", "tls-exporter"] as $t) {
try {
openssl_get_channel_binding($plain, $t);
echo "$t: no error\n";
} catch (Throwable $e) {
echo $t, ': ', $e::class, ': ', $e->getMessage(), "\n";
}
}
fclose($plain);
?>
--EXPECT--
ValueError: openssl_get_channel_binding(): argument #2 ($channel_binding_type) "not-a-type" is not a known channel binding type, expected "tls-unique", "tls-server-end-point" or "tls-exporter"
ValueError: openssl_get_channel_binding(): argument #2 ($channel_binding_type) "TLS-UNIQUE" is not a known channel binding type, expected "tls-unique", "tls-server-end-point" or "tls-exporter"
TypeError: openssl_get_channel_binding(): Argument #1 ($stream) must be of type resource, int given
tls-unique: Openssl\OpensslException: Stream does not have transport encryption enabled
tls-server-end-point: Openssl\OpensslException: Stream does not have transport encryption enabled
tls-exporter: Openssl\OpensslException: Stream does not have transport encryption enabled
107 changes: 107 additions & 0 deletions ext/openssl/tests/openssl_get_channel_binding_tls12.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
--TEST--
openssl_get_channel_binding(): TLS 1.2 full handshake, client and server agree
--EXTENSIONS--
openssl
--SKIPIF--
<?php
if (!function_exists("proc_open")) die("skip no proc_open");
?>
--FILE--
<?php
$certFile = __DIR__ . DIRECTORY_SEPARATOR . 'cb_tls12.pem.tmp';

/* Server: complete the TLS 1.2 handshake, then send its three channel-binding
* values to the client over the connection (hex-encoded; empty = null). */
$serverCode = <<<'CODE'
$ctx = stream_context_create(['ssl' => [
'local_cert' => '%s',
'verify_peer' => false,
'verify_peer_name' => false,
'capture_peer_cert' => true,
'security_level' => 0,
]]);
$flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN;
$server = stream_socket_server("tlsv1.2://127.0.0.1:0", $errno, $errstr, $flags, $ctx);
phpt_notify_server_start($server);

$conn = stream_socket_accept($server, 30);
if ($conn === false) {
echo "SERVER_EXCEPTION accept failed\n";
exit(1);
}
foreach (["tls-unique", "tls-server-end-point", "tls-exporter"] as $t) {
$v = openssl_get_channel_binding($conn, $t);
fwrite($conn, $t . "=" . (is_string($v) ? bin2hex($v) : "") . "\n");
}
phpt_wait();
fclose($conn);
CODE;
$serverCode = sprintf($serverCode, $certFile);

/* Client: complete the handshake, compute its own values, read the server's
* values and report whether they agree. Also check tls-server-end-point against
* the SHA-256 fingerprint of the captured peer certificate. */
$clientCode = <<<'CODE'
$ctx = stream_context_create(['ssl' => [
'verify_peer' => false,
'verify_peer_name' => false,
'capture_peer_cert' => true,
'security_level' => 0,
]]);
$client = stream_socket_client("tlsv1.2://{{ ADDR }}", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $ctx);
if ($client === false) {
echo "client connect failed\n";
exit(1);
}

$my = [];
foreach (["tls-unique", "tls-server-end-point", "tls-exporter"] as $t) {
$my[$t] = openssl_get_channel_binding($client, $t);
}

$peer = [];
for ($i = 0; $i < 3; $i++) {
$line = fgets($client);
if ($line === false) break;
$line = rtrim($line);
$eq = strpos($line, "=");
$t = substr($line, 0, $eq);
$enc = substr($line, $eq + 1);
$peer[$t] = ($enc === "") ? null : hex2bin($enc);
}

foreach (["tls-unique", "tls-server-end-point", "tls-exporter"] as $t) {
$c = $my[$t];
$s = $peer[$t];
$lc = is_string($c) ? strlen($c) : "null";
$ls = is_string($s) ? strlen($s) : "null";
printf("%s equal=%s len_client=%s len_server=%s\n",
$t, var_export($c === $s, true), $lc, $ls);
}

$opts = stream_context_get_options($client);
$cert = $opts['ssl']['peer_certificate'] ?? null;
$fpr = $cert ? openssl_x509_fingerprint($cert, "sha256", true) : null;
echo "tse_matches_sha256_fingerprint="
. var_export($fpr !== null && $my["tls-server-end-point"] === $fpr, true) . "\n";

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

include 'CertificateGenerator.inc';
$generator = new CertificateGenerator();
$generator->saveNewCertAsFileWithKey('cb-tls12', $certFile);

include 'ServerClientTestCase.inc';
ServerClientTestCase::getInstance()->run($clientCode, ['server' => $serverCode]);
?>
--CLEAN--
<?php
@unlink(__DIR__ . DIRECTORY_SEPARATOR . 'cb_tls12.pem.tmp');
?>
--EXPECTF--
tls-unique equal=true len_client=%d len_server=%d
tls-server-end-point equal=true len_client=32 len_server=32
tls-exporter equal=true len_client=32 len_server=32
tse_matches_sha256_fingerprint=true
Loading
Loading