From 5d63d1de7a3a677a53375bb033511f1c2c784568 Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Thu, 1 Oct 2026 07:09:47 +0000 Subject: [PATCH 01/12] Add stream_get_channel_binding Related to https://github.com/php/php-src/issues/16766 --- ext/openssl/openssl.stub.php | 17 ++ ext/openssl/openssl_arginfo.h | 9 +- .../stream_get_channel_binding_errors.phpt | 49 ++++ .../stream_get_channel_binding_tls12.phpt | 107 +++++++++ .../stream_get_channel_binding_tls13.phpt | 105 ++++++++ ext/openssl/xp_ssl.c | 225 ++++++++++++++++++ 6 files changed, 511 insertions(+), 1 deletion(-) create mode 100644 ext/openssl/tests/stream_get_channel_binding_errors.phpt create mode 100644 ext/openssl/tests/stream_get_channel_binding_tls12.phpt create mode 100644 ext/openssl/tests/stream_get_channel_binding_tls13.phpt diff --git a/ext/openssl/openssl.stub.php b/ext/openssl/openssl.stub.php index 3d3fa3ea634f..589b22116aa1 100644 --- a/ext/openssl/openssl.stub.php +++ b/ext/openssl/openssl.stub.php @@ -776,4 +776,21 @@ function openssl_password_hash(string $algo, #[\SensitiveParameter] string $pass function openssl_password_verify(string $algo, #[\SensitiveParameter] string $password, string $hash): bool {} #endif + /** + * Get TLS channel binding data for the connection behind a stream. + * + * The binding data is only available once the TLS handshake has completed; + * for a stream without transport encryption a RuntimeException is thrown. + * + * @param resource $stream + * @param string $channel_binding_type One of "tls-unique", + * "tls-server-endpoint" or "tls-exporter" (the IANA "Channel Binding" + * registry names, RFC 5929 / RFC 9266). + * @return string|null The channel binding data, or NULL when it is not + * applicable to the connection (currently "tls-unique" over TLS 1.3). + * @throws RuntimeException + * @throws ValueError + */ +function stream_get_channel_binding($stream, string $channel_binding_type): ?string {} + } diff --git a/ext/openssl/openssl_arginfo.h b/ext/openssl/openssl_arginfo.h index c7fdf82c7861..fcdae745838b 100644 --- a/ext/openssl/openssl_arginfo.h +++ b/ext/openssl/openssl_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit openssl.stub.php instead. - * Stub hash: 7cad995b734d69f98d489edb97a7878a4ea8f47e */ + * Stub hash: 00e3e90742171820379330fb2037be2a59fd65ce */ #include "zend_attributes.h" #include "zend_constants.h" @@ -411,6 +411,11 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_password_verify, 0, 3, _ ZEND_END_ARG_INFO() #endif +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_stream_get_channel_binding, 0, 2, IS_STRING, 1) + ZEND_ARG_INFO(0, stream) + ZEND_ARG_TYPE_INFO(0, channel_binding_type, IS_STRING, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Openssl_Psk___construct, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, psk, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, identity, IS_STRING, 1, "null") @@ -516,6 +521,7 @@ ZEND_FUNCTION(openssl_get_cert_locations); ZEND_FUNCTION(openssl_password_hash); ZEND_FUNCTION(openssl_password_verify); #endif +ZEND_FUNCTION(stream_get_channel_binding); ZEND_METHOD(Openssl_Psk, __construct); ZEND_METHOD(Openssl_Session, export); ZEND_METHOD(Openssl_Session, import); @@ -600,6 +606,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(openssl_password_hash, arginfo_openssl_password_hash) ZEND_FE(openssl_password_verify, arginfo_openssl_password_verify) #endif + ZEND_FE(stream_get_channel_binding, arginfo_stream_get_channel_binding) ZEND_FE_END }; diff --git a/ext/openssl/tests/stream_get_channel_binding_errors.phpt b/ext/openssl/tests/stream_get_channel_binding_errors.phpt new file mode 100644 index 000000000000..edc2f220a846 --- /dev/null +++ b/ext/openssl/tests/stream_get_channel_binding_errors.phpt @@ -0,0 +1,49 @@ +--TEST-- +stream_get_channel_binding(): argument and type error handling +--EXTENSIONS-- +openssl +--FILE-- + ValueError (checked before the stream). */ +try { + stream_get_channel_binding(fopen("php://memory", "r"), "not-a-type"); + echo "no error\n"; +} catch (ValueError $e) { + echo "ValueError\n"; +} + +/* Case-sensitivity: a mismatched case is also unknown. */ +try { + stream_get_channel_binding(fopen("php://memory", "r"), "TLS-UNIQUE"); + echo "no error\n"; +} catch (ValueError $e) { + echo "ValueError (case sensitive)\n"; +} + +/* Non-stream argument -> TypeError. */ +try { + stream_get_channel_binding(123, "tls-unique"); + echo "no error\n"; +} catch (TypeError $e) { + echo "TypeError\n"; +} + +/* A stream without transport encryption -> RuntimeException. */ +$plain = fopen("php://memory", "r"); +foreach (["tls-unique", "tls-server-endpoint", "tls-exporter"] as $t) { + try { + stream_get_channel_binding($plain, $t); + echo "$t: no error\n"; + } catch (RuntimeException $e) { + echo "$t: RuntimeException\n"; + } +} +fclose($plain); +?> +--EXPECT-- +ValueError +ValueError (case sensitive) +TypeError +tls-unique: RuntimeException +tls-server-endpoint: RuntimeException +tls-exporter: RuntimeException diff --git a/ext/openssl/tests/stream_get_channel_binding_tls12.phpt b/ext/openssl/tests/stream_get_channel_binding_tls12.phpt new file mode 100644 index 000000000000..e36d083ebd1a --- /dev/null +++ b/ext/openssl/tests/stream_get_channel_binding_tls12.phpt @@ -0,0 +1,107 @@ +--TEST-- +stream_get_channel_binding(): TLS 1.2 full handshake, client and server agree +--EXTENSIONS-- +openssl +--SKIPIF-- + +--FILE-- + [ + '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-endpoint", "tls-exporter"] as $t) { + $v = stream_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-endpoint 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-endpoint", "tls-exporter"] as $t) { + $my[$t] = stream_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-endpoint", "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-endpoint"] === $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-- + +--EXPECTF-- +tls-unique equal=true len_client=%d len_server=%d +tls-server-endpoint equal=true len_client=32 len_server=32 +tls-exporter equal=true len_client=32 len_server=32 +tse_matches_sha256_fingerprint=true diff --git a/ext/openssl/tests/stream_get_channel_binding_tls13.phpt b/ext/openssl/tests/stream_get_channel_binding_tls13.phpt new file mode 100644 index 000000000000..bbc51a9430c3 --- /dev/null +++ b/ext/openssl/tests/stream_get_channel_binding_tls13.phpt @@ -0,0 +1,105 @@ +--TEST-- +stream_get_channel_binding(): TLS 1.3, tls-unique is not applicable +--EXTENSIONS-- +openssl +--SKIPIF-- + +--FILE-- + [ + '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.3://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-endpoint", "tls-exporter"] as $t) { + $v = stream_get_channel_binding($conn, $t); + fwrite($conn, $t . "=" . (is_string($v) ? bin2hex($v) : "") . "\n"); + } + phpt_wait(); + fclose($conn); +CODE; +$serverCode = sprintf($serverCode, $certFile); + +$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.3://{{ ADDR }}", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $ctx); + if ($client === false) { + echo "client connect failed\n"; + exit(1); + } + + $my = []; + foreach (["tls-unique", "tls-server-endpoint", "tls-exporter"] as $t) { + $my[$t] = stream_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-endpoint", "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); + } + + echo "tls_unique_is_null=" . var_export($my["tls-unique"] === null, true) . "\n"; + + $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-endpoint"] === $fpr, true) . "\n"; + + phpt_notify('server'); + fclose($client); +CODE; + +include 'CertificateGenerator.inc'; +$generator = new CertificateGenerator(); +$generator->saveNewCertAsFileWithKey('cb-tls13', $certFile); + +include 'ServerClientTestCase.inc'; +ServerClientTestCase::getInstance()->run($clientCode, ['server' => $serverCode]); +?> +--CLEAN-- + +--EXPECT-- +tls-unique equal=true len_client=null len_server=null +tls-server-endpoint equal=true len_client=32 len_server=32 +tls-exporter equal=true len_client=32 len_server=32 +tls_unique_is_null=true +tse_matches_sha256_fingerprint=true diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index 854227318da0..02ee9840179d 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -23,8 +23,10 @@ #include "ext/standard/file.h" #include "ext/uri/php_uri.h" #include "streams/php_streams_int.h" +#include "main/php_streams.h" #include "zend_smart_str.h" #include "zend_exceptions.h" +#include "ext/spl/spl_exceptions.h" #include "php_openssl.h" #include "php_openssl_backend.h" #include "php_io.h" @@ -35,6 +37,7 @@ #include #include #include +#include #ifdef PHP_WIN32 #include "win32/winutil.h" @@ -3946,6 +3949,228 @@ static const php_stream_ops php_openssl_socket_ops = { php_openssl_sockop_set_option, }; +/* 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 */ +}; + +/* + * Get the NID of the message-digest algorithm used to sign a certificate, + * or NID_undef if it cannot be determined. The parameter list of + * X509_get_signature_info() changed between OpenSSL 1.1.1 and 3.0. + */ +static int php_openssl_get_cert_signature_md_nid(const X509 *cert) /* {{{ */ +{ + int md_nid; + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + int sign_alg, secbits, pkeybits; + + if (!X509_get_signature_info((X509 *)cert, &sign_alg, &md_nid, &secbits, &pkeybits)) { + return NID_undef; + } +#else + int pkey_nid, secbits; + uint32_t flags = 0; + + if (!X509_get_signature_info((X509 *)cert, &md_nid, &pkey_nid, &secbits, &flags)) { + return NID_undef; + } +#endif + return md_nid; +} +/* }}} */ + +/* + * Extract TLS channel binding data from an established openssl stream. + * + * On success *out is set to a newly-allocated zend_string holding the data + * and PHP_OSSL_CB_OK is returned. For a type that is not applicable to the + * connection (currently: tls-unique over TLS 1.3) *out is set to NULL and + * PHP_OSSL_CB_NOT_APPLICABLE is returned. If the stream is not an active TLS + * stream PHP_OSSL_CB_NOT_TLS is returned, and if an OpenSSL call fails + * PHP_OSSL_CB_ERROR is returned; in both of these cases *out is set to NULL. + */ +static int php_openssl_netstream_get_channel_binding( /* {{{ */ + php_stream *stream, int type, zend_string **out) +{ + unsigned char buf[EVP_MAX_MD_SIZE]; + size_t len = 0; + php_openssl_netstream_data_t *sslsock; + SSL *ssl; + + *out = NULL; + + if (stream == NULL || stream->ops != &php_openssl_socket_ops) { + return PHP_OSSL_CB_NOT_TLS; + } + sslsock = (php_openssl_netstream_data_t *)stream->abstract; + if (sslsock == NULL || !sslsock->ssl_active || sslsock->ssl_handle == NULL) { + return PHP_OSSL_CB_NOT_TLS; + } + ssl = sslsock->ssl_handle; + + switch (type) { + case PHP_OSSL_CB_TLS_UNIQUE: + /* RFC 5929 section 3: tls-unique is not defined for TLS 1.3. */ + if (SSL_version(ssl) >= TLS1_3_VERSION) { + return PHP_OSSL_CB_NOT_APPLICABLE; + } + + /* + * In TLS 1.2 and earlier both endpoints always transmit a Finished + * message. Pick it so that the client and the server derive the same + * value: in a full handshake that is the client's Finished (the client + * reads it from its own send buffer, the server from its receive + * buffer), and in a resumed handshake the server's Finished. This is + * the same selection CPython's _ssl.get_channel_binding() makes. + */ + if (sslsock->is_client ^ SSL_session_reused(ssl)) { + len = SSL_get_finished(ssl, buf, sizeof(buf)); + } else { + len = SSL_get_peer_finished(ssl, buf, sizeof(buf)); + } + if (len == 0) { + return PHP_OSSL_CB_ERROR; + } + *out = zend_string_init((char *)buf, len, 0); + return *out != NULL ? PHP_OSSL_CB_OK : PHP_OSSL_CB_ERROR; + + case PHP_OSSL_CB_TLS_SERVER_ENDPOINT: + { + X509 *cert = sslsock->is_client ? + SSL_get_peer_certificate(ssl) : SSL_get_certificate(ssl); + int md_nid, rc; + const EVP_MD *md; + unsigned int digest_len = 0; + + if (cert == NULL) { + return PHP_OSSL_CB_ERROR; + } + + md_nid = php_openssl_get_cert_signature_md_nid(cert); + /* + * RFC 5929 section 4.1: if the digest used to sign the + * certificate is MD5 or SHA-1, or cannot be determined, use + * SHA-256 instead. + */ + if (md_nid == NID_md5 || md_nid == NID_sha1 || md_nid == NID_undef) { + md_nid = NID_sha256; + } + md = EVP_get_digestbynid(md_nid); + if (md == NULL) { + md = EVP_sha256(); + } + rc = X509_digest(cert, md, buf, &digest_len); + /* + * SSL_get_peer_certificate() returns a certificate with an + * incremented reference count that we must free; + * SSL_get_certificate() returns the certificate owned by the + * SSL_CTX, which must not be freed. + */ + if (sslsock->is_client) { + X509_free(cert); + } + if (rc != 1) { + return PHP_OSSL_CB_ERROR; + } + *out = zend_string_init((char *)buf, digest_len, 0); + return *out != NULL ? PHP_OSSL_CB_OK : PHP_OSSL_CB_ERROR; + } + + case PHP_OSSL_CB_TLS_EXPORTER: + /* + * RFC 9266 section 4 / RFC 5705: 32 octets, label + * "EXPORTER-Channel-Binding", empty context. For an empty context the + * use_context flag is immaterial (see tls13_export_keying_material() + * in OpenSSL, which zeroes the context length when it is clear). + */ + if (SSL_export_keying_material( + ssl, buf, 32, + "EXPORTER-Channel-Binding", sizeof("EXPORTER-Channel-Binding") - 1, + (const unsigned char *)"", 0, 1) != 1) { + return PHP_OSSL_CB_ERROR; + } + *out = zend_string_init((char *)buf, 32, 0); + return *out != NULL ? PHP_OSSL_CB_OK : PHP_OSSL_CB_ERROR; + + default: + return PHP_OSSL_CB_ERROR; + } +} +/* }}} */ + +/* {{{ */ +PHP_FUNCTION(stream_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-endpoint")) { + 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-endpoint\" 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(spl_ce_RuntimeException, 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(spl_ce_RuntimeException, 0, + "Failed to get channel binding data: %s", errstr); + } else { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, + "Failed to get channel binding data"); + } + } + RETURN_THROWS(); + } +} +/* }}} */ + static zend_long php_openssl_get_crypto_method( php_stream_context *ctx, zend_long crypto_method) /* {{{ */ { From 929179533c512b234a5dfbb7aab3c993e887a9f0 Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Thu, 1 Oct 2026 08:18:46 +0000 Subject: [PATCH 02/12] Use tls-server-end-point instead of tls-server-endpoint --- ext/openssl/openssl.stub.php | 2 +- ext/openssl/openssl_arginfo.h | 2 +- .../tests/stream_get_channel_binding_errors.phpt | 4 ++-- .../tests/stream_get_channel_binding_tls12.phpt | 12 ++++++------ .../tests/stream_get_channel_binding_tls13.phpt | 10 +++++----- ext/openssl/xp_ssl.c | 4 ++-- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/ext/openssl/openssl.stub.php b/ext/openssl/openssl.stub.php index 589b22116aa1..8e49866dab54 100644 --- a/ext/openssl/openssl.stub.php +++ b/ext/openssl/openssl.stub.php @@ -784,7 +784,7 @@ function openssl_password_verify(string $algo, #[\SensitiveParameter] string $pa * * @param resource $stream * @param string $channel_binding_type One of "tls-unique", - * "tls-server-endpoint" or "tls-exporter" (the IANA "Channel Binding" + * "tls-server-end-point" or "tls-exporter" (the IANA "Channel Binding" * registry names, RFC 5929 / RFC 9266). * @return string|null The channel binding data, or NULL when it is not * applicable to the connection (currently "tls-unique" over TLS 1.3). diff --git a/ext/openssl/openssl_arginfo.h b/ext/openssl/openssl_arginfo.h index fcdae745838b..f5ed0896ff15 100644 --- a/ext/openssl/openssl_arginfo.h +++ b/ext/openssl/openssl_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit openssl.stub.php instead. - * Stub hash: 00e3e90742171820379330fb2037be2a59fd65ce */ + * Stub hash: 0176f3c5793093d29ee8b9dfe8868d0e3835c046 */ #include "zend_attributes.h" #include "zend_constants.h" diff --git a/ext/openssl/tests/stream_get_channel_binding_errors.phpt b/ext/openssl/tests/stream_get_channel_binding_errors.phpt index edc2f220a846..1677037f9faf 100644 --- a/ext/openssl/tests/stream_get_channel_binding_errors.phpt +++ b/ext/openssl/tests/stream_get_channel_binding_errors.phpt @@ -30,7 +30,7 @@ try { /* A stream without transport encryption -> RuntimeException. */ $plain = fopen("php://memory", "r"); -foreach (["tls-unique", "tls-server-endpoint", "tls-exporter"] as $t) { +foreach (["tls-unique", "tls-server-end-point", "tls-exporter"] as $t) { try { stream_get_channel_binding($plain, $t); echo "$t: no error\n"; @@ -45,5 +45,5 @@ ValueError ValueError (case sensitive) TypeError tls-unique: RuntimeException -tls-server-endpoint: RuntimeException +tls-server-end-point: RuntimeException tls-exporter: RuntimeException diff --git a/ext/openssl/tests/stream_get_channel_binding_tls12.phpt b/ext/openssl/tests/stream_get_channel_binding_tls12.phpt index e36d083ebd1a..008e4b139ba5 100644 --- a/ext/openssl/tests/stream_get_channel_binding_tls12.phpt +++ b/ext/openssl/tests/stream_get_channel_binding_tls12.phpt @@ -29,7 +29,7 @@ $serverCode = <<<'CODE' echo "SERVER_EXCEPTION accept failed\n"; exit(1); } - foreach (["tls-unique", "tls-server-endpoint", "tls-exporter"] as $t) { + foreach (["tls-unique", "tls-server-end-point", "tls-exporter"] as $t) { $v = stream_get_channel_binding($conn, $t); fwrite($conn, $t . "=" . (is_string($v) ? bin2hex($v) : "") . "\n"); } @@ -39,7 +39,7 @@ 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-endpoint against + * 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' => [ @@ -55,7 +55,7 @@ $clientCode = <<<'CODE' } $my = []; - foreach (["tls-unique", "tls-server-endpoint", "tls-exporter"] as $t) { + foreach (["tls-unique", "tls-server-end-point", "tls-exporter"] as $t) { $my[$t] = stream_get_channel_binding($client, $t); } @@ -70,7 +70,7 @@ $clientCode = <<<'CODE' $peer[$t] = ($enc === "") ? null : hex2bin($enc); } - foreach (["tls-unique", "tls-server-endpoint", "tls-exporter"] as $t) { + foreach (["tls-unique", "tls-server-end-point", "tls-exporter"] as $t) { $c = $my[$t]; $s = $peer[$t]; $lc = is_string($c) ? strlen($c) : "null"; @@ -83,7 +83,7 @@ $clientCode = <<<'CODE' $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-endpoint"] === $fpr, true) . "\n"; + . var_export($fpr !== null && $my["tls-server-end-point"] === $fpr, true) . "\n"; phpt_notify('server'); fclose($client); @@ -102,6 +102,6 @@ ServerClientTestCase::getInstance()->run($clientCode, ['server' => $serverCode]) ?> --EXPECTF-- tls-unique equal=true len_client=%d len_server=%d -tls-server-endpoint equal=true len_client=32 len_server=32 +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 diff --git a/ext/openssl/tests/stream_get_channel_binding_tls13.phpt b/ext/openssl/tests/stream_get_channel_binding_tls13.phpt index bbc51a9430c3..bd18dcc9bf9c 100644 --- a/ext/openssl/tests/stream_get_channel_binding_tls13.phpt +++ b/ext/openssl/tests/stream_get_channel_binding_tls13.phpt @@ -27,7 +27,7 @@ $serverCode = <<<'CODE' echo "SERVER_EXCEPTION accept failed\n"; exit(1); } - foreach (["tls-unique", "tls-server-endpoint", "tls-exporter"] as $t) { + foreach (["tls-unique", "tls-server-end-point", "tls-exporter"] as $t) { $v = stream_get_channel_binding($conn, $t); fwrite($conn, $t . "=" . (is_string($v) ? bin2hex($v) : "") . "\n"); } @@ -50,7 +50,7 @@ $clientCode = <<<'CODE' } $my = []; - foreach (["tls-unique", "tls-server-endpoint", "tls-exporter"] as $t) { + foreach (["tls-unique", "tls-server-end-point", "tls-exporter"] as $t) { $my[$t] = stream_get_channel_binding($client, $t); } @@ -65,7 +65,7 @@ $clientCode = <<<'CODE' $peer[$t] = ($enc === "") ? null : hex2bin($enc); } - foreach (["tls-unique", "tls-server-endpoint", "tls-exporter"] as $t) { + foreach (["tls-unique", "tls-server-end-point", "tls-exporter"] as $t) { $c = $my[$t]; $s = $peer[$t]; $lc = is_string($c) ? strlen($c) : "null"; @@ -80,7 +80,7 @@ $clientCode = <<<'CODE' $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-endpoint"] === $fpr, true) . "\n"; + . var_export($fpr !== null && $my["tls-server-end-point"] === $fpr, true) . "\n"; phpt_notify('server'); fclose($client); @@ -99,7 +99,7 @@ ServerClientTestCase::getInstance()->run($clientCode, ['server' => $serverCode]) ?> --EXPECT-- tls-unique equal=true len_client=null len_server=null -tls-server-endpoint equal=true len_client=32 len_server=32 +tls-server-end-point equal=true len_client=32 len_server=32 tls-exporter equal=true len_client=32 len_server=32 tls_unique_is_null=true tse_matches_sha256_fingerprint=true diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index 02ee9840179d..0219f48208ca 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -4129,7 +4129,7 @@ PHP_FUNCTION(stream_get_channel_binding) if (zend_string_equals_literal(type, "tls-unique")) { type_code = PHP_OSSL_CB_TLS_UNIQUE; - } else if (zend_string_equals_literal(type, "tls-server-endpoint")) { + } 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; @@ -4137,7 +4137,7 @@ PHP_FUNCTION(stream_get_channel_binding) zend_value_error( "%s(): argument #2 ($channel_binding_type) \"%s\" is not a known " "channel binding type, expected \"tls-unique\", " - "\"tls-server-endpoint\" or \"tls-exporter\"", + "\"tls-server-end-point\" or \"tls-exporter\"", get_active_function_name(), ZSTR_VAL(type)); RETURN_THROWS(); } From 30820f94c61013b5bb70cb63f7a0b6ad6f054b83 Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Thu, 1 Oct 2026 08:22:34 +0000 Subject: [PATCH 03/12] Remove parameter list switch for X509_get_signature_info --- ext/openssl/xp_ssl.c | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index 0219f48208ca..6e43488a71a2 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -3969,27 +3969,17 @@ enum php_openssl_channel_binding_result { /* * Get the NID of the message-digest algorithm used to sign a certificate, - * or NID_undef if it cannot be determined. The parameter list of - * X509_get_signature_info() changed between OpenSSL 1.1.1 and 3.0. + * or NID_undef if it cannot be determined. */ static int php_openssl_get_cert_signature_md_nid(const X509 *cert) /* {{{ */ { - int md_nid; - -#if OPENSSL_VERSION_NUMBER < 0x30000000L - int sign_alg, secbits, pkeybits; - - if (!X509_get_signature_info((X509 *)cert, &sign_alg, &md_nid, &secbits, &pkeybits)) { - return NID_undef; - } -#else - int pkey_nid, secbits; + int md_nid, pkey_nid, secbits; uint32_t flags = 0; if (!X509_get_signature_info((X509 *)cert, &md_nid, &pkey_nid, &secbits, &flags)) { return NID_undef; } -#endif + return md_nid; } /* }}} */ From da5405bad45c6dc7cbf2dc68ed292f52e476ca3b Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Thu, 1 Oct 2026 08:28:15 +0000 Subject: [PATCH 04/12] Add stream_get_channel_binding to zend optimizer --- Zend/Optimizer/zend_func_infos.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Zend/Optimizer/zend_func_infos.h b/Zend/Optimizer/zend_func_infos.h index 0add5a32f911..2c8e3dbbd400 100644 --- a/Zend/Optimizer/zend_func_infos.h +++ b/Zend/Optimizer/zend_func_infos.h @@ -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("stream_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), From fb8217b7bf73aeb36eda3b430d9cb9f10997f48a Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Thu, 1 Oct 2026 09:46:27 +0000 Subject: [PATCH 05/12] Fix stream_get_channel_binding stub --- ext/openssl/openssl.stub.php | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/ext/openssl/openssl.stub.php b/ext/openssl/openssl.stub.php index 8e49866dab54..740c2fb36913 100644 --- a/ext/openssl/openssl.stub.php +++ b/ext/openssl/openssl.stub.php @@ -777,19 +777,8 @@ function openssl_password_verify(string $algo, #[\SensitiveParameter] string $pa #endif /** - * Get TLS channel binding data for the connection behind a stream. - * - * The binding data is only available once the TLS handshake has completed; - * for a stream without transport encryption a RuntimeException is thrown. - * * @param resource $stream - * @param string $channel_binding_type One of "tls-unique", - * "tls-server-end-point" or "tls-exporter" (the IANA "Channel Binding" - * registry names, RFC 5929 / RFC 9266). - * @return string|null The channel binding data, or NULL when it is not - * applicable to the connection (currently "tls-unique" over TLS 1.3). - * @throws RuntimeException - * @throws ValueError + * @refcount 1 */ function stream_get_channel_binding($stream, string $channel_binding_type): ?string {} From e932f5499e6ed45dab482c9ef26230f46fc2ee8a Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Thu, 1 Oct 2026 10:20:56 +0000 Subject: [PATCH 06/12] Fix arginfo --- ext/openssl/openssl_arginfo.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/openssl/openssl_arginfo.h b/ext/openssl/openssl_arginfo.h index f5ed0896ff15..a761d1d34b59 100644 --- a/ext/openssl/openssl_arginfo.h +++ b/ext/openssl/openssl_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit openssl.stub.php instead. - * Stub hash: 0176f3c5793093d29ee8b9dfe8868d0e3835c046 */ + * Stub hash: f71f04eb6d0e02d4cb5589e8783b8ca615a2b371 */ #include "zend_attributes.h" #include "zend_constants.h" From f57f3e2e9a5c4195fb8cd88ac3d1d7cabdc2a7ec Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Mon, 5 Oct 2026 11:40:44 +0000 Subject: [PATCH 07/12] Rename to openssl_get_channel_binding --- Zend/Optimizer/zend_func_infos.h | 2 +- ext/openssl/openssl.stub.php | 2 +- ext/openssl/openssl_arginfo.h | 8 ++++---- ...rs.phpt => openssl_get_channel_binding_errors.phpt} | 10 +++++----- ...s12.phpt => openssl_get_channel_binding_tls12.phpt} | 6 +++--- ...s13.phpt => openssl_get_channel_binding_tls13.phpt} | 6 +++--- ext/openssl/xp_ssl.c | 2 +- 7 files changed, 18 insertions(+), 18 deletions(-) rename ext/openssl/tests/{stream_get_channel_binding_errors.phpt => openssl_get_channel_binding_errors.phpt} (75%) rename ext/openssl/tests/{stream_get_channel_binding_tls12.phpt => openssl_get_channel_binding_tls12.phpt} (94%) rename ext/openssl/tests/{stream_get_channel_binding_tls13.phpt => openssl_get_channel_binding_tls13.phpt} (94%) diff --git a/Zend/Optimizer/zend_func_infos.h b/Zend/Optimizer/zend_func_infos.h index 2c8e3dbbd400..7367018fef4d 100644 --- a/Zend/Optimizer/zend_func_infos.h +++ b/Zend/Optimizer/zend_func_infos.h @@ -297,7 +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("stream_get_channel_binding", MAY_BE_STRING|MAY_BE_NULL), + 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), diff --git a/ext/openssl/openssl.stub.php b/ext/openssl/openssl.stub.php index 740c2fb36913..712cc8d22e94 100644 --- a/ext/openssl/openssl.stub.php +++ b/ext/openssl/openssl.stub.php @@ -780,6 +780,6 @@ function openssl_password_verify(string $algo, #[\SensitiveParameter] string $pa * @param resource $stream * @refcount 1 */ -function stream_get_channel_binding($stream, string $channel_binding_type): ?string {} +function openssl_get_channel_binding($stream, string $channel_binding_type): ?string {} } diff --git a/ext/openssl/openssl_arginfo.h b/ext/openssl/openssl_arginfo.h index a761d1d34b59..0f7ea643a219 100644 --- a/ext/openssl/openssl_arginfo.h +++ b/ext/openssl/openssl_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit openssl.stub.php instead. - * Stub hash: f71f04eb6d0e02d4cb5589e8783b8ca615a2b371 */ + * Stub hash: 0c7e0942232271e2696203ab80ba01958b46c5ae */ #include "zend_attributes.h" #include "zend_constants.h" @@ -411,7 +411,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_password_verify, 0, 3, _ ZEND_END_ARG_INFO() #endif -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_stream_get_channel_binding, 0, 2, IS_STRING, 1) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_get_channel_binding, 0, 2, IS_STRING, 1) ZEND_ARG_INFO(0, stream) ZEND_ARG_TYPE_INFO(0, channel_binding_type, IS_STRING, 0) ZEND_END_ARG_INFO() @@ -521,7 +521,7 @@ ZEND_FUNCTION(openssl_get_cert_locations); ZEND_FUNCTION(openssl_password_hash); ZEND_FUNCTION(openssl_password_verify); #endif -ZEND_FUNCTION(stream_get_channel_binding); +ZEND_FUNCTION(openssl_get_channel_binding); ZEND_METHOD(Openssl_Psk, __construct); ZEND_METHOD(Openssl_Session, export); ZEND_METHOD(Openssl_Session, import); @@ -606,7 +606,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(openssl_password_hash, arginfo_openssl_password_hash) ZEND_FE(openssl_password_verify, arginfo_openssl_password_verify) #endif - ZEND_FE(stream_get_channel_binding, arginfo_stream_get_channel_binding) + ZEND_FE(openssl_get_channel_binding, arginfo_openssl_get_channel_binding) ZEND_FE_END }; diff --git a/ext/openssl/tests/stream_get_channel_binding_errors.phpt b/ext/openssl/tests/openssl_get_channel_binding_errors.phpt similarity index 75% rename from ext/openssl/tests/stream_get_channel_binding_errors.phpt rename to ext/openssl/tests/openssl_get_channel_binding_errors.phpt index 1677037f9faf..a2b7903d1f99 100644 --- a/ext/openssl/tests/stream_get_channel_binding_errors.phpt +++ b/ext/openssl/tests/openssl_get_channel_binding_errors.phpt @@ -1,12 +1,12 @@ --TEST-- -stream_get_channel_binding(): argument and type error handling +openssl_get_channel_binding(): argument and type error handling --EXTENSIONS-- openssl --FILE-- ValueError (checked before the stream). */ try { - stream_get_channel_binding(fopen("php://memory", "r"), "not-a-type"); + openssl_get_channel_binding(fopen("php://memory", "r"), "not-a-type"); echo "no error\n"; } catch (ValueError $e) { echo "ValueError\n"; @@ -14,7 +14,7 @@ try { /* Case-sensitivity: a mismatched case is also unknown. */ try { - stream_get_channel_binding(fopen("php://memory", "r"), "TLS-UNIQUE"); + openssl_get_channel_binding(fopen("php://memory", "r"), "TLS-UNIQUE"); echo "no error\n"; } catch (ValueError $e) { echo "ValueError (case sensitive)\n"; @@ -22,7 +22,7 @@ try { /* Non-stream argument -> TypeError. */ try { - stream_get_channel_binding(123, "tls-unique"); + openssl_get_channel_binding(123, "tls-unique"); echo "no error\n"; } catch (TypeError $e) { echo "TypeError\n"; @@ -32,7 +32,7 @@ try { $plain = fopen("php://memory", "r"); foreach (["tls-unique", "tls-server-end-point", "tls-exporter"] as $t) { try { - stream_get_channel_binding($plain, $t); + openssl_get_channel_binding($plain, $t); echo "$t: no error\n"; } catch (RuntimeException $e) { echo "$t: RuntimeException\n"; diff --git a/ext/openssl/tests/stream_get_channel_binding_tls12.phpt b/ext/openssl/tests/openssl_get_channel_binding_tls12.phpt similarity index 94% rename from ext/openssl/tests/stream_get_channel_binding_tls12.phpt rename to ext/openssl/tests/openssl_get_channel_binding_tls12.phpt index 008e4b139ba5..bb09b424aae8 100644 --- a/ext/openssl/tests/stream_get_channel_binding_tls12.phpt +++ b/ext/openssl/tests/openssl_get_channel_binding_tls12.phpt @@ -1,5 +1,5 @@ --TEST-- -stream_get_channel_binding(): TLS 1.2 full handshake, client and server agree +openssl_get_channel_binding(): TLS 1.2 full handshake, client and server agree --EXTENSIONS-- openssl --SKIPIF-- @@ -30,7 +30,7 @@ $serverCode = <<<'CODE' exit(1); } foreach (["tls-unique", "tls-server-end-point", "tls-exporter"] as $t) { - $v = stream_get_channel_binding($conn, $t); + $v = openssl_get_channel_binding($conn, $t); fwrite($conn, $t . "=" . (is_string($v) ? bin2hex($v) : "") . "\n"); } phpt_wait(); @@ -56,7 +56,7 @@ $clientCode = <<<'CODE' $my = []; foreach (["tls-unique", "tls-server-end-point", "tls-exporter"] as $t) { - $my[$t] = stream_get_channel_binding($client, $t); + $my[$t] = openssl_get_channel_binding($client, $t); } $peer = []; diff --git a/ext/openssl/tests/stream_get_channel_binding_tls13.phpt b/ext/openssl/tests/openssl_get_channel_binding_tls13.phpt similarity index 94% rename from ext/openssl/tests/stream_get_channel_binding_tls13.phpt rename to ext/openssl/tests/openssl_get_channel_binding_tls13.phpt index bd18dcc9bf9c..e147dc27e0a7 100644 --- a/ext/openssl/tests/stream_get_channel_binding_tls13.phpt +++ b/ext/openssl/tests/openssl_get_channel_binding_tls13.phpt @@ -1,5 +1,5 @@ --TEST-- -stream_get_channel_binding(): TLS 1.3, tls-unique is not applicable +openssl_get_channel_binding(): TLS 1.3, tls-unique is not applicable --EXTENSIONS-- openssl --SKIPIF-- @@ -28,7 +28,7 @@ $serverCode = <<<'CODE' exit(1); } foreach (["tls-unique", "tls-server-end-point", "tls-exporter"] as $t) { - $v = stream_get_channel_binding($conn, $t); + $v = openssl_get_channel_binding($conn, $t); fwrite($conn, $t . "=" . (is_string($v) ? bin2hex($v) : "") . "\n"); } phpt_wait(); @@ -51,7 +51,7 @@ $clientCode = <<<'CODE' $my = []; foreach (["tls-unique", "tls-server-end-point", "tls-exporter"] as $t) { - $my[$t] = stream_get_channel_binding($client, $t); + $my[$t] = openssl_get_channel_binding($client, $t); } $peer = []; diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index 6e43488a71a2..5dddd0ea1ccd 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -4104,7 +4104,7 @@ static int php_openssl_netstream_get_channel_binding( /* {{{ */ /* }}} */ /* {{{ */ -PHP_FUNCTION(stream_get_channel_binding) +PHP_FUNCTION(openssl_get_channel_binding) { php_stream *stream = NULL; zend_string *type = NULL; From 8bd3d4c4ee2d21ac22c34253cae4097e8885ede7 Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Mon, 5 Oct 2026 12:55:11 +0000 Subject: [PATCH 08/12] Move PHP function to openssl.c, don't throw SPL exceptions --- ext/openssl/openssl.c | 60 ++++++++++++++ ext/openssl/php_openssl.h | 19 +++++ .../openssl_get_channel_binding_errors.phpt | 28 +++---- ext/openssl/xp_ssl.c | 79 +------------------ 4 files changed, 94 insertions(+), 92 deletions(-) diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index b05ee7418d0c..8f7619c2eccc 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -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(); + } +} +/* }}} */ diff --git a/ext/openssl/php_openssl.h b/ext/openssl/php_openssl.h index 588cca0e93d8..25516d33bbee 100644 --- a/ext/openssl/php_openssl.h +++ b/ext/openssl/php_openssl.h @@ -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) diff --git a/ext/openssl/tests/openssl_get_channel_binding_errors.phpt b/ext/openssl/tests/openssl_get_channel_binding_errors.phpt index a2b7903d1f99..7563b0321ead 100644 --- a/ext/openssl/tests/openssl_get_channel_binding_errors.phpt +++ b/ext/openssl/tests/openssl_get_channel_binding_errors.phpt @@ -8,24 +8,24 @@ openssl try { openssl_get_channel_binding(fopen("php://memory", "r"), "not-a-type"); echo "no error\n"; -} catch (ValueError $e) { - echo "ValueError\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 (ValueError $e) { - echo "ValueError (case sensitive)\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 (TypeError $e) { - echo "TypeError\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } /* A stream without transport encryption -> RuntimeException. */ @@ -34,16 +34,16 @@ foreach (["tls-unique", "tls-server-end-point", "tls-exporter"] as $t) { try { openssl_get_channel_binding($plain, $t); echo "$t: no error\n"; - } catch (RuntimeException $e) { - echo "$t: RuntimeException\n"; + } catch (Throwable $e) { + echo $t, ': ', $e::class, ': ', $e->getMessage(), "\n"; } } fclose($plain); ?> --EXPECT-- -ValueError -ValueError (case sensitive) -TypeError -tls-unique: RuntimeException -tls-server-end-point: RuntimeException -tls-exporter: RuntimeException +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 diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index 5dddd0ea1ccd..55f4eceebad1 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -26,7 +26,6 @@ #include "main/php_streams.h" #include "zend_smart_str.h" #include "zend_exceptions.h" -#include "ext/spl/spl_exceptions.h" #include "php_openssl.h" #include "php_openssl_backend.h" #include "php_io.h" @@ -3949,24 +3948,6 @@ static const php_stream_ops php_openssl_socket_ops = { php_openssl_sockop_set_option, }; -/* 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 */ -}; - /* * Get the NID of the message-digest algorithm used to sign a certificate, * or NID_undef if it cannot be determined. @@ -3994,7 +3975,7 @@ static int php_openssl_get_cert_signature_md_nid(const X509 *cert) /* {{{ */ * stream PHP_OSSL_CB_NOT_TLS is returned, and if an OpenSSL call fails * PHP_OSSL_CB_ERROR is returned; in both of these cases *out is set to NULL. */ -static int php_openssl_netstream_get_channel_binding( /* {{{ */ +int php_openssl_netstream_get_channel_binding( /* {{{ */ php_stream *stream, int type, zend_string **out) { unsigned char buf[EVP_MAX_MD_SIZE]; @@ -4103,64 +4084,6 @@ static int php_openssl_netstream_get_channel_binding( /* {{{ */ } /* }}} */ -/* {{{ */ -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(spl_ce_RuntimeException, 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(spl_ce_RuntimeException, 0, - "Failed to get channel binding data: %s", errstr); - } else { - zend_throw_exception_ex(spl_ce_RuntimeException, 0, - "Failed to get channel binding data"); - } - } - RETURN_THROWS(); - } -} -/* }}} */ - static zend_long php_openssl_get_crypto_method( php_stream_context *ctx, zend_long crypto_method) /* {{{ */ { From 93f0e0aced4650a071587c05d19d05cb46a19724 Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Wed, 14 Oct 2026 09:02:25 +0000 Subject: [PATCH 09/12] Consistently use enum's as type, correctly name values --- ext/openssl/openssl.c | 21 ++++++++++---------- ext/openssl/php_openssl.h | 38 ++++++++++++++++++------------------- ext/openssl/xp_ssl.c | 40 +++++++++++++++++++-------------------- 3 files changed, 49 insertions(+), 50 deletions(-) diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index 8f7619c2eccc..90532a4f12f6 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -5110,21 +5110,19 @@ 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(); + php_openssl_channel_binding_type_t type_code; if (zend_string_equals_literal(type, "tls-unique")) { - type_code = PHP_OSSL_CB_TLS_UNIQUE; + type_code = PHP_OPENSSL_CHANNEL_BINDING_TYPE_TLS_UNIQUE; } else if (zend_string_equals_literal(type, "tls-server-end-point")) { - type_code = PHP_OSSL_CB_TLS_SERVER_ENDPOINT; + type_code = PHP_OPENSSL_CHANNEL_BINDING_TYPE_TLS_SERVER_ENDPOINT; } else if (zend_string_equals_literal(type, "tls-exporter")) { - type_code = PHP_OSSL_CB_TLS_EXPORTER; + type_code = PHP_OPENSSL_CHANNEL_BINDING_TYPE_TLS_EXPORTER; } else { zend_value_error( "%s(): argument #2 ($channel_binding_type) \"%s\" is not a known " @@ -5134,17 +5132,18 @@ PHP_FUNCTION(openssl_get_channel_binding) RETURN_THROWS(); } - ret = php_openssl_netstream_get_channel_binding(stream, type_code, &result); + zend_string *result = NULL; + php_openssl_channel_binding_result_t ret = php_openssl_netstream_get_channel_binding(stream, type_code, &result); switch (ret) { - case PHP_OSSL_CB_OK: + case PHP_OPENSSL_CHANNEL_BINDING_RESULT_OK: RETURN_STR(result); - case PHP_OSSL_CB_NOT_APPLICABLE: + case PHP_OPENSSL_CHANNEL_BINDING_RESULT_NOT_APPLICABLE: RETURN_NULL(); - case PHP_OSSL_CB_NOT_TLS: + case PHP_OPENSSL_CHANNEL_BINDING_RESULT_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: + case PHP_OPENSSL_CHANNEL_BINDING_RESULT_ERROR: { unsigned long err = ERR_peek_last_error(); if (err != 0) { diff --git a/ext/openssl/php_openssl.h b/ext/openssl/php_openssl.h index 25516d33bbee..ff38b8bcfe3b 100644 --- a/ext/openssl/php_openssl.h +++ b/ext/openssl/php_openssl.h @@ -224,6 +224,24 @@ typedef struct _php_openssl_session_object { zend_object std; } php_openssl_session_object; +/* 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 openssl_get_channel_binding() are the IANA "Channel + * Binding" registry names. */ +typedef enum php_openssl_channel_binding_type { + PHP_OPENSSL_CHANNEL_BINDING_TYPE_TLS_UNIQUE = 0, + PHP_OPENSSL_CHANNEL_BINDING_TYPE_TLS_SERVER_ENDPOINT, + PHP_OPENSSL_CHANNEL_BINDING_TYPE_TLS_EXPORTER, +} php_openssl_channel_binding_type_t; + +/* 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) */ + 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_t; + 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)); } @@ -236,25 +254,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); -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 */ -}; +php_openssl_channel_binding_result_t php_openssl_netstream_get_channel_binding(struct _php_stream *stream, php_openssl_channel_binding_type_t type, zend_string **out); #if defined(HAVE_OPENSSL_ARGON2) diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index 55f4eceebad1..c20da430c32f 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -3969,14 +3969,14 @@ static int php_openssl_get_cert_signature_md_nid(const X509 *cert) /* {{{ */ * Extract TLS channel binding data from an established openssl stream. * * On success *out is set to a newly-allocated zend_string holding the data - * and PHP_OSSL_CB_OK is returned. For a type that is not applicable to the + * and PHP_OPENSSL_CHANNEL_BINDING_RESULT_OK is returned. For a type that is not applicable to the * connection (currently: tls-unique over TLS 1.3) *out is set to NULL and - * PHP_OSSL_CB_NOT_APPLICABLE is returned. If the stream is not an active TLS - * stream PHP_OSSL_CB_NOT_TLS is returned, and if an OpenSSL call fails - * PHP_OSSL_CB_ERROR is returned; in both of these cases *out is set to NULL. + * PHP_OPENSSL_CHANNEL_BINDING_RESULT_NOT_APPLICABLE is returned. If the stream is not an active TLS + * stream PHP_OPENSSL_CHANNEL_BINDING_RESULT_NOT_TLS is returned, and if an OpenSSL call fails + * PHP_OPENSSL_CHANNEL_BINDING_RESULT_ERROR is returned; in both of these cases *out is set to NULL. */ -int php_openssl_netstream_get_channel_binding( /* {{{ */ - php_stream *stream, int type, zend_string **out) +php_openssl_channel_binding_result_t php_openssl_netstream_get_channel_binding( /* {{{ */ + php_stream *stream, php_openssl_channel_binding_type_t type, zend_string **out) { unsigned char buf[EVP_MAX_MD_SIZE]; size_t len = 0; @@ -3986,19 +3986,19 @@ int php_openssl_netstream_get_channel_binding( /* {{{ */ *out = NULL; if (stream == NULL || stream->ops != &php_openssl_socket_ops) { - return PHP_OSSL_CB_NOT_TLS; + return PHP_OPENSSL_CHANNEL_BINDING_RESULT_NOT_TLS; } sslsock = (php_openssl_netstream_data_t *)stream->abstract; if (sslsock == NULL || !sslsock->ssl_active || sslsock->ssl_handle == NULL) { - return PHP_OSSL_CB_NOT_TLS; + return PHP_OPENSSL_CHANNEL_BINDING_RESULT_NOT_TLS; } ssl = sslsock->ssl_handle; switch (type) { - case PHP_OSSL_CB_TLS_UNIQUE: + case PHP_OPENSSL_CHANNEL_BINDING_TYPE_TLS_UNIQUE: /* RFC 5929 section 3: tls-unique is not defined for TLS 1.3. */ if (SSL_version(ssl) >= TLS1_3_VERSION) { - return PHP_OSSL_CB_NOT_APPLICABLE; + return PHP_OPENSSL_CHANNEL_BINDING_RESULT_NOT_APPLICABLE; } /* @@ -4015,12 +4015,12 @@ int php_openssl_netstream_get_channel_binding( /* {{{ */ len = SSL_get_peer_finished(ssl, buf, sizeof(buf)); } if (len == 0) { - return PHP_OSSL_CB_ERROR; + return PHP_OPENSSL_CHANNEL_BINDING_RESULT_ERROR; } *out = zend_string_init((char *)buf, len, 0); - return *out != NULL ? PHP_OSSL_CB_OK : PHP_OSSL_CB_ERROR; + return *out != NULL ? PHP_OPENSSL_CHANNEL_BINDING_RESULT_OK : PHP_OPENSSL_CHANNEL_BINDING_RESULT_ERROR; - case PHP_OSSL_CB_TLS_SERVER_ENDPOINT: + case PHP_OPENSSL_CHANNEL_BINDING_TYPE_TLS_SERVER_ENDPOINT: { X509 *cert = sslsock->is_client ? SSL_get_peer_certificate(ssl) : SSL_get_certificate(ssl); @@ -4029,7 +4029,7 @@ int php_openssl_netstream_get_channel_binding( /* {{{ */ unsigned int digest_len = 0; if (cert == NULL) { - return PHP_OSSL_CB_ERROR; + return PHP_OPENSSL_CHANNEL_BINDING_RESULT_ERROR; } md_nid = php_openssl_get_cert_signature_md_nid(cert); @@ -4056,13 +4056,13 @@ int php_openssl_netstream_get_channel_binding( /* {{{ */ X509_free(cert); } if (rc != 1) { - return PHP_OSSL_CB_ERROR; + return PHP_OPENSSL_CHANNEL_BINDING_RESULT_ERROR; } *out = zend_string_init((char *)buf, digest_len, 0); - return *out != NULL ? PHP_OSSL_CB_OK : PHP_OSSL_CB_ERROR; + return *out != NULL ? PHP_OPENSSL_CHANNEL_BINDING_RESULT_OK : PHP_OPENSSL_CHANNEL_BINDING_RESULT_ERROR; } - case PHP_OSSL_CB_TLS_EXPORTER: + case PHP_OPENSSL_CHANNEL_BINDING_TYPE_TLS_EXPORTER: /* * RFC 9266 section 4 / RFC 5705: 32 octets, label * "EXPORTER-Channel-Binding", empty context. For an empty context the @@ -4073,13 +4073,13 @@ int php_openssl_netstream_get_channel_binding( /* {{{ */ ssl, buf, 32, "EXPORTER-Channel-Binding", sizeof("EXPORTER-Channel-Binding") - 1, (const unsigned char *)"", 0, 1) != 1) { - return PHP_OSSL_CB_ERROR; + return PHP_OPENSSL_CHANNEL_BINDING_RESULT_ERROR; } *out = zend_string_init((char *)buf, 32, 0); - return *out != NULL ? PHP_OSSL_CB_OK : PHP_OSSL_CB_ERROR; + return *out != NULL ? PHP_OPENSSL_CHANNEL_BINDING_RESULT_OK : PHP_OPENSSL_CHANNEL_BINDING_RESULT_ERROR; default: - return PHP_OSSL_CB_ERROR; + return PHP_OPENSSL_CHANNEL_BINDING_RESULT_ERROR; } } /* }}} */ From 28e5556f18ab45201fab9ed858d417dc5d0a1786 Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Wed, 14 Oct 2026 09:29:06 +0000 Subject: [PATCH 10/12] Remove reserved _t suffix --- ext/openssl/openssl.c | 4 ++-- ext/openssl/php_openssl.h | 6 +++--- ext/openssl/xp_ssl.c | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index 90532a4f12f6..afcf042148a2 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -5116,7 +5116,7 @@ PHP_FUNCTION(openssl_get_channel_binding) Z_PARAM_STR(type) ZEND_PARSE_PARAMETERS_END(); - php_openssl_channel_binding_type_t type_code; + php_openssl_channel_binding_type type_code; if (zend_string_equals_literal(type, "tls-unique")) { type_code = PHP_OPENSSL_CHANNEL_BINDING_TYPE_TLS_UNIQUE; } else if (zend_string_equals_literal(type, "tls-server-end-point")) { @@ -5133,7 +5133,7 @@ PHP_FUNCTION(openssl_get_channel_binding) } zend_string *result = NULL; - php_openssl_channel_binding_result_t ret = php_openssl_netstream_get_channel_binding(stream, type_code, &result); + php_openssl_channel_binding_result ret = php_openssl_netstream_get_channel_binding(stream, type_code, &result); switch (ret) { case PHP_OPENSSL_CHANNEL_BINDING_RESULT_OK: RETURN_STR(result); diff --git a/ext/openssl/php_openssl.h b/ext/openssl/php_openssl.h index ff38b8bcfe3b..faa6417a5a34 100644 --- a/ext/openssl/php_openssl.h +++ b/ext/openssl/php_openssl.h @@ -232,7 +232,7 @@ typedef enum php_openssl_channel_binding_type { PHP_OPENSSL_CHANNEL_BINDING_TYPE_TLS_UNIQUE = 0, PHP_OPENSSL_CHANNEL_BINDING_TYPE_TLS_SERVER_ENDPOINT, PHP_OPENSSL_CHANNEL_BINDING_TYPE_TLS_EXPORTER, -} php_openssl_channel_binding_type_t; +} php_openssl_channel_binding_type; /* Result of php_openssl_netstream_get_channel_binding(). */ typedef enum php_openssl_channel_binding_result { @@ -240,7 +240,7 @@ typedef enum php_openssl_channel_binding_result { PHP_OPENSSL_CHANNEL_BINDING_RESULT_NOT_APPLICABLE, /* *out = NULL (e.g. tls-unique over TLS 1.3) */ 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_t; +} 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)); @@ -254,7 +254,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_t php_openssl_netstream_get_channel_binding(struct _php_stream *stream, php_openssl_channel_binding_type_t type, zend_string **out); +php_openssl_channel_binding_result php_openssl_netstream_get_channel_binding(struct _php_stream *stream, php_openssl_channel_binding_type type, zend_string **out); #if defined(HAVE_OPENSSL_ARGON2) diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index c20da430c32f..d2ebbb281eb0 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -3975,8 +3975,8 @@ static int php_openssl_get_cert_signature_md_nid(const X509 *cert) /* {{{ */ * stream PHP_OPENSSL_CHANNEL_BINDING_RESULT_NOT_TLS is returned, and if an OpenSSL call fails * PHP_OPENSSL_CHANNEL_BINDING_RESULT_ERROR is returned; in both of these cases *out is set to NULL. */ -php_openssl_channel_binding_result_t php_openssl_netstream_get_channel_binding( /* {{{ */ - php_stream *stream, php_openssl_channel_binding_type_t type, zend_string **out) +php_openssl_channel_binding_result php_openssl_netstream_get_channel_binding( /* {{{ */ + php_stream *stream, php_openssl_channel_binding_type type, zend_string **out) { unsigned char buf[EVP_MAX_MD_SIZE]; size_t len = 0; From 7d1e8b58ef12349334d302aa1c9b672ddf9a316f Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Tue, 27 Oct 2026 09:37:51 +0000 Subject: [PATCH 11/12] Use enum instead of string for channel type --- ext/openssl/openssl.c | 27 +++++----------- ext/openssl/openssl.stub.php | 17 ++++++++-- ext/openssl/openssl_arginfo.h | 28 ++++++++++++++-- ext/openssl/openssl_decl.h | 13 ++++++++ ext/openssl/php_openssl.h | 14 ++------ .../openssl_get_channel_binding_errors.phpt | 32 ++++++++++++------- .../openssl_get_channel_binding_tls12.phpt | 13 ++++---- .../openssl_get_channel_binding_tls13.phpt | 13 ++++---- ext/openssl/xp_ssl.c | 8 ++--- 9 files changed, 103 insertions(+), 62 deletions(-) create mode 100644 ext/openssl/openssl_decl.h diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index afcf042148a2..097adbf77c0f 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -57,6 +57,10 @@ ZEND_DECLARE_MODULE_GLOBALS(openssl) zend_class_entry *php_openssl_exception_ce; +/* Openssl\ChannelBindingType enum */ + +static zend_class_entry *php_openssl_channel_binding_type_ce; + /* OpenSSLCertificate class */ zend_class_entry *php_openssl_certificate_ce; @@ -757,6 +761,7 @@ PHP_INI_END() PHP_MINIT_FUNCTION(openssl) { php_openssl_exception_ce = register_class_Openssl_OpensslException(zend_ce_exception); + 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; @@ -5109,31 +5114,15 @@ PHP_FUNCTION(openssl_random_pseudo_bytes) PHP_FUNCTION(openssl_get_channel_binding) { php_stream *stream = NULL; - zend_string *type = NULL; + zend_enum_Openssl_ChannelBindingType type; ZEND_PARSE_PARAMETERS_START(2, 2) PHP_Z_PARAM_STREAM(stream) - Z_PARAM_STR(type) + Z_PARAM_ENUM(type, php_openssl_channel_binding_type_ce) ZEND_PARSE_PARAMETERS_END(); - php_openssl_channel_binding_type type_code; - if (zend_string_equals_literal(type, "tls-unique")) { - type_code = PHP_OPENSSL_CHANNEL_BINDING_TYPE_TLS_UNIQUE; - } else if (zend_string_equals_literal(type, "tls-server-end-point")) { - type_code = PHP_OPENSSL_CHANNEL_BINDING_TYPE_TLS_SERVER_ENDPOINT; - } else if (zend_string_equals_literal(type, "tls-exporter")) { - type_code = PHP_OPENSSL_CHANNEL_BINDING_TYPE_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(); - } - zend_string *result = NULL; - php_openssl_channel_binding_result ret = php_openssl_netstream_get_channel_binding(stream, type_code, &result); + 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); diff --git a/ext/openssl/openssl.stub.php b/ext/openssl/openssl.stub.php index 712cc8d22e94..6e48b69df49d 100644 --- a/ext/openssl/openssl.stub.php +++ b/ext/openssl/openssl.stub.php @@ -1,6 +1,9 @@ #include /* OpenSSL version check */ @@ -224,16 +226,6 @@ typedef struct _php_openssl_session_object { zend_object std; } php_openssl_session_object; -/* 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 openssl_get_channel_binding() are the IANA "Channel - * Binding" registry names. */ -typedef enum php_openssl_channel_binding_type { - PHP_OPENSSL_CHANNEL_BINDING_TYPE_TLS_UNIQUE = 0, - PHP_OPENSSL_CHANNEL_BINDING_TYPE_TLS_SERVER_ENDPOINT, - PHP_OPENSSL_CHANNEL_BINDING_TYPE_TLS_EXPORTER, -} php_openssl_channel_binding_type; - /* 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 */ @@ -254,7 +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, php_openssl_channel_binding_type type, zend_string **out); +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) diff --git a/ext/openssl/tests/openssl_get_channel_binding_errors.phpt b/ext/openssl/tests/openssl_get_channel_binding_errors.phpt index 7563b0321ead..e289dbeeae0f 100644 --- a/ext/openssl/tests/openssl_get_channel_binding_errors.phpt +++ b/ext/openssl/tests/openssl_get_channel_binding_errors.phpt @@ -4,17 +4,22 @@ openssl_get_channel_binding(): argument and type error handling openssl --FILE-- ValueError (checked before the stream). */ +/* 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"), "not-a-type"); + openssl_get_channel_binding(fopen("php://memory", "r"), "tls-unique"); echo "no error\n"; } catch (Throwable $e) { echo $e::class, ': ', $e->getMessage(), "\n"; } -/* Case-sensitivity: a mismatched case is also unknown. */ +/* An unknown string is not accepted either. */ try { - openssl_get_channel_binding(fopen("php://memory", "r"), "TLS-UNIQUE"); + openssl_get_channel_binding(fopen("php://memory", "r"), "not-a-type"); echo "no error\n"; } catch (Throwable $e) { echo $e::class, ': ', $e->getMessage(), "\n"; @@ -22,27 +27,30 @@ try { /* Non-stream argument -> TypeError. */ try { - openssl_get_channel_binding(123, "tls-unique"); + openssl_get_channel_binding(123, Openssl\ChannelBindingType::TlsUnique); echo "no error\n"; } catch (Throwable $e) { echo $e::class, ': ', $e->getMessage(), "\n"; } -/* A stream without transport encryption -> RuntimeException. */ +/* A stream without transport encryption -> Openssl\OpensslException. */ $plain = fopen("php://memory", "r"); -foreach (["tls-unique", "tls-server-end-point", "tls-exporter"] as $t) { +foreach (Openssl\ChannelBindingType::cases() as $type) { try { - openssl_get_channel_binding($plain, $t); - echo "$t: no error\n"; + openssl_get_channel_binding($plain, $type); + echo $type->value, ": no error\n"; } catch (Throwable $e) { - echo $t, ': ', $e::class, ': ', $e->getMessage(), "\n"; + echo $type->value, ': ', $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" +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 tls-unique: Openssl\OpensslException: Stream does not have transport encryption enabled tls-server-end-point: Openssl\OpensslException: Stream does not have transport encryption enabled diff --git a/ext/openssl/tests/openssl_get_channel_binding_tls12.phpt b/ext/openssl/tests/openssl_get_channel_binding_tls12.phpt index bb09b424aae8..651dfc0c743a 100644 --- a/ext/openssl/tests/openssl_get_channel_binding_tls12.phpt +++ b/ext/openssl/tests/openssl_get_channel_binding_tls12.phpt @@ -29,9 +29,9 @@ $serverCode = <<<'CODE' 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"); + foreach (Openssl\ChannelBindingType::cases() as $type) { + $v = openssl_get_channel_binding($conn, $type); + fwrite($conn, $type->value . "=" . (is_string($v) ? bin2hex($v) : "") . "\n"); } phpt_wait(); fclose($conn); @@ -55,8 +55,8 @@ $clientCode = <<<'CODE' } $my = []; - foreach (["tls-unique", "tls-server-end-point", "tls-exporter"] as $t) { - $my[$t] = openssl_get_channel_binding($client, $t); + foreach (Openssl\ChannelBindingType::cases() as $type) { + $my[$type->value] = openssl_get_channel_binding($client, $type); } $peer = []; @@ -70,7 +70,8 @@ $clientCode = <<<'CODE' $peer[$t] = ($enc === "") ? null : hex2bin($enc); } - foreach (["tls-unique", "tls-server-end-point", "tls-exporter"] as $t) { + foreach (Openssl\ChannelBindingType::cases() as $type) { + $t = $type->value; $c = $my[$t]; $s = $peer[$t]; $lc = is_string($c) ? strlen($c) : "null"; diff --git a/ext/openssl/tests/openssl_get_channel_binding_tls13.phpt b/ext/openssl/tests/openssl_get_channel_binding_tls13.phpt index e147dc27e0a7..f50e9e7edf8b 100644 --- a/ext/openssl/tests/openssl_get_channel_binding_tls13.phpt +++ b/ext/openssl/tests/openssl_get_channel_binding_tls13.phpt @@ -27,9 +27,9 @@ $serverCode = <<<'CODE' 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"); + foreach (Openssl\ChannelBindingType::cases() as $type) { + $v = openssl_get_channel_binding($conn, $type); + fwrite($conn, $type->value . "=" . (is_string($v) ? bin2hex($v) : "") . "\n"); } phpt_wait(); fclose($conn); @@ -50,8 +50,8 @@ $clientCode = <<<'CODE' } $my = []; - foreach (["tls-unique", "tls-server-end-point", "tls-exporter"] as $t) { - $my[$t] = openssl_get_channel_binding($client, $t); + foreach (Openssl\ChannelBindingType::cases() as $type) { + $my[$type->value] = openssl_get_channel_binding($client, $type); } $peer = []; @@ -65,7 +65,8 @@ $clientCode = <<<'CODE' $peer[$t] = ($enc === "") ? null : hex2bin($enc); } - foreach (["tls-unique", "tls-server-end-point", "tls-exporter"] as $t) { + foreach (Openssl\ChannelBindingType::cases() as $type) { + $t = $type->value; $c = $my[$t]; $s = $peer[$t]; $lc = is_string($c) ? strlen($c) : "null"; diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index d2ebbb281eb0..04381112e0eb 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -3976,7 +3976,7 @@ static int php_openssl_get_cert_signature_md_nid(const X509 *cert) /* {{{ */ * PHP_OPENSSL_CHANNEL_BINDING_RESULT_ERROR is returned; in both of these cases *out is set to NULL. */ php_openssl_channel_binding_result php_openssl_netstream_get_channel_binding( /* {{{ */ - php_stream *stream, php_openssl_channel_binding_type type, zend_string **out) + php_stream *stream, zend_enum_Openssl_ChannelBindingType type, zend_string **out) { unsigned char buf[EVP_MAX_MD_SIZE]; size_t len = 0; @@ -3995,7 +3995,7 @@ php_openssl_channel_binding_result php_openssl_netstream_get_channel_binding( /* ssl = sslsock->ssl_handle; switch (type) { - case PHP_OPENSSL_CHANNEL_BINDING_TYPE_TLS_UNIQUE: + case ZEND_ENUM_Openssl_ChannelBindingType_TlsUnique: /* RFC 5929 section 3: tls-unique is not defined for TLS 1.3. */ if (SSL_version(ssl) >= TLS1_3_VERSION) { return PHP_OPENSSL_CHANNEL_BINDING_RESULT_NOT_APPLICABLE; @@ -4020,7 +4020,7 @@ php_openssl_channel_binding_result php_openssl_netstream_get_channel_binding( /* *out = zend_string_init((char *)buf, len, 0); return *out != NULL ? PHP_OPENSSL_CHANNEL_BINDING_RESULT_OK : PHP_OPENSSL_CHANNEL_BINDING_RESULT_ERROR; - case PHP_OPENSSL_CHANNEL_BINDING_TYPE_TLS_SERVER_ENDPOINT: + case ZEND_ENUM_Openssl_ChannelBindingType_TlsServerEndPoint: { X509 *cert = sslsock->is_client ? SSL_get_peer_certificate(ssl) : SSL_get_certificate(ssl); @@ -4062,7 +4062,7 @@ php_openssl_channel_binding_result php_openssl_netstream_get_channel_binding( /* return *out != NULL ? PHP_OPENSSL_CHANNEL_BINDING_RESULT_OK : PHP_OPENSSL_CHANNEL_BINDING_RESULT_ERROR; } - case PHP_OPENSSL_CHANNEL_BINDING_TYPE_TLS_EXPORTER: + case ZEND_ENUM_Openssl_ChannelBindingType_TlsExporter: /* * RFC 9266 section 4 / RFC 5705: 32 octets, label * "EXPORTER-Channel-Binding", empty context. For an empty context the From 81098e13644d8a2ca92d2c4e47b5ecaab961ff84 Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Fri, 30 Oct 2026 10:43:54 +0000 Subject: [PATCH 12/12] Throw exception when channel binding is not supported Instead of returning null. --- Zend/Optimizer/zend_func_infos.h | 2 +- ext/openssl/openssl.c | 26 ++++++- ext/openssl/openssl.stub.php | 6 +- ext/openssl/openssl_arginfo.h | 14 +++- ext/openssl/openssl_decl.h | 8 +-- ext/openssl/php_openssl.h | 2 +- .../openssl_get_channel_binding_errors.phpt | 15 ++-- .../openssl_get_channel_binding_psk.phpt | 71 +++++++++++++++++++ .../openssl_get_channel_binding_tls13.phpt | 24 +++++-- ext/openssl/xp_ssl.c | 6 +- 10 files changed, 149 insertions(+), 25 deletions(-) create mode 100644 ext/openssl/tests/openssl_get_channel_binding_psk.phpt diff --git a/Zend/Optimizer/zend_func_infos.h b/Zend/Optimizer/zend_func_infos.h index 7367018fef4d..f862fee58711 100644 --- a/Zend/Optimizer/zend_func_infos.h +++ b/Zend/Optimizer/zend_func_infos.h @@ -297,7 +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), + 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), diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index 097adbf77c0f..7b19fe325992 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -57,6 +57,10 @@ 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; @@ -761,6 +765,8 @@ 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(); @@ -5127,10 +5133,24 @@ PHP_FUNCTION(openssl_get_channel_binding) case PHP_OPENSSL_CHANNEL_BINDING_RESULT_OK: RETURN_STR(result); case PHP_OPENSSL_CHANNEL_BINDING_RESULT_NOT_APPLICABLE: - RETURN_NULL(); + 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_ex(php_openssl_exception_ce, 0, - "Stream does not have transport encryption enabled"); + zend_throw_exception(zend_ce_value_error, + "Stream does not have transport encryption enabled", 0); RETURN_THROWS(); case PHP_OPENSSL_CHANNEL_BINDING_RESULT_ERROR: { diff --git a/ext/openssl/openssl.stub.php b/ext/openssl/openssl.stub.php index 6e48b69df49d..93f881b525b3 100644 --- a/ext/openssl/openssl.stub.php +++ b/ext/openssl/openssl.stub.php @@ -11,6 +11,10 @@ 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. @@ -793,6 +797,6 @@ function openssl_password_verify(string $algo, #[\SensitiveParameter] string $pa * @param resource $stream * @refcount 1 */ -function openssl_get_channel_binding($stream, Openssl\ChannelBindingType $channel_binding_type): ?string {} +function openssl_get_channel_binding($stream, Openssl\ChannelBindingType $channel_binding_type): string {} } diff --git a/ext/openssl/openssl_arginfo.h b/ext/openssl/openssl_arginfo.h index 91dca4f68684..ea0694549c48 100644 --- a/ext/openssl/openssl_arginfo.h +++ b/ext/openssl/openssl_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit openssl.stub.php instead. - * Stub hash: ebf128e671cd7d9caf1b793173dd5b5acf8dbc8d + * Stub hash: 3da1308c2a61aeb7a402d7c9b409aa001f73db94 * Has decl header: yes */ #include "zend_attributes.h" @@ -413,7 +413,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_password_verify, 0, 3, _ ZEND_END_ARG_INFO() #endif -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_get_channel_binding, 0, 2, IS_STRING, 1) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_get_channel_binding, 0, 2, IS_STRING, 0) ZEND_ARG_INFO(0, stream) ZEND_ARG_OBJ_INFO(0, channel_binding_type, Openssl\\ChannelBindingType, 0) ZEND_END_ARG_INFO() @@ -848,6 +848,16 @@ static zend_class_entry *register_class_Openssl_OpensslException(zend_class_entr return class_entry; } +static zend_class_entry *register_class_Openssl_UnsupportedChannelBindingException(zend_class_entry *class_entry_Openssl_OpensslException) +{ + zend_class_entry ce, *class_entry; + + INIT_NS_CLASS_ENTRY(ce, "Openssl", "UnsupportedChannelBindingException", NULL); + class_entry = zend_register_internal_class_with_flags(&ce, class_entry_Openssl_OpensslException, 0); + + return class_entry; +} + static zend_class_entry *register_class_Openssl_ChannelBindingType(void) { zend_class_entry *class_entry = zend_register_internal_enum("Openssl\\ChannelBindingType", IS_STRING, NULL); diff --git a/ext/openssl/openssl_decl.h b/ext/openssl/openssl_decl.h index aaea6859a118..3351cded1243 100644 --- a/ext/openssl/openssl_decl.h +++ b/ext/openssl/openssl_decl.h @@ -1,8 +1,8 @@ /* This is a generated file, edit openssl.stub.php instead. - * Stub hash: ebf128e671cd7d9caf1b793173dd5b5acf8dbc8d */ + * Stub hash: 3da1308c2a61aeb7a402d7c9b409aa001f73db94 */ -#ifndef ZEND_OPENSSL_DECL_ebf128e671cd7d9caf1b793173dd5b5acf8dbc8d_H -#define ZEND_OPENSSL_DECL_ebf128e671cd7d9caf1b793173dd5b5acf8dbc8d_H +#ifndef ZEND_OPENSSL_DECL_3da1308c2a61aeb7a402d7c9b409aa001f73db94_H +#define ZEND_OPENSSL_DECL_3da1308c2a61aeb7a402d7c9b409aa001f73db94_H typedef enum zend_enum_Openssl_ChannelBindingType { ZEND_ENUM_Openssl_ChannelBindingType_TlsUnique = 1, @@ -10,4 +10,4 @@ typedef enum zend_enum_Openssl_ChannelBindingType { ZEND_ENUM_Openssl_ChannelBindingType_TlsExporter = 3, } zend_enum_Openssl_ChannelBindingType; -#endif /* ZEND_OPENSSL_DECL_ebf128e671cd7d9caf1b793173dd5b5acf8dbc8d_H */ +#endif /* ZEND_OPENSSL_DECL_3da1308c2a61aeb7a402d7c9b409aa001f73db94_H */ diff --git a/ext/openssl/php_openssl.h b/ext/openssl/php_openssl.h index 909f855d1799..adb3090e2c16 100644 --- a/ext/openssl/php_openssl.h +++ b/ext/openssl/php_openssl.h @@ -229,7 +229,7 @@ typedef struct _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) */ + 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; diff --git a/ext/openssl/tests/openssl_get_channel_binding_errors.phpt b/ext/openssl/tests/openssl_get_channel_binding_errors.phpt index e289dbeeae0f..d7b02be641d5 100644 --- a/ext/openssl/tests/openssl_get_channel_binding_errors.phpt +++ b/ext/openssl/tests/openssl_get_channel_binding_errors.phpt @@ -33,7 +33,13 @@ try { echo $e::class, ': ', $e->getMessage(), "\n"; } -/* A stream without transport encryption -> Openssl\OpensslException. */ +/* 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 { @@ -52,6 +58,7 @@ 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 -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 +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 diff --git a/ext/openssl/tests/openssl_get_channel_binding_psk.phpt b/ext/openssl/tests/openssl_get_channel_binding_psk.phpt new file mode 100644 index 000000000000..0acb53c5b792 --- /dev/null +++ b/ext/openssl/tests/openssl_get_channel_binding_psk.phpt @@ -0,0 +1,71 @@ +--TEST-- +openssl_get_channel_binding(): TLS 1.2 PSK, tls-server-end-point is not applicable +--EXTENSIONS-- +openssl +--SKIPIF-- += 1.1.1 required"); +?> +--FILE-- + [ + '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 diff --git a/ext/openssl/tests/openssl_get_channel_binding_tls13.phpt b/ext/openssl/tests/openssl_get_channel_binding_tls13.phpt index f50e9e7edf8b..8dd4caf48e48 100644 --- a/ext/openssl/tests/openssl_get_channel_binding_tls13.phpt +++ b/ext/openssl/tests/openssl_get_channel_binding_tls13.phpt @@ -28,8 +28,12 @@ $serverCode = <<<'CODE' exit(1); } foreach (Openssl\ChannelBindingType::cases() as $type) { - $v = openssl_get_channel_binding($conn, $type); - fwrite($conn, $type->value . "=" . (is_string($v) ? bin2hex($v) : "") . "\n"); + try { + $v = openssl_get_channel_binding($conn, $type); + fwrite($conn, $type->value . "=" . bin2hex($v) . "\n"); + } catch (Openssl\UnsupportedChannelBindingException) { + fwrite($conn, $type->value . "=unsupported\n"); + } } phpt_wait(); fclose($conn); @@ -50,8 +54,14 @@ $clientCode = <<<'CODE' } $my = []; + $myException = []; foreach (Openssl\ChannelBindingType::cases() as $type) { - $my[$type->value] = openssl_get_channel_binding($client, $type); + try { + $my[$type->value] = openssl_get_channel_binding($client, $type); + } catch (Openssl\UnsupportedChannelBindingException $e) { + $my[$type->value] = null; + $myException[$type->value] = $e::class . ': ' . $e->getMessage(); + } } $peer = []; @@ -62,7 +72,7 @@ $clientCode = <<<'CODE' $eq = strpos($line, "="); $t = substr($line, 0, $eq); $enc = substr($line, $eq + 1); - $peer[$t] = ($enc === "") ? null : hex2bin($enc); + $peer[$t] = ($enc === "unsupported") ? null : hex2bin($enc); } foreach (Openssl\ChannelBindingType::cases() as $type) { @@ -75,7 +85,8 @@ $clientCode = <<<'CODE' $t, var_export($c === $s, true), $lc, $ls); } - echo "tls_unique_is_null=" . var_export($my["tls-unique"] === null, true) . "\n"; + echo "tls_unique_unsupported=" . var_export($my["tls-unique"] === null, true) . "\n"; + echo "tls_unique_message=" . ($myException["tls-unique"] ?? "none") . "\n"; $opts = stream_context_get_options($client); $cert = $opts['ssl']['peer_certificate'] ?? null; @@ -102,5 +113,6 @@ ServerClientTestCase::getInstance()->run($clientCode, ['server' => $serverCode]) tls-unique equal=true len_client=null len_server=null tls-server-end-point equal=true len_client=32 len_server=32 tls-exporter equal=true len_client=32 len_server=32 -tls_unique_is_null=true +tls_unique_unsupported=true +tls_unique_message=Openssl\UnsupportedChannelBindingException: Channel binding type "tls-unique" is not supported for TLS 1.3 tse_matches_sha256_fingerprint=true diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index 04381112e0eb..d7c0f94bf150 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -3970,8 +3970,8 @@ static int php_openssl_get_cert_signature_md_nid(const X509 *cert) /* {{{ */ * * On success *out is set to a newly-allocated zend_string holding the data * and PHP_OPENSSL_CHANNEL_BINDING_RESULT_OK is returned. For a type that is not applicable to the - * connection (currently: tls-unique over TLS 1.3) *out is set to NULL and - * PHP_OPENSSL_CHANNEL_BINDING_RESULT_NOT_APPLICABLE is returned. If the stream is not an active TLS + * connection (tls-unique over TLS 1.3, or tls-server-end-point without a certificate) *out is set + * to NULL and PHP_OPENSSL_CHANNEL_BINDING_RESULT_NOT_APPLICABLE is returned. If the stream is not an active TLS * stream PHP_OPENSSL_CHANNEL_BINDING_RESULT_NOT_TLS is returned, and if an OpenSSL call fails * PHP_OPENSSL_CHANNEL_BINDING_RESULT_ERROR is returned; in both of these cases *out is set to NULL. */ @@ -4029,7 +4029,7 @@ php_openssl_channel_binding_result php_openssl_netstream_get_channel_binding( /* unsigned int digest_len = 0; if (cert == NULL) { - return PHP_OPENSSL_CHANNEL_BINDING_RESULT_ERROR; + return PHP_OPENSSL_CHANNEL_BINDING_RESULT_NOT_APPLICABLE; } md_nid = php_openssl_get_cert_signature_md_nid(cert);