From 9778ae6f94361f7c5bced2102deac1d875e9bb0d Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Thu, 12 Nov 2026 22:17:45 +0900 Subject: [PATCH] ssl: update docs for SSLContext#servername_cb Recommend using it with the newly added SSLSocket#add_certificate. --- lib/openssl/ssl.rb | 36 +++++++++++++++++++++++++++++++----- test/openssl/test_ssl.rb | 22 ++++++++++++++++++++++ 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/lib/openssl/ssl.rb b/lib/openssl/ssl.rb index e50be00ff..dc4ad44b7 100644 --- a/lib/openssl/ssl.rb +++ b/lib/openssl/ssl.rb @@ -74,11 +74,37 @@ class SSLContext DEFAULT_CERT_STORE = OpenSSL::X509::Store.new # :nodoc: DEFAULT_CERT_STORE.set_default_paths - # A callback invoked at connect time to distinguish between multiple - # server names. - # - # The callback is invoked with an SSLSocket and a server name. The - # callback must return an SSLContext for the server name or nil. + # A callback invoked during the server-side handshake when the client + # sends the Server Name Indication (SNI) extension. + # + # The callback is invoked with an SSLSocket and the server name requested + # by the client as a String. + # + # A typical use case is to select the appropriate server certificate. + # The callback can use SSLSocket#add_certificate to add certificates and + # their corresponding private keys. It should return +nil+ to continue + # the handshake and notify the client that the server name is accepted. + # + # Alternatively, the callback can return an SSLContext to switch the + # context used for the connection. However, doing so is discouraged + # because the underlying \OpenSSL API SSL_set_SSL_CTX() has complex and + # inconsistent semantics. + # + # See also the man page SSL_CTX_set_tlsext_servername_callback(3). + # + # === Example + # ctx = OpenSSL::SSL::SSLContext.new + # ctx.servername_cb = proc do |ssl_socket, server_name| + # if server_name == "example.com" + # cert = OpenSSL::X509::Certificate.new(File.read("cert.pem")) + # privkey = OpenSSL::PKey.read(File.read("cert_privkey.pem")) + # ssl_socket.add_certificate(cert, privkey) + # else + # # Aborts the handshake with an "unrecognized_name" alert. + # raise "unknown SNI hostname: #{server_name}" + # end + # nil + # end attr_accessor :servername_cb # call-seq: diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb index 1676bb69e..3a548e36f 100644 --- a/test/openssl/test_ssl.rb +++ b/test/openssl/test_ssl.rb @@ -1185,6 +1185,28 @@ def test_tlsext_hostname end end + def test_servername_cb_add_certificate + called = [] + sctx = make_server_context + sctx.servername_cb = proc do |ssl, servername| + called << servername + ssl.add_certificate(@cli_cert, @cli_key) + nil + end + start_server(sctx) do |port| + sock = TCPSocket.new("127.0.0.1", port) + ssl = OpenSSL::SSL::SSLSocket.new(sock) + ssl.hostname = "example.org" + ssl.connect + assert_equal(@cli_cert, ssl.peer_cert) + assert_equal(["example.org"], called) + ssl.puts("abc"); ssl.gets + ensure + ssl&.close + sock&.close + end + end + def test_servername_cb_exception server_proc = proc do |sock| sctx = make_server_context