Ruby中基于TCPServer与SSLContext实现SNI证书切换的方法
Ruby实现基于SNI的多证书TLS服务器
要实现根据主机名(SNI)返回不同TLS证书,核心是利用OpenSSL的SNI回调机制,在TLS握手阶段动态为当前连接切换对应证书。以下是完整实现方案:
实现步骤与代码示例
1. 准备证书存储
提前加载所有需要的证书和密钥,用主机名作为索引存到哈希中,避免每次连接重复读取文件:
require 'openssl' require 'socket' # 存储主机名到[证书, 密钥]的映射 cert_store = { "example.com" => [ OpenSSL::X509::Certificate.new(File.read("example.com.crt")), OpenSSL::PKey::RSA.new(File.read("example.com.key")) ], "test.com" => [ OpenSSL::X509::Certificate.new(File.read("test.com.crt")), OpenSSL::PKey::RSA.new(File.read("test.com.key")) ] }
2. 配置带SNI回调的SSL上下文
创建基础SSLContext,设置通用TLS参数,关键是添加SNI回调函数,根据客户端发送的主机名切换证书:
context = OpenSSL::SSL::SSLContext.new context.min_version = :TLS1_2 # 设置SNI回调:握手时根据主机名动态配置证书 context.sni_callback = lambda do |ssl, hostname| cert, key = cert_store[hostname] if cert && key # 为当前连接设置匹配的证书和密钥 ssl.context.cert = cert ssl.context.key = key else # 无匹配证书时的处理:用默认证书或终止连接 ssl.context.cert = OpenSSL::X509::Certificate.new(File.read("default.crt")) ssl.context.key = OpenSSL::PKey::RSA.new(File.read("default.key")) # 可选:直接拒绝连接 # ssl.alert_write(:handshake_failure) end end
3. 启动SNI-enabled服务器
创建TCP服务器并包装成SSL服务器,循环处理多连接:
host = '0.0.0.0' port = 443 serv = TCPServer.new(host, port) secure_server = OpenSSL::SSL::SSLServer.new(serv, context) puts "SNI TLS server running on #{host}:#{port}" loop do Thread.start(secure_server.accept) do |conn| begin puts "New connection: #{conn.peeraddr.last}, SNI hostname: #{conn.hostname}" # 这里添加业务逻辑,比如HTTP响应 conn.puts "Welcome to #{conn.hostname}!" rescue => e puts "Connection error: #{e.message}" ensure conn.close end end end
关键说明
- SNI回调时机:TLS握手初期,客户端发送SNI扩展(包含目标主机名)时触发回调,此时还未完成证书交换,因此可以动态切换证书。
- 证书链要求:如果使用中间证书,需要将中间证书合并到主证书文件中(按顺序:主证书在前,中间证书在后),确保客户端能验证完整链。
- 测试方法:用OpenSSL命令测试SNI功能:
openssl s_client -connect localhost:443 -servername example.com openssl s_client -connect localhost:443 -servername test.com
内容的提问来源于stack exchange,提问作者Marek Küthe
相关产品推荐
相关产品推荐

