diff --git a/NEWS b/NEWS index 9a901f31dbb0..a024bae23063 100644 --- a/NEWS +++ b/NEWS @@ -89,6 +89,8 @@ PHP NEWS structures with persistent streams). (cxxz16, David Carlier) . Fixed an exception thrown by the session_get_cb stream context callback being replaced with a TypeError. (Ilia Alshanetsky) + . Fixed SNI_server_certs being ignored on a server stream that reuses the + SSL context of a session stream. (Jakub Zelenka) - PDO_Firebird: . Fixed stack buffer overflow with long connection user names, passwords, diff --git a/ext/openssl/tests/session_resumption_server_sni.phpt b/ext/openssl/tests/session_resumption_server_sni.phpt new file mode 100644 index 000000000000..4607d197e301 --- /dev/null +++ b/ext/openssl/tests/session_resumption_server_sni.phpt @@ -0,0 +1,78 @@ +--TEST-- +Server SNI certificate selection on a stream seeded from a session stream +--EXTENSIONS-- +openssl +--FILE-- +saveNewCertAsFileWithKey('cs.php.net', $csFile); +$certificateGenerator->saveNewCertAsFileWithKey('uk.php.net', $ukFile); +$certificateGenerator->saveNewCertAsFileWithKey('us.php.net', $usFile); + +$server = stream_socket_server('tcp://127.0.0.1:0'); +$addr = stream_socket_get_name($server, false); + +function handshake(string $addr, $server, string $peerName, $session, array $serverOptions = []): array +{ + $client = stream_socket_client("tcp://$addr", $errno, $errstr, 5, STREAM_CLIENT_CONNECT, + stream_context_create(['ssl' => [ + 'verify_peer' => false, + 'verify_peer_name' => false, + 'capture_peer_cert' => true, + 'peer_name' => $peerName, + ]])); + $accepted = stream_socket_accept($server); + if ($serverOptions) { + stream_context_set_options($accepted, ['ssl' => $serverOptions]); + } + stream_set_blocking($client, false); + stream_set_blocking($accepted, false); + + $clientDone = $serverDone = 0; + for ($i = 0; $i < 1000 && ($clientDone !== true || $serverDone !== true); $i++) { + if ($clientDone !== true) { + $clientDone = stream_socket_enable_crypto($client, true, STREAM_CRYPTO_METHOD_TLS_CLIENT); + } + if ($serverDone !== true) { + $serverDone = stream_socket_enable_crypto($accepted, true, STREAM_CRYPTO_METHOD_TLS_SERVER, $session); + } + usleep(1000); + } + + $cert = stream_context_get_options($client)['ssl']['peer_certificate']; + var_dump(openssl_x509_parse($cert)['subject']['CN']); + + return [$accepted, $client]; +} + +$parent = handshake($addr, $server, 'uk.php.net', null, [ + 'local_cert' => $csFile, + 'SNI_server_certs' => [ + 'cs.php.net' => $csFile, + 'uk.php.net' => $ukFile, + 'us.php.net' => $usFile, + ], +]); +$child = handshake($addr, $server, 'us.php.net', $parent[0]); + +fclose($parent[0]); +fclose($parent[1]); +unset($parent); + +$grandchild = handshake($addr, $server, 'uk.php.net', $child[0]); +?> +--CLEAN-- + +--EXPECT-- +string(10) "uk.php.net" +string(10) "us.php.net" +string(10) "uk.php.net" diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index 60cbf78ae719..82d14ba5d8c9 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -1584,6 +1584,33 @@ static zend_result php_openssl_enable_server_sni( return SUCCESS; } +static void php_openssl_share_server_sni(php_stream *stream, php_openssl_netstream_data_t *sslsock, + const php_openssl_netstream_data_t *parent_sslsock) +{ + if (sslsock->sni_certs || !parent_sslsock->sni_certs || !parent_sslsock->sni_cert_count) { + return; + } + + bool persistent = php_stream_is_persistent(stream); + unsigned count = 0; + + sslsock->sni_certs = safe_pemalloc(parent_sslsock->sni_cert_count, + sizeof(php_openssl_sni_cert_t), 0, persistent); + + for (unsigned i = 0; i < parent_sslsock->sni_cert_count; i++) { + const php_openssl_sni_cert_t *parent_cert = &parent_sslsock->sni_certs[i]; + if (!parent_cert->ctx) { + continue; + } + SSL_CTX_up_ref(parent_cert->ctx); + sslsock->sni_certs[count].ctx = parent_cert->ctx; + sslsock->sni_certs[count].name = pestrdup(parent_cert->name, persistent); + count++; + } + + sslsock->sni_cert_count = count; +} + static void php_openssl_enable_client_sni(php_stream *stream, php_openssl_netstream_data_t *sslsock) /* {{{ */ { zval *val; @@ -2846,6 +2873,9 @@ static zend_result php_openssl_setup_crypto(php_stream *stream, } SSL_set_ex_data(sslsock->ssl_handle, php_openssl_get_ssl_stream_data_index(), stream); +#ifdef HAVE_TLS_SNI + php_openssl_share_server_sni(stream, sslsock, parent_sslsock); +#endif if (!SSL_set_fd(sslsock->ssl_handle, sslsock->s.socket)) { php_openssl_handle_ssl_error(stream, 0, true);