更新:首先,我的测试代码没有充分显示 ruby 2.4 看到 :SSLCiphers 选项,而 ruby 2.2 没有。我编辑了下面的示例代码以清楚地表明这一点。
更新:由于我的问题未能得到社区的任何帮助,我继续前进,两天后找到了解决方案,我将其包含在下面。
我有一个基于 Ruby 2.2 和 Webrick 的小型 Rails 3 应用程序,它可以处理小型负载,因此不需要“真正的”Web 服务器的复杂性。它已被修补以支持 https 连接以进行安全登录,但默认情况下,它接受许多旧的弱密码,我想禁止这些密码。虽然将 ruby 升级到 2.4 提供了新的强化选项来实现此目的,但我无法立即切换到较新的 ruby。因此,我尝试对 ruby 2.2 的 webrick/ssl.rb 进行猴子修补,以添加其中几个选项::SSLVersion 和:SSLCiphers。我只取得了部分成功:虽然 :SSLVersion 似乎可以工作,但 :SSLCiphers 却不能。
首先,以下示例基于此处的答案显示了 Ruby 2.4 上的成功强化https://stackoverflow.com/a/23283909/6588873 https://stackoverflow.com/a/23283909/6588873但也结合了这里找到的一些建议:https://gist.github.com/tam7t/86eb4793e8ecf3f55037 https://gist.github.com/tam7t/86eb4793e8ecf3f55037除了最初我们会过于激进并排除 AES128 只是为了显示 :SSLCiphers 选项的工作原理(因为 Ruby 2.4 中的 openssl 版本默认情况下禁用所有 SHA1 密码)。
#!/usr/bin/env ruby.exe
# script/rails:
require 'rails/commands/server'
require 'rack'
require 'webrick'
require 'webrick/https'
module Rails
class Server < ::Rack::Server
SSL_ENABLED=true
def default_options
# Don't use SSLv3
no_ssl_3 = OpenSSL::SSL::OP_NO_SSLv3
# Don't use SSLv2
no_ssl_2 = OpenSSL::SSL::OP_NO_SSLv2
# Don't use compression (CRIME CVE-2012-4929)
no_ssl_compression = OpenSSL::SSL::OP_NO_COMPRESSION
ssl_options = no_ssl_2 + no_ssl_3 + no_ssl_compression
super.merge({
:Port => 3002,
:environment => (ENV['RAILS_ENV'] || "development").dup,
:daemonize => false,
:debugger => false,
:config => File.expand_path("config.ru"),
:SSLEnable => true,
:SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE,
:SSLPrivateKey => OpenSSL::PKey::RSA.new(
File.open(File.expand_path("../cert/test.cert.key",__FILE__)).read),
:SSLCertificate => OpenSSL::X509::Certificate.new(
File.open(File.expand_path("../cert/test.cert.crt",__FILE__)).read),
:SSLCertName => [["CN", WEBrick::Utils::getservername]],
:SSLOptions => ssl_options,
:SSLCiphers => 'TLSv1.2:!aNULL:!eNULL:!AES128',
:SSLVersion => :TLSv1_2,
})
end
end
end
APP_PATH = File.expand_path('../../config/application', __FILE__)
require File.expand_path('../../config/boot', __FILE__)
require 'rails/commands'
这是应用了上述补丁的 sslscan 输出的摘录,显示了受支持的密码的简短列表,其中包含不安全的密码和已删除的过时 TLS 版本(尽管默认情况下它们无论如何都会被删除,因此 !AES128 只是为了显示该选项的原理证明)作品)。
Supported Server Cipher(s):
Preferred TLSv1.2 256 bits ECDHE-RSA-AES256-GCM-SHA384 Curve P-256 DHE 256
Accepted TLSv1.2 256 bits ECDHE-RSA-AES256-SHA384 Curve P-256 DHE 256
Accepted TLSv1.2 256 bits DHE-RSA-AES256-GCM-SHA384 DHE 1024 bits
Accepted TLSv1.2 256 bits DHE-RSA-AES256-SHA256 DHE 1024 bits
Accepted TLSv1.2 256 bits AES256-GCM-SHA384
Accepted TLSv1.2 256 bits AES256-SHA256
接下来,我将 :SSLCiphers 更改为我想要的实际字符串,因为我实际上对 AES128 很满意。我只想排除旧版 openssl 默认情况下不排除的所有旧 SHA1 密码:
:SSLCiphers => 'TLSv1.2:!aNULL:!eNULL!SHA',
然后准备我的反向移植,插入到上面所示的 Rails::Server 补丁之后。以下内容直接从 Ruby 2.2 的 webrick/ssl.rb 中提取,但添加了 Ruby 2.4 中的两个新选项,并通过 WEBrick::GenericServer#setup_ssl_context() 传递到 ruby-openssl:
if RUBY_VERSION < '2.4'
module WEBrick
module Config
svrsoft = General[:ServerSoftware]
osslv = ::OpenSSL::OPENSSL_VERSION.split[1]
SSL = {
:ServerSoftware => "#{svrsoft} OpenSSL/#{osslv}",
:SSLEnable => false,
:SSLCertificate => nil,
:SSLPrivateKey => nil,
:SSLClientCA => nil,
:SSLExtraChainCert => nil,
:SSLCACertificateFile => nil,
:SSLCACertificatePath => nil,
:SSLCertificateStore => nil,
:SSLTmpDhCallback => nil,
:SSLVerifyClient => ::OpenSSL::SSL::VERIFY_NONE,
:SSLVerifyDepth => nil,
:SSLVerifyCallback => nil, # custom verification
:SSLTimeout => nil,
:SSLOptions => nil,
:SSLCiphers => nil,
:SSLVersion => nil,
:SSLStartImmediately => true,
# Must specify if you use auto generated certificate.
:SSLCertName => nil,
:SSLCertComment => "Generated by Ruby/OpenSSL"
}
General.update(SSL)
end
class GenericServer
def setup_ssl_context(config) # :nodoc:
unless config[:SSLCertificate]
cn = config[:SSLCertName]
comment = config[:SSLCertComment]
cert, key = Utils::create_self_signed_cert(1024, cn, comment)
config[:SSLCertificate] = cert
config[:SSLPrivateKey] = key
end
ctx = OpenSSL::SSL::SSLContext.new
ctx.key = config[:SSLPrivateKey]
ctx.cert = config[:SSLCertificate]
ctx.client_ca = config[:SSLClientCA]
ctx.extra_chain_cert = config[:SSLExtraChainCert]
ctx.ca_file = config[:SSLCACertificateFile]
ctx.ca_path = config[:SSLCACertificatePath]
ctx.cert_store = config[:SSLCertificateStore]
ctx.tmp_dh_callback = config[:SSLTmpDhCallback]
ctx.verify_mode = config[:SSLVerifyClient]
ctx.verify_depth = config[:SSLVerifyDepth]
ctx.verify_callback = config[:SSLVerifyCallback]
ctx.timeout = config[:SSLTimeout]
ctx.options = config[:SSLOptions]
ctx.ciphers = config[:SSLCiphers]
ctx.ssl_version = config[:SSLVersion]
ctx
end
end
end
end
启动时,我看到以下重新定义警告。第一个,我预料到了。第二个我不确定,但我认为还可以:
script/rails:47: warning: already initialized constant WEBrick::Config::SSL
C:/Ruby226/lib/ruby/2.2.0/webrick/ssl.rb:62: warning: previous definition of SSL was here
现在,虽然 sslscan 现在指示遵守 SSLVersion(即不接受 TLS 1.0 或 1.1),但 SSLCiphers 选项似乎根本没有执行任何操作,即仍然接受 SHA 密码:
Supported Server Cipher(s):
Preferred TLSv1.2 256 bits DHE-RSA-AES256-GCM-SHA384 DHE 1024 bits
Accepted TLSv1.2 256 bits DHE-RSA-AES256-SHA256 DHE 1024 bits
Accepted TLSv1.2 256 bits DHE-RSA-AES256-SHA DHE 1024 bits
Accepted TLSv1.2 256 bits DHE-RSA-CAMELLIA256-SHA DHE 1024 bits
Accepted TLSv1.2 256 bits AES256-GCM-SHA384
Accepted TLSv1.2 256 bits AES256-SHA256
Accepted TLSv1.2 256 bits AES256-SHA
Accepted TLSv1.2 256 bits CAMELLIA256-SHA
Accepted TLSv1.2 128 bits DHE-RSA-AES128-GCM-SHA256 DHE 1024 bits
Accepted TLSv1.2 128 bits DHE-RSA-AES128-SHA256 DHE 1024 bits
Accepted TLSv1.2 128 bits DHE-RSA-AES128-SHA DHE 1024 bits
Accepted TLSv1.2 128 bits DHE-RSA-SEED-SHA DHE 1024 bits
Accepted TLSv1.2 128 bits DHE-RSA-CAMELLIA128-SHA DHE 1024 bits
Accepted TLSv1.2 128 bits AES128-GCM-SHA256
Accepted TLSv1.2 128 bits AES128-SHA256
Accepted TLSv1.2 128 bits AES128-SHA
Accepted TLSv1.2 128 bits SEED-SHA
Accepted TLSv1.2 128 bits CAMELLIA128-SHA
而我期望的是:
Supported Server Cipher(s):
Preferred TLSv1.2 256 bits DHE-RSA-AES256-GCM-SHA384 DHE 1024 bits
Accepted TLSv1.2 256 bits DHE-RSA-AES256-SHA256 DHE 1024 bits
Accepted TLSv1.2 256 bits AES256-GCM-SHA384
Accepted TLSv1.2 256 bits AES256-SHA256
Accepted TLSv1.2 128 bits DHE-RSA-AES128-GCM-SHA256 DHE 1024 bits
Accepted TLSv1.2 128 bits DHE-RSA-AES128-SHA256 DHE 1024 bits
Accepted TLSv1.2 128 bits AES128-GCM-SHA256
Accepted TLSv1.2 128 bits AES128-SHA256
事实上,我将 :SSLCiphers 更改为任何有效甚至无效值并不重要。显然,仅仅更新 ruby 是不够的。
我这个向后移植哪里出了问题?我还可以尝试其他什么方法来达到相同的结果吗?我似乎非常接近这里的解决方案,在我们的 Ruby 版本和 webrick 的限制下工作,这对我来说很难摆脱。
解决方案:
我插入了调试打印语句来查看 SSLCiphers 值在哪里“丢失”。尽管我的测试显示 WEBrick 方面一切正常,但在 OpenSSL 类中,当设置 SSL 侦听器时,似乎传入了错误的密码列表(即默认值而不是覆盖值)。
当查看 OpenSSL API 寻找有关如何获取有关失败的更多信息的线索时,我发现OpenSSL::SSL::SSLContext#set_params http://ruby-doc.org/stdlib-2.2.6/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html#method-i-set_params,一种将哈希值作为参数的方法,其中包含 SSLContext 的任何参数以设置新值。因此,我实验性地替换了我的猴子补丁 WEBrick::GenericServer#setup_ssl_context 以使用它并传递要更改的所有值的哈希值,而不是替换每个设置的 ctx.= config:[:] (直接从原始 WEBrick 代码中提取;甚至在 Ruby 2.4 中这一点没有改变)。
我的结果?成功!使用更改 SSLContext 对象的替代方法,我能够使 :SSLCiphers 覆盖值“stick”
if RUBY_VERSION < '2.4'
require 'webrick/ssl'
module WEBrick
module Config
svrsoft = General[:ServerSoftware]
osslv = ::OpenSSL::OPENSSL_VERSION.split[1]
SSL = {
:ServerSoftware => "#{svrsoft} OpenSSL/#{osslv}",
:SSLEnable => false,
:SSLCertificate => nil,
:SSLPrivateKey => nil,
:SSLClientCA => nil,
:SSLExtraChainCert => nil,
:SSLCACertificateFile => nil,
:SSLCACertificatePath => nil,
:SSLCertificateStore => nil,
:SSLTmpDhCallback => nil,
:SSLVerifyClient => ::OpenSSL::SSL::VERIFY_NONE,
:SSLVerifyDepth => nil,
:SSLVerifyCallback => nil, # custom verification
:SSLTimeout => nil,
:SSLOptions => nil,
:SSLCiphers => nil,
:SSLVersion => nil,
:SSLStartImmediately => true,
# Must specify if you use auto generated certificate.
:SSLCertName => nil,
:SSLCertComment => "Generated by Ruby/OpenSSL"
}
General.update(SSL)
end
class GenericServer
def setup_ssl_context(config) # :nodoc:
unless config[:SSLCertificate]
cn = config[:SSLCertName]
comment = config[:SSLCertComment]
cert, key = Utils::create_self_signed_cert(1024, cn, comment)
config[:SSLCertificate] = cert
config[:SSLPrivateKey] = key
end
ctx = OpenSSL::SSL::SSLContext.new
ctx.set_params({
key: config[:SSLPrivateKey],
cert: config[:SSLCertificate],
client_ca: config[:SSLClientCA],
extra_chain_cert: config[:SSLExtraChainCert],
ca_file: config[:SSLCACertificateFile],
ca_path: config[:SSLCACertificatePath],
cert_store: config[:SSLCertificateStore],
tmp_dh_callback: config[:SSLTmpDhCallback],
verify_mode: config[:SSLVerifyClient],
verify_depth: config[:SSLVerifyDepth],
verify_callback: config[:SSLVerifyCallback],
timeout: config[:SSLTimeout],
options: config[:SSLOptions],
ciphers: config[:SSLCiphers],
ssl_version: config[:SSLVersion],
})
ctx
end
end
end
end
安装此补丁后,sslscan 输出与上面显示的预期输出相匹配。