diff --git a/Zend/Optimizer/zend_func_infos.h b/Zend/Optimizer/zend_func_infos.h index 0add5a32f911..f862fee58711 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("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 b05ee7418d0c..7b19fe325992 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -57,6 +57,14 @@ ZEND_DECLARE_MODULE_GLOBALS(openssl) zend_class_entry *php_openssl_exception_ce; +/* Openssl\UnsupportedChannelBindingException class */ + +static zend_class_entry *php_openssl_unsupported_channel_binding_exception_ce; + +/* Openssl\ChannelBindingType enum */ + +static zend_class_entry *php_openssl_channel_binding_type_ce; + /* OpenSSLCertificate class */ zend_class_entry *php_openssl_certificate_ce; @@ -757,6 +765,9 @@ PHP_INI_END() PHP_MINIT_FUNCTION(openssl) { php_openssl_exception_ce = register_class_Openssl_OpensslException(zend_ce_exception); + php_openssl_unsupported_channel_binding_exception_ce = + register_class_Openssl_UnsupportedChannelBindingException(php_openssl_exception_ce); + php_openssl_channel_binding_type_ce = register_class_Openssl_ChannelBindingType(); php_openssl_certificate_ce = register_class_OpenSSLCertificate(); php_openssl_certificate_ce->create_object = php_openssl_certificate_create_object; @@ -5104,3 +5115,60 @@ PHP_FUNCTION(openssl_random_pseudo_bytes) } } /* }}} */ + +/* {{{ */ +PHP_FUNCTION(openssl_get_channel_binding) +{ + php_stream *stream = NULL; + zend_enum_Openssl_ChannelBindingType type; + + ZEND_PARSE_PARAMETERS_START(2, 2) + PHP_Z_PARAM_STREAM(stream) + Z_PARAM_ENUM(type, php_openssl_channel_binding_type_ce) + ZEND_PARSE_PARAMETERS_END(); + + zend_string *result = NULL; + php_openssl_channel_binding_result ret = php_openssl_netstream_get_channel_binding(stream, type, &result); + switch (ret) { + case PHP_OPENSSL_CHANNEL_BINDING_RESULT_OK: + RETURN_STR(result); + case PHP_OPENSSL_CHANNEL_BINDING_RESULT_NOT_APPLICABLE: + switch (type) { + case ZEND_ENUM_Openssl_ChannelBindingType_TlsUnique: + zend_throw_exception(php_openssl_unsupported_channel_binding_exception_ce, + "Channel binding type \"tls-unique\" is not supported for TLS 1.3", 0); + break; + case ZEND_ENUM_Openssl_ChannelBindingType_TlsServerEndPoint: + zend_throw_exception(php_openssl_unsupported_channel_binding_exception_ce, + "Channel binding type \"tls-server-end-point\" requires a certificate", 0); + break; + default: + zend_throw_exception(php_openssl_unsupported_channel_binding_exception_ce, + "Channel binding type is not applicable to this connection", 0); + break; + } + RETURN_THROWS(); + case PHP_OPENSSL_CHANNEL_BINDING_RESULT_NOT_TLS: + zend_throw_exception(zend_ce_value_error, + "Stream does not have transport encryption enabled", 0); + RETURN_THROWS(); + case PHP_OPENSSL_CHANNEL_BINDING_RESULT_ERROR: + { + unsigned long err = ERR_peek_last_error(); + if (err != 0) { + char errstr[256]; + + (void)ERR_error_string_n(err, errstr, sizeof(errstr)); + zend_throw_exception_ex(php_openssl_exception_ce, 0, + "Failed to get channel binding data: %s", errstr); + } else { + zend_throw_exception_ex(php_openssl_exception_ce, 0, + "Failed to get channel binding data"); + } + } + RETURN_THROWS(); + default: + ZEND_UNREACHABLE(); + } +} +/* }}} */ diff --git a/ext/openssl/openssl.stub.php b/ext/openssl/openssl.stub.php index 3d3fa3ea634f..93f881b525b3 100644 --- a/ext/openssl/openssl.stub.php +++ b/ext/openssl/openssl.stub.php @@ -1,6 +1,9 @@ #include /* OpenSSL version check */ @@ -224,6 +226,14 @@ typedef struct _php_openssl_session_object { zend_object std; } php_openssl_session_object; +/* Result of php_openssl_netstream_get_channel_binding(). */ +typedef enum php_openssl_channel_binding_result { + PHP_OPENSSL_CHANNEL_BINDING_RESULT_OK = 0, /* *out holds a zend_string with the data */ + PHP_OPENSSL_CHANNEL_BINDING_RESULT_NOT_APPLICABLE, /* *out = NULL (e.g. tls-unique over TLS 1.3, or tls-server-end-point without a certificate) */ + PHP_OPENSSL_CHANNEL_BINDING_RESULT_NOT_TLS, /* stream is not an active TLS stream */ + PHP_OPENSSL_CHANNEL_BINDING_RESULT_ERROR, /* an OpenSSL-level failure occurred */ +} php_openssl_channel_binding_result; + static inline php_openssl_session_object *php_openssl_session_from_obj(zend_object *obj) { return (php_openssl_session_object *)((char *)(obj) - offsetof(php_openssl_session_object, std)); } @@ -236,6 +246,7 @@ extern zend_class_entry *php_openssl_session_ce; void php_openssl_session_object_init(zval *zv, SSL_SESSION *session); bool php_openssl_is_session_ce(zval *val); SSL_SESSION *php_openssl_session_from_zval(zval *zv); +php_openssl_channel_binding_result php_openssl_netstream_get_channel_binding(struct _php_stream *stream, zend_enum_Openssl_ChannelBindingType type, zend_string **out); #if defined(HAVE_OPENSSL_ARGON2) diff --git a/ext/openssl/tests/openssl_get_channel_binding_errors.phpt b/ext/openssl/tests/openssl_get_channel_binding_errors.phpt new file mode 100644 index 000000000000..d7b02be641d5 --- /dev/null +++ b/ext/openssl/tests/openssl_get_channel_binding_errors.phpt @@ -0,0 +1,64 @@ +--TEST-- +openssl_get_channel_binding(): argument and type error handling +--EXTENSIONS-- +openssl +--FILE-- +name, '=', $type->value, "\n"; +} + +/* A string is not accepted; the parameter requires the enum. */ +try { + openssl_get_channel_binding(fopen("php://memory", "r"), "tls-unique"); + echo "no error\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +/* An unknown string is not accepted either. */ +try { + openssl_get_channel_binding(fopen("php://memory", "r"), "not-a-type"); + echo "no error\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +/* Non-stream argument -> TypeError. */ +try { + openssl_get_channel_binding(123, Openssl\ChannelBindingType::TlsUnique); + echo "no error\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +/* The unsupported-type exception extends the general OpenSSL exception. */ +var_dump(is_subclass_of( + Openssl\UnsupportedChannelBindingException::class, + Openssl\OpensslException::class, +)); + +/* A stream without transport encryption -> ValueError. */ +$plain = fopen("php://memory", "r"); +foreach (Openssl\ChannelBindingType::cases() as $type) { + try { + openssl_get_channel_binding($plain, $type); + echo $type->value, ": no error\n"; + } catch (Throwable $e) { + echo $type->value, ': ', $e::class, ': ', $e->getMessage(), "\n"; + } +} +fclose($plain); +?> +--EXPECT-- +TlsUnique=tls-unique +TlsServerEndPoint=tls-server-end-point +TlsExporter=tls-exporter +TypeError: openssl_get_channel_binding(): Argument #2 ($channel_binding_type) must be of type Openssl\ChannelBindingType, string given +TypeError: openssl_get_channel_binding(): Argument #2 ($channel_binding_type) must be of type Openssl\ChannelBindingType, string given +TypeError: openssl_get_channel_binding(): Argument #1 ($stream) must be of type resource, int given +bool(true) +tls-unique: ValueError: Stream does not have transport encryption enabled +tls-server-end-point: ValueError: Stream does not have transport encryption enabled +tls-exporter: ValueError: Stream does not have transport encryption enabled 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_tls12.phpt b/ext/openssl/tests/openssl_get_channel_binding_tls12.phpt new file mode 100644 index 000000000000..651dfc0c743a --- /dev/null +++ b/ext/openssl/tests/openssl_get_channel_binding_tls12.phpt @@ -0,0 +1,108 @@ +--TEST-- +openssl_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 (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); +CODE; +$serverCode = sprintf($serverCode, $certFile); + +/* Client: complete the handshake, compute its own values, read the server's + * values and report whether they agree. Also check tls-server-end-point against + * the SHA-256 fingerprint of the captured peer certificate. */ +$clientCode = <<<'CODE' + $ctx = stream_context_create(['ssl' => [ + 'verify_peer' => false, + 'verify_peer_name' => false, + 'capture_peer_cert' => true, + 'security_level' => 0, + ]]); + $client = stream_socket_client("tlsv1.2://{{ ADDR }}", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $ctx); + if ($client === false) { + echo "client connect failed\n"; + exit(1); + } + + $my = []; + foreach (Openssl\ChannelBindingType::cases() as $type) { + $my[$type->value] = openssl_get_channel_binding($client, $type); + } + + $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 (Openssl\ChannelBindingType::cases() as $type) { + $t = $type->value; + $c = $my[$t]; + $s = $peer[$t]; + $lc = is_string($c) ? strlen($c) : "null"; + $ls = is_string($s) ? strlen($s) : "null"; + printf("%s equal=%s len_client=%s len_server=%s\n", + $t, var_export($c === $s, true), $lc, $ls); + } + + $opts = stream_context_get_options($client); + $cert = $opts['ssl']['peer_certificate'] ?? null; + $fpr = $cert ? openssl_x509_fingerprint($cert, "sha256", true) : null; + echo "tse_matches_sha256_fingerprint=" + . var_export($fpr !== null && $my["tls-server-end-point"] === $fpr, true) . "\n"; + + phpt_notify('server'); + fclose($client); +CODE; + +include 'CertificateGenerator.inc'; +$generator = new CertificateGenerator(); +$generator->saveNewCertAsFileWithKey('cb-tls12', $certFile); + +include 'ServerClientTestCase.inc'; +ServerClientTestCase::getInstance()->run($clientCode, ['server' => $serverCode]); +?> +--CLEAN-- + +--EXPECTF-- +tls-unique equal=true len_client=%d len_server=%d +tls-server-end-point equal=true len_client=32 len_server=32 +tls-exporter equal=true len_client=32 len_server=32 +tse_matches_sha256_fingerprint=true diff --git a/ext/openssl/tests/openssl_get_channel_binding_tls13.phpt b/ext/openssl/tests/openssl_get_channel_binding_tls13.phpt new file mode 100644 index 000000000000..8dd4caf48e48 --- /dev/null +++ b/ext/openssl/tests/openssl_get_channel_binding_tls13.phpt @@ -0,0 +1,118 @@ +--TEST-- +openssl_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 (Openssl\ChannelBindingType::cases() as $type) { + 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); +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 = []; + $myException = []; + foreach (Openssl\ChannelBindingType::cases() as $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 = []; + 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 === "unsupported") ? null : hex2bin($enc); + } + + foreach (Openssl\ChannelBindingType::cases() as $type) { + $t = $type->value; + $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_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; + $fpr = $cert ? openssl_x509_fingerprint($cert, "sha256", true) : null; + echo "tse_matches_sha256_fingerprint=" + . var_export($fpr !== null && $my["tls-server-end-point"] === $fpr, true) . "\n"; + + phpt_notify('server'); + fclose($client); +CODE; + +include 'CertificateGenerator.inc'; +$generator = new CertificateGenerator(); +$generator->saveNewCertAsFileWithKey('cb-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-end-point equal=true len_client=32 len_server=32 +tls-exporter equal=true len_client=32 len_server=32 +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 854227318da0..d7c0f94bf150 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -23,6 +23,7 @@ #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 "php_openssl.h" @@ -35,6 +36,7 @@ #include #include #include +#include #ifdef PHP_WIN32 #include "win32/winutil.h" @@ -3946,6 +3948,142 @@ static const php_stream_ops php_openssl_socket_ops = { php_openssl_sockop_set_option, }; +/* + * Get the NID of the message-digest algorithm used to sign a certificate, + * or NID_undef if it cannot be determined. + */ +static int php_openssl_get_cert_signature_md_nid(const X509 *cert) /* {{{ */ +{ + 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; + } + + 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_OPENSSL_CHANNEL_BINDING_RESULT_OK is returned. For a type that is not applicable to the + * 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. + */ +php_openssl_channel_binding_result php_openssl_netstream_get_channel_binding( /* {{{ */ + php_stream *stream, zend_enum_Openssl_ChannelBindingType 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_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_OPENSSL_CHANNEL_BINDING_RESULT_NOT_TLS; + } + ssl = sslsock->ssl_handle; + + switch (type) { + 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; + } + + /* + * 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_OPENSSL_CHANNEL_BINDING_RESULT_ERROR; + } + *out = zend_string_init((char *)buf, len, 0); + return *out != NULL ? PHP_OPENSSL_CHANNEL_BINDING_RESULT_OK : PHP_OPENSSL_CHANNEL_BINDING_RESULT_ERROR; + + case ZEND_ENUM_Openssl_ChannelBindingType_TlsServerEndPoint: + { + 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_OPENSSL_CHANNEL_BINDING_RESULT_NOT_APPLICABLE; + } + + 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_OPENSSL_CHANNEL_BINDING_RESULT_ERROR; + } + *out = zend_string_init((char *)buf, digest_len, 0); + return *out != NULL ? PHP_OPENSSL_CHANNEL_BINDING_RESULT_OK : PHP_OPENSSL_CHANNEL_BINDING_RESULT_ERROR; + } + + 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 + * 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_OPENSSL_CHANNEL_BINDING_RESULT_ERROR; + } + *out = zend_string_init((char *)buf, 32, 0); + return *out != NULL ? PHP_OPENSSL_CHANNEL_BINDING_RESULT_OK : PHP_OPENSSL_CHANNEL_BINDING_RESULT_ERROR; + + default: + return PHP_OPENSSL_CHANNEL_BINDING_RESULT_ERROR; + } +} +/* }}} */ + static zend_long php_openssl_get_crypto_method( php_stream_context *ctx, zend_long crypto_method) /* {{{ */ {