From a77ed4b9908e179cea8f018f7e245535301746a8 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Tue, 25 Aug 2026 18:25:32 +0900 Subject: [PATCH] ssl: deprecate OpenSSL::SSL::SSLServer Although OpenSSL::SSL::SSLServer presents itself as a TCPServer-like wrapper, its design has flaws. Document OpenSSL::SSL::SSLServer as deprecated and recommend using OpenSSL::SSL::SSLSocket directly. SSLServer#accept calls #accept on the underlying listening socket and then performs the TLS handshake synchronously. This is an obvious problem for programs that expect more than one client to connect. Fixing this would require keeping a backlog of accepted TCP connections in SSLServer while their TLS handshakes complete, which would be too significant a change. This is also why SSLServer#accept_nonblock was never implemented. The blocking behavior of #accept can be worked around by setting SSLServer#start_immediately to false, which skips the handshake. However, at that point SSLServer provides little value over using TCPServer directly and wrapping each accepted socket with SSLSocket. --- ext/openssl/ossl.c | 12 ++++++++---- lib/openssl/ssl.rb | 14 +++++++++++++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/ext/openssl/ossl.c b/ext/openssl/ossl.c index d14265c09..f167ee98f 100644 --- a/ext/openssl/ossl.c +++ b/ext/openssl/ossl.c @@ -944,16 +944,20 @@ ossl_crypto_fixed_length_secure_compare(VALUE dummy, VALUE str1, VALUE str2) * context.cert = cert * context.key = key * - * Then create an OpenSSL::SSL::SSLServer with a TCP server socket and the - * context. Use the SSLServer like an ordinary TCP server. + * After establishing a TCP connection, the socket is wrapped in an + * OpenSSL::SSL::SSLSocket with the context. OpenSSL::SSL::SSLSocket#accept + * is called to perform the TLS handshake. * * require 'socket' * * tcp_server = TCPServer.new 5000 - * ssl_server = OpenSSL::SSL::SSLServer.new tcp_server, context * * loop do - * ssl_connection = ssl_server.accept + * tcp_connection = tcp_server.accept + * ssl_connection = OpenSSL::SSL::SSLSocket.new tcp_connection, context + * # Or you can close tcp_connection manually after ssl_connection.close + * ssl_connection.sync_close = true + * ssl_connection.accept * * data = ssl_connection.gets * diff --git a/lib/openssl/ssl.rb b/lib/openssl/ssl.rb index dccc11a55..486bdf858 100644 --- a/lib/openssl/ssl.rb +++ b/lib/openssl/ssl.rb @@ -475,9 +475,19 @@ def open(remote_host, remote_port, local_host=nil, local_port=nil, context: nil) ## # SSLServer represents a TCP/IP server socket with Secure Sockets Layer. + # + # *Deprecated.* Use TCPServer or Socket to accept a TCP connection, and + # then wrap it with OpenSSL::SSL::SSLSocket. + # See also OpenSSL::SSL::SSLSocket#accept. class SSLServer include SocketForwarder - # When true then #accept works exactly the same as TCPServer#accept + + # When set to +true+, #accept will immediately perform the SSL/TLS + # handshake after accepting a TCP connection. Defaults to +true+. + # + # *NOTE*: #accept performs the SSL/TLS handshake synchronously. A slow + # client can therefore prevent the server from accepting new connections + # indefinitely. For this reason, SSLServer is deprecated. attr_accessor :start_immediately # Creates a new instance of SSLServer. @@ -511,6 +521,8 @@ def shutdown(how=Socket::SHUT_RDWR) end # Works similar to TCPServer#accept. + # + # *NOTE*: SSLServer is deprecated. See #start_immediately for details. def accept # Socket#accept returns [socket, addrinfo]. # TCPServer#accept returns a socket.