Improve default security for SSLContext with a monkeypatch.

New defaults:
* Cipher suite based on Mozilla's Intermediate set from
  https://wiki.mozilla.org/Security/Server_Side_TLS (at time of writing)
* Disable SSLv2 explicitly
* Disable SSLv3 explicitly
* Disable compression if possible

The SSL option setting came from the ruby-ftw library's FTW::Connection
(apache 2 licensed, I am author), and transitively through work
published by jmhodges to improve Ruby's SSL strength.

I include specs to ensure we never include export or weak ciphers by
default.

Using this patch to test the security improvements according to
`www.howsmyssl.com` shows much improved results:

---

Testing this:

```
ruby -r ./lib/logstash/patches/stronger_openssl_defaults.rb  -ropenssl -rsocket -rjson -rawesome_print -e 'c = OpenSSL::SSL::SSLContext.new; t = TCPSocket.new("www.howsmyssl.com", 443); o = OpenSSL::SSL::SSLSocket.new(t, c); o.connect; o.puts "GET /a/check HTTP/1.1\r\nHost: www.howsmyssl.com\r\n\r\n"; headers,body = o.read.split("\r\n\r\n", 2); puts body'
```

(I processed the JSON output w/ jq for easier reading)

The purpose of the above is to test the default behavior of SSLContext.

* JRuby 1.7.19 w/ this patch reports no cipher problems.
* JRuby 1.7.19 without this patch has several weak ciphers used:

```
  "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA: [\"uses keys smaller than 128 bits in its encryption\"]",
  "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA: [\"uses keys smaller than 128 bits in its encryption\"]",
  "TLS_DHE_RSA_WITH_DES_CBC_SHA: [\"uses keys smaller than 128 bits in its encryption\"]",
  "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA: [\"uses keys smaller than 128 bits in its encryption\"]",
  "TLS_RSA_EXPORT_WITH_RC4_40_MD5: [\"uses keys smaller than 128 bits in its encryption\",\"use RC4 which has insecure biases in its output\"]",
  "TLS_RSA_WITH_DES_CBC_SHA: [\"uses keys smaller than 128 bits in its encryption\"]",
  "TLS_RSA_WITH_RC4_128_MD5: [\"use RC4 which has insecure biases in its output\"]",
  "TLS_RSA_WITH_RC4_128_SHA: [\"use RC4 which has insecure biases in its output\"]"
```

Under MRI, similar cipher selection problems are observed without this patch (weak export
ciphers, other weak small-key ciphers, RC4 complaints). With this patch, no cipher complaints
are reported by www.howsmyssl.com

One other note: Because JRuby defaults to TLS 1.0 and only makes CBC ciphers
available under the Mozilla Intermediate cipher set, I believe (and
howsmyssl.com agrees) that these defaults still make the BEAST exploit a
problem. Switching to TLS 1.1 should fix this, but we need to do more research
to determine the what, if any, impact it will have if we force TLS 1.1 to be
the default..

Fixes #3579
This commit is contained in:
Jordan Sissel 2015-07-02 09:46:25 -07:00
parent f7b76fa2ae
commit fbf8e1e320
3 changed files with 88 additions and 0 deletions

View file

@ -1,3 +1,4 @@
require "logstash/patches/bugfix_jruby_2558" require "logstash/patches/bugfix_jruby_2558"
require "logstash/patches/cabin" require "logstash/patches/cabin"
require "logstash/patches/profile_require_calls" require "logstash/patches/profile_require_calls"
require "logstash/patches/stronger_openssl_defaults"

View file

@ -0,0 +1,62 @@
require "openssl"
# :nodoc:
class OpenSSL::SSL::SSLContext
# Wrap SSLContext.new to a stronger default settings.
class << self
alias_method :orig_new, :new
def new(*args)
c = orig_new(*args)
# MRI nor JRuby seem to actually invoke `SSLContext#set_params` by
# default, which makes the default ciphers (and other settings) not
# actually defaults. Oops!
# To force this, and force our (hopefully more secure) defaults on
# all things using openssl in Ruby, we will invoke set_params
# on all new SSLContext objects.
c.set_params
c
end
end
# This cipher selection comes from https://wiki.mozilla.org/Security/Server_Side_TLS
MOZILLA_INTERMEDIATE_CIPHERS = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA"
# Returns the value that should be used for the default SSLContext options
#
# This is a method instead of a constant because some constants (like
# OpenSSL::SSL::OP_NO_COMPRESSION) may not be available in all Ruby
# versions/platforms.
def self.__default_options
# ruby-core is refusing to patch ruby's default openssl settings to be more
# secure, so let's fix that here. The next few lines setting options and
# ciphers come from jmhodges' proposed patch
ssloptions = OpenSSL::SSL::OP_ALL
# TODO(sissel): JRuby doesn't have this. Maybe work on a fix?
if defined?(OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS)
ssloptions &= ~OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS
end
# TODO(sissel): JRuby doesn't have this. Maybe work on a fix?
if defined?(OpenSSL::SSL::OP_NO_COMPRESSION)
ssloptions |= OpenSSL::SSL::OP_NO_COMPRESSION
end
# Disable SSLv2 and SSLv3. They are insecure and highly discouraged.
ssloptions |= OpenSSL::SSL::OP_NO_SSLv2 if defined?(OpenSSL::SSL::OP_NO_SSLv2)
ssloptions |= OpenSSL::SSL::OP_NO_SSLv3 if defined?(OpenSSL::SSL::OP_NO_SSLv3)
ssloptions
end
# Overwriting the DEFAULT_PARAMS const idea from here: https://www.ruby-lang.org/en/news/2014/10/27/changing-default-settings-of-ext-openssl/
remove_const(:DEFAULT_PARAMS) if const_defined?(:DEFAULT_PARAMS)
DEFAULT_PARAMS = {
:ssl_version => "SSLv23",
:verify_mode => OpenSSL::SSL::VERIFY_PEER,
:ciphers => MOZILLA_INTERMEDIATE_CIPHERS,
:options => __default_options # Not a constant because it's computed at start-time.
}
end

View file

@ -0,0 +1,25 @@
require "logstash/patches"
describe "OpenSSL defaults" do
subject { OpenSSL::SSL::SSLContext.new }
# OpenSSL::SSL::SSLContext#ciphers returns an array of
# [ [ ciphername, version, bits, alg_bits ], [ ... ], ... ]
# List of cipher names
let(:ciphers) { subject.ciphers.map(&:first) }
# List of cipher encryption bit strength.
let(:encryption_bits) { subject.ciphers.map { |_, _, _, a| a } }
it "should not include any export ciphers" do
# SSLContext#ciphers returns an array of [ciphername, tlsversion, key_bits, alg_bits]
# Let's just check the cipher names
expect(ciphers).not_to be_any { |name| name =~ /EXPORT/ || name =~ /^EXP/ }
end
it "should not include any weak ciphers (w/ less than 128 bits in encryption algorithm)" do
# SSLContext#ciphers returns an array of [ciphername, tlsversion, key_bits, alg_bits]
expect(encryption_bits).not_to be_any { |bits| bits < 128 }
end
end