From 16155f1170134f020ccc1668647d5f4ea1fa7aae Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Sat, 21 Jun 2025 19:48:51 +0200 Subject: [PATCH] Fix bug #80770: openssl cafile not used in SNI SSL_CTX The issue is about not being able to connect as cafile for SNI is not used in its SSL context. This sets it up so it is possible to capture the client certificate which is only possible when verify_peer is true. --- ext/openssl/tests/bug80770.phpt | 83 +++++++++++++++++++++++++++++++++ ext/openssl/xp_ssl.c | 20 ++++++-- 2 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 ext/openssl/tests/bug80770.phpt diff --git a/ext/openssl/tests/bug80770.phpt b/ext/openssl/tests/bug80770.phpt new file mode 100644 index 0000000000000..9100aaa5aa188 --- /dev/null +++ b/ext/openssl/tests/bug80770.phpt @@ -0,0 +1,83 @@ +--TEST-- +Bug #80770: SNI_server_certs does not inherit peer verification options +--EXTENSIONS-- +openssl +--SKIPIF-- + +--FILE-- + [ + 'SNI_server_certs' => [ + "cs.php.net" => __DIR__ . "/sni_server_cs.pem", + "uk.php.net" => __DIR__ . "/sni_server_uk.pem", + "us.php.net" => __DIR__ . "/sni_server_us.pem" + ], + 'verify_peer' => true, + 'cafile' => '%s', + 'capture_peer_cert' => true, + 'verify_peer_name' => false, + 'security_level' => 0, + ]]); + $server = stream_socket_server('tcp://127.0.0.1:0', $errno, $errstr, $flags, $ctx); + phpt_notify_server_start($server); + + $client = stream_socket_accept($server, 30); + if ($client) { + $success = stream_socket_enable_crypto($client, true, STREAM_CRYPTO_METHOD_TLS_SERVER); + if ($success) { + $options = stream_context_get_options($client); + $hasCert = isset($options['ssl']['peer_certificate']); + phpt_notify(message: $hasCert ? "CLIENT_CERT_CAPTURED" : "NO_CLIENT_CERT"); + } else { + phpt_notify(message: "TLS_HANDSHAKE_FAILED"); + } + } else { + phpt_notify(message: "ACCEPT_FAILED"); + } +CODE; +$serverCode = sprintf($serverCode, $caCertFile); + +$clientCode = <<<'CODE' + $flags = STREAM_CLIENT_CONNECT; + $ctx = stream_context_create(['ssl' => [ + 'verify_peer' => false, + 'verify_peer_name' => false, + 'local_cert' => '%s', + 'peer_name' => 'cs.php.net', + 'security_level' => 0, + ]]); + $client = stream_socket_client("tcp://{{ ADDR }}", $errno, $errstr, 30, $flags, $ctx); + if ($client) { + stream_socket_enable_crypto($client, true, STREAM_CRYPTO_METHOD_TLS_CLIENT); + } + + $result = phpt_wait(); + echo trim($result); +CODE; +$clientCode = sprintf($clientCode, $clientCertFile); + +include 'CertificateGenerator.inc'; + +// Generate CA and client certificate signed by that CA +$certificateGenerator = new CertificateGenerator(); +$certificateGenerator->saveCaCert($caCertFile); +$certificateGenerator->saveNewCertAsFileWithKey('Bug80770 Test Client', $clientCertFile); + +include 'ServerClientTestCase.inc'; +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--CLEAN-- + +--EXPECTF-- +CLIENT_CERT_CAPTURED diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index 1c7aabefa1bcd..37ab9c4012d84 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -1443,7 +1443,8 @@ static SSL_CTX *php_openssl_create_sni_server_ctx(char *cert_path, char *key_pat } /* }}} */ -static zend_result php_openssl_enable_server_sni(php_stream *stream, php_openssl_netstream_data_t *sslsock) /* {{{ */ +static zend_result php_openssl_enable_server_sni( + php_stream *stream, php_openssl_netstream_data_t *sslsock, bool verify_peer) { zval *val; zval *current; @@ -1564,6 +1565,12 @@ static zend_result php_openssl_enable_server_sni(php_stream *stream, php_openssl return FAILURE; } + if (!verify_peer) { + php_openssl_disable_peer_verification(ctx, stream); + } else if (FAILURE == php_openssl_enable_peer_verification(ctx, stream)) { + return FAILURE; + } + sslsock->sni_certs[i].name = pestrdup(ZSTR_VAL(key), php_stream_is_persistent(stream)); sslsock->sni_certs[i].ctx = ctx; ++i; @@ -1574,7 +1581,6 @@ static zend_result php_openssl_enable_server_sni(php_stream *stream, php_openssl return SUCCESS; } -/* }}} */ static void php_openssl_enable_client_sni(php_stream *stream, php_openssl_netstream_data_t *sslsock) /* {{{ */ { @@ -1666,6 +1672,7 @@ zend_result php_openssl_setup_crypto(php_stream *stream, char *cipherlist = NULL; char *alpn_protocols = NULL; zval *val; + bool verify_peer = false; if (sslsock->ssl_handle) { if (sslsock->s.is_blocked) { @@ -1717,8 +1724,11 @@ zend_result php_openssl_setup_crypto(php_stream *stream, if (GET_VER_OPT("verify_peer") && !zend_is_true(val)) { php_openssl_disable_peer_verification(sslsock->ctx, stream); - } else if (FAILURE == php_openssl_enable_peer_verification(sslsock->ctx, stream)) { - return FAILURE; + } else { + verify_peer = true; + if (FAILURE == php_openssl_enable_peer_verification(sslsock->ctx, stream)) { + return FAILURE; + } } /* callback for the passphrase (for localcert) */ @@ -1819,7 +1829,7 @@ zend_result php_openssl_setup_crypto(php_stream *stream, #ifdef HAVE_TLS_SNI /* Enable server-side SNI */ - if (!sslsock->is_client && php_openssl_enable_server_sni(stream, sslsock) == FAILURE) { + if (!sslsock->is_client && php_openssl_enable_server_sni(stream, sslsock, verify_peer) == FAILURE) { return FAILURE; } #endif