mirror of
https://github.com/puma/puma.git
synced 2022-11-09 13:48:40 -05:00
commit
3ea247174e
9 changed files with 159 additions and 35 deletions
16
.travis.yml
16
.travis.yml
|
@ -1,4 +1,5 @@
|
|||
dist: xenial
|
||||
env: OS="Xenial 16.04 OpenSSL 1.0.2"
|
||||
language: ruby
|
||||
cache: bundler
|
||||
|
||||
|
@ -19,6 +20,7 @@ before_install:
|
|||
- ruby -v && gem --version && bundle version
|
||||
|
||||
before_script:
|
||||
- if [ "$jit" == "yes" ]; then export RUBYOPT=--jit ; fi ; echo RUBYOPT is $RUBYOPT
|
||||
- bundle exec rake compile
|
||||
|
||||
script:
|
||||
|
@ -37,18 +39,20 @@ matrix:
|
|||
include:
|
||||
- rvm: 2.2
|
||||
dist: trusty
|
||||
env: NOTES="Trusty OpenSSL 1.0.1"
|
||||
env: OS="Trusty 14.04 OpenSSL 1.0.1"
|
||||
- rvm: 2.6
|
||||
dist: bionic
|
||||
env: OS="Bionic 18.04 OpenSSL 1.1.1"
|
||||
- rvm: ruby-head
|
||||
env: RUBYOPT="--jit"
|
||||
env: jit=yes
|
||||
- rvm: 2.4.6
|
||||
os: osx
|
||||
osx_image: xcode10.2
|
||||
env: OS="osx xcode10.2"
|
||||
- rvm: 2.5.5
|
||||
os: osx
|
||||
osx_image: xcode10.2
|
||||
- rvm: 2.6.3
|
||||
os: osx
|
||||
osx_image: xcode10.2
|
||||
env: OS="osx xcode10.2"
|
||||
- rvm: jruby-9.2.7.0
|
||||
env: JRUBY_OPTS="--debug" JAVA_OPTS="--add-opens java.base/sun.nio.ch=org.jruby.dist --add-opens java.base/java.io=org.jruby.dist --add-opens java.base/java.util.zip=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.security.cert=ALL-UNNAMED --add-opens java.base/java.security=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED"
|
||||
- rvm: jruby-head
|
||||
|
@ -56,7 +60,7 @@ matrix:
|
|||
allow_failures:
|
||||
- rvm: ruby-head
|
||||
- rvm: ruby-head
|
||||
env: RUBYOPT="--jit"
|
||||
env: jit=yes
|
||||
- rvm: jruby-9.2.7.0
|
||||
- rvm: jruby-head
|
||||
|
||||
|
|
|
@ -142,7 +142,7 @@ VALUE engine_init_server(VALUE self, VALUE mini_ssl_ctx) {
|
|||
VALUE obj;
|
||||
SSL_CTX* ctx;
|
||||
SSL* ssl;
|
||||
int ssl_options;
|
||||
int min, ssl_options;
|
||||
|
||||
ms_conn* conn = engine_alloc(self, &obj);
|
||||
|
||||
|
@ -168,6 +168,9 @@ VALUE engine_init_server(VALUE self, VALUE mini_ssl_ctx) {
|
|||
ID sym_no_tlsv1 = rb_intern("no_tlsv1");
|
||||
VALUE no_tlsv1 = rb_funcall(mini_ssl_ctx, sym_no_tlsv1, 0);
|
||||
|
||||
ID sym_no_tlsv1_1 = rb_intern("no_tlsv1_1");
|
||||
VALUE no_tlsv1_1 = rb_funcall(mini_ssl_ctx, sym_no_tlsv1_1, 0);
|
||||
|
||||
#ifdef HAVE_TLS_SERVER_METHOD
|
||||
ctx = SSL_CTX_new(TLS_server_method());
|
||||
#else
|
||||
|
@ -183,12 +186,36 @@ VALUE engine_init_server(VALUE self, VALUE mini_ssl_ctx) {
|
|||
SSL_CTX_load_verify_locations(ctx, RSTRING_PTR(ca), NULL);
|
||||
}
|
||||
|
||||
ssl_options = SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_SINGLE_DH_USE | SSL_OP_SINGLE_ECDH_USE | SSL_OP_NO_COMPRESSION;
|
||||
ssl_options = SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_SINGLE_ECDH_USE | SSL_OP_NO_COMPRESSION;
|
||||
|
||||
if(RTEST(no_tlsv1)) {
|
||||
#ifdef HAVE_SSL_CTX_SET_MIN_PROTO_VERSION
|
||||
if (RTEST(no_tlsv1_1)) {
|
||||
min = TLS1_2_VERSION;
|
||||
}
|
||||
else if (RTEST(no_tlsv1)) {
|
||||
min = TLS1_1_VERSION;
|
||||
}
|
||||
else {
|
||||
min = TLS1_VERSION;
|
||||
}
|
||||
|
||||
SSL_CTX_set_min_proto_version(ctx, min);
|
||||
|
||||
SSL_CTX_set_options(ctx, ssl_options);
|
||||
|
||||
#else
|
||||
/* As of 1.0.2f, SSL_OP_SINGLE_DH_USE key use is always on */
|
||||
ssl_options |= SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_SINGLE_DH_USE;
|
||||
|
||||
if (RTEST(no_tlsv1)) {
|
||||
ssl_options |= SSL_OP_NO_TLSv1;
|
||||
}
|
||||
if(RTEST(no_tlsv1_1)) {
|
||||
ssl_options |= SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1;
|
||||
}
|
||||
SSL_CTX_set_options(ctx, ssl_options);
|
||||
#endif
|
||||
|
||||
SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
|
||||
|
||||
if (!NIL_P(ssl_cipher_filter)) {
|
||||
|
@ -458,14 +485,35 @@ void Init_mini_ssl(VALUE puma) {
|
|||
// OpenSSL Build / Runtime/Load versions
|
||||
|
||||
/* Version of OpenSSL that Puma was compiled with */
|
||||
rb_define_const(mod, "OPENSSL_VERSION", rb_str_new2(OPENSSL_VERSION_TEXT));
|
||||
rb_define_const(mod, "OPENSSL_VERSION", rb_str_new2(OPENSSL_VERSION_TEXT));
|
||||
|
||||
#if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10100000
|
||||
/* Version of OpenSSL that Puma loaded with */
|
||||
rb_define_const(mod, "OPENSSL_LIBRARY_VERSION", rb_str_new2(OpenSSL_version(OPENSSL_VERSION)));
|
||||
/* Version of OpenSSL that Puma loaded with */
|
||||
rb_define_const(mod, "OPENSSL_LIBRARY_VERSION", rb_str_new2(OpenSSL_version(OPENSSL_VERSION)));
|
||||
#else
|
||||
rb_define_const(mod, "OPENSSL_LIBRARY_VERSION", rb_str_new2(SSLeay_version(SSLEAY_VERSION)));
|
||||
rb_define_const(mod, "OPENSSL_LIBRARY_VERSION", rb_str_new2(SSLeay_version(SSLEAY_VERSION)));
|
||||
#endif
|
||||
|
||||
#if defined(OPENSSL_NO_SSL3) || defined(OPENSSL_NO_SSL3_METHOD)
|
||||
/* True if SSL3 is not available */
|
||||
rb_define_const(mod, "OPENSSL_NO_SSL3", Qtrue);
|
||||
#else
|
||||
rb_define_const(mod, "OPENSSL_NO_SSL3", Qfalse);
|
||||
#endif
|
||||
|
||||
#if defined(OPENSSL_NO_TLS1) || defined(OPENSSL_NO_TLS1_METHOD)
|
||||
/* True if TLS1 is not available */
|
||||
rb_define_const(mod, "OPENSSL_NO_TLS1", Qtrue);
|
||||
#else
|
||||
rb_define_const(mod, "OPENSSL_NO_TLS1", Qfalse);
|
||||
#endif
|
||||
|
||||
#if defined(OPENSSL_NO_TLS1_1) || defined(OPENSSL_NO_TLS1_1_METHOD)
|
||||
/* True if TLS1_1 is not available */
|
||||
rb_define_const(mod, "OPENSSL_NO_TLS1_1", Qtrue);
|
||||
#else
|
||||
rb_define_const(mod, "OPENSSL_NO_TLS1_1", Qfalse);
|
||||
#endif
|
||||
|
||||
rb_define_singleton_method(mod, "check", noop, 0);
|
||||
|
||||
|
|
|
@ -166,6 +166,10 @@ public class MiniSSL extends RubyObject {
|
|||
protocols = new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" };
|
||||
}
|
||||
|
||||
if(miniSSLContext.callMethod(threadContext, "no_tlsv1_1").isTrue()) {
|
||||
protocols = new String[] { "TLSv1.2" };
|
||||
}
|
||||
|
||||
engine.setEnabledProtocols(protocols);
|
||||
engine.setUseClientMode(false);
|
||||
|
||||
|
|
|
@ -195,6 +195,7 @@ module Puma
|
|||
end
|
||||
|
||||
ctx.no_tlsv1 = true if params['no_tlsv1'] == 'true'
|
||||
ctx.no_tlsv1_1 = true if params['no_tlsv1_1'] == 'true'
|
||||
|
||||
if params['verify_mode']
|
||||
ctx.verify_mode = case params['verify_mode']
|
||||
|
|
|
@ -398,14 +398,15 @@ module Puma
|
|||
def ssl_bind(host, port, opts)
|
||||
verify = opts.fetch(:verify_mode, 'none')
|
||||
no_tlsv1 = opts.fetch(:no_tlsv1, 'false')
|
||||
no_tlsv1_1 = opts.fetch(:no_tlsv1_1, 'false')
|
||||
ca_additions = "&ca=#{opts[:ca]}" if ['peer', 'force_peer'].include?(verify)
|
||||
|
||||
if defined?(JRUBY_VERSION)
|
||||
keystore_additions = "keystore=#{opts[:keystore]}&keystore-pass=#{opts[:keystore_pass]}"
|
||||
bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}&#{keystore_additions}&verify_mode=#{verify}&no_tlsv1=#{no_tlsv1}#{ca_additions}"
|
||||
bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}&#{keystore_additions}&verify_mode=#{verify}&no_tlsv1=#{no_tlsv1}&no_tlsv1_1=#{no_tlsv1_1}#{ca_additions}"
|
||||
else
|
||||
ssl_cipher_filter = "&ssl_cipher_filter=#{opts[:ssl_cipher_filter]}" if opts[:ssl_cipher_filter]
|
||||
bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}#{ssl_cipher_filter}&verify_mode=#{verify}&no_tlsv1=#{no_tlsv1}#{ca_additions}"
|
||||
bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}#{ssl_cipher_filter}&verify_mode=#{verify}&no_tlsv1=#{no_tlsv1}&no_tlsv1_1=#{no_tlsv1_1}#{ca_additions}"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -176,10 +176,11 @@ module Puma
|
|||
|
||||
class Context
|
||||
attr_accessor :verify_mode
|
||||
attr_reader :no_tlsv1
|
||||
attr_reader :no_tlsv1, :no_tlsv1_1
|
||||
|
||||
def initialize
|
||||
@no_tlsv1 = false
|
||||
@no_tlsv1 = false
|
||||
@no_tlsv1_1 = false
|
||||
end
|
||||
|
||||
if defined?(JRUBY_VERSION)
|
||||
|
@ -219,18 +220,24 @@ module Puma
|
|||
@ca = ca
|
||||
end
|
||||
|
||||
|
||||
def check
|
||||
raise "Key not configured" unless @key
|
||||
raise "Cert not configured" unless @cert
|
||||
end
|
||||
end
|
||||
|
||||
# disables TLSv1
|
||||
def no_tlsv1=(tlsv1)
|
||||
raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1)
|
||||
@no_tlsv1 = tlsv1
|
||||
end
|
||||
|
||||
# disables TLSv1 and TLSv1.1. Overrides `#no_tlsv1=`
|
||||
def no_tlsv1_1=(tlsv1_1)
|
||||
raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1_1)
|
||||
@no_tlsv1_1 = tlsv1_1
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
VERIFY_NONE = 0
|
||||
|
|
|
@ -77,9 +77,22 @@ class TestBinderMRI < TestBinderBase
|
|||
refute ssl_context_for_binder(@binder).no_tlsv1
|
||||
end
|
||||
|
||||
def test_binder_parses_tlsv1_unspecified_defaults_to_enabled
|
||||
def test_binder_parses_tlsv1_tlsv1_1_unspecified_defaults_to_enabled
|
||||
@binder.parse(["ssl://0.0.0.0?key=#{@key}&cert=#{@cert}"], @events)
|
||||
|
||||
refute ssl_context_for_binder(@binder).no_tlsv1
|
||||
refute ssl_context_for_binder(@binder).no_tlsv1_1
|
||||
end
|
||||
|
||||
def test_binder_parses_tlsv1_1_disabled
|
||||
@binder.parse(["ssl://0.0.0.0?key=#{@key}&cert=#{@cert}&no_tlsv1_1=true"], @events)
|
||||
|
||||
assert ssl_context_for_binder(@binder).no_tlsv1_1
|
||||
end
|
||||
|
||||
def test_binder_parses_tlsv1_1_enabled
|
||||
@binder.parse(["ssl://0.0.0.0?key=#{@key}&cert=#{@cert}&no_tlsv1_1=false"], @events)
|
||||
|
||||
refute ssl_context_for_binder(@binder).no_tlsv1_1
|
||||
end
|
||||
end
|
||||
|
|
|
@ -75,7 +75,7 @@ class TestConfigFile < TestConfigFileBase
|
|||
|
||||
conf.load
|
||||
|
||||
ssl_binding = "ssl://0.0.0.0:9292?cert=/path/to/cert&key=/path/to/key&verify_mode=the_verify_mode&no_tlsv1=false"
|
||||
ssl_binding = "ssl://0.0.0.0:9292?cert=/path/to/cert&key=/path/to/key&verify_mode=the_verify_mode&no_tlsv1=false&no_tlsv1_1=false"
|
||||
assert_equal [ssl_binding], conf.options[:binds]
|
||||
end
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
require_relative "helper"
|
||||
require "puma/minissl"
|
||||
require "puma/puma_http11"
|
||||
# net/http (loaded in helper) does not necessarily load OpenSSL
|
||||
require "openssl" unless Object.const_defined? :OpenSSL
|
||||
|
||||
#———————————————————————————————————————————————————————————————————————————————
|
||||
# NOTE: ALL TESTS BYPASSED IF DISABLE_SSL IS TRUE
|
||||
|
@ -27,13 +25,25 @@ DISABLE_SSL = begin
|
|||
rescue
|
||||
true
|
||||
else
|
||||
# net/http (loaded in helper) does not necessarily load OpenSSL
|
||||
require "openssl" unless Object.const_defined? :OpenSSL
|
||||
false
|
||||
end
|
||||
|
||||
class TestPumaServerSSL < Minitest::Test
|
||||
|
||||
def setup
|
||||
return if DISABLE_SSL
|
||||
@http = nil
|
||||
@server = nil
|
||||
end
|
||||
|
||||
def teardown
|
||||
@http.finish if @http && @http.started?
|
||||
@server.stop(true) if @server
|
||||
end
|
||||
|
||||
# yields ctx to block, use for ctx setup & configuration
|
||||
def start_server
|
||||
@port = UniquePort.call
|
||||
@host = "127.0.0.1"
|
||||
|
||||
|
@ -51,6 +61,8 @@ class TestPumaServerSSL < Minitest::Test
|
|||
|
||||
ctx.verify_mode = Puma::MiniSSL::VERIFY_NONE
|
||||
|
||||
yield ctx if block_given?
|
||||
|
||||
@events = SSLEventsHelper.new STDOUT, STDERR
|
||||
@server = Puma::Server.new app, @events
|
||||
@ssl_listener = @server.add_ssl_listener @host, @port, ctx
|
||||
|
@ -61,13 +73,8 @@ class TestPumaServerSSL < Minitest::Test
|
|||
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
||||
end
|
||||
|
||||
def teardown
|
||||
return if DISABLE_SSL
|
||||
@http.finish if @http.started?
|
||||
@server.stop(true)
|
||||
end
|
||||
|
||||
def test_url_scheme_for_https
|
||||
start_server
|
||||
body = nil
|
||||
@http.start do
|
||||
req = Net::HTTP::Get.new "/", {}
|
||||
|
@ -81,6 +88,7 @@ class TestPumaServerSSL < Minitest::Test
|
|||
end
|
||||
|
||||
def test_request_wont_block_thread
|
||||
start_server
|
||||
# Open a connection and give enough data to trigger a read, then wait
|
||||
ctx = OpenSSL::SSL::SSLContext.new
|
||||
ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
||||
|
@ -100,6 +108,7 @@ class TestPumaServerSSL < Minitest::Test
|
|||
end
|
||||
|
||||
def test_very_large_return
|
||||
start_server
|
||||
giant = "x" * 2056610
|
||||
|
||||
@server.app = proc do
|
||||
|
@ -118,6 +127,7 @@ class TestPumaServerSSL < Minitest::Test
|
|||
end
|
||||
|
||||
def test_form_submit
|
||||
start_server
|
||||
body = nil
|
||||
@http.start do
|
||||
req = Net::HTTP::Post.new '/'
|
||||
|
@ -133,6 +143,8 @@ class TestPumaServerSSL < Minitest::Test
|
|||
end
|
||||
|
||||
def test_ssl_v3_rejection
|
||||
skip("SSLv3 protocol is unavailable") if Puma::MiniSSL::OPENSSL_NO_SSL3
|
||||
start_server
|
||||
@http.ssl_version= :SSLv3
|
||||
# Ruby 2.4.5 on Travis raises ArgumentError
|
||||
assert_raises(OpenSSL::SSL::SSLError, ArgumentError) do
|
||||
|
@ -146,6 +158,46 @@ class TestPumaServerSSL < Minitest::Test
|
|||
end
|
||||
end
|
||||
|
||||
def test_tls_v1_rejection
|
||||
skip("TLSv1 protocol is unavailable") if Puma::MiniSSL::OPENSSL_NO_TLS1
|
||||
start_server { |ctx| ctx.no_tlsv1 = true }
|
||||
|
||||
if @http.respond_to? :max_version=
|
||||
@http.max_version = :TLS1
|
||||
else
|
||||
@http.ssl_version = :TLSv1
|
||||
end
|
||||
# Ruby 2.4.5 on Travis raises ArgumentError
|
||||
assert_raises(OpenSSL::SSL::SSLError, ArgumentError) do
|
||||
@http.start do
|
||||
Net::HTTP::Get.new '/'
|
||||
end
|
||||
end
|
||||
unless Puma.jruby?
|
||||
msg = /wrong version number|(unknown|unsupported) protocol|no protocols available|version too low|unknown SSL method/
|
||||
assert_match(msg, @events.error.message) if @events.error
|
||||
end
|
||||
end
|
||||
|
||||
def test_tls_v1_1_rejection
|
||||
start_server { |ctx| ctx.no_tlsv1_1 = true }
|
||||
|
||||
if @http.respond_to? :max_version=
|
||||
@http.max_version = :TLS1_1
|
||||
else
|
||||
@http.ssl_version = :TLSv1_1
|
||||
end
|
||||
# Ruby 2.4.5 on Travis raises ArgumentError
|
||||
assert_raises(OpenSSL::SSL::SSLError, ArgumentError) do
|
||||
@http.start do
|
||||
Net::HTTP::Get.new '/'
|
||||
end
|
||||
end
|
||||
unless Puma.jruby?
|
||||
msg = /wrong version number|(unknown|unsupported) protocol|no protocols available|version too low|unknown SSL method/
|
||||
assert_match(msg, @events.error.message) if @events.error
|
||||
end
|
||||
end
|
||||
end unless DISABLE_SSL
|
||||
|
||||
# client-side TLS authentication tests
|
||||
|
@ -203,16 +255,12 @@ class TestPumaServerSSLClient < Minitest::Test
|
|||
end
|
||||
|
||||
def test_verify_fail_if_no_client_cert
|
||||
return if DISABLE_SSL
|
||||
|
||||
assert_ssl_client_error_match 'peer did not return a certificate' do |http|
|
||||
# nothing
|
||||
end
|
||||
end
|
||||
|
||||
def test_verify_fail_if_client_unknown_ca
|
||||
return if DISABLE_SSL
|
||||
|
||||
assert_ssl_client_error_match('self signed certificate in certificate chain', '/DC=net/DC=puma/CN=ca-unknown') do |http|
|
||||
key = File.expand_path "../../examples/puma/client-certs/client_unknown.key", __FILE__
|
||||
crt = File.expand_path "../../examples/puma/client-certs/client_unknown.crt", __FILE__
|
||||
|
@ -223,7 +271,6 @@ class TestPumaServerSSLClient < Minitest::Test
|
|||
end
|
||||
|
||||
def test_verify_fail_if_client_expired_cert
|
||||
return if DISABLE_SSL
|
||||
assert_ssl_client_error_match('certificate has expired', '/DC=net/DC=puma/CN=client-expired') do |http|
|
||||
key = File.expand_path "../../examples/puma/client-certs/client_expired.key", __FILE__
|
||||
crt = File.expand_path "../../examples/puma/client-certs/client_expired.crt", __FILE__
|
||||
|
@ -234,7 +281,6 @@ class TestPumaServerSSLClient < Minitest::Test
|
|||
end
|
||||
|
||||
def test_verify_client_cert
|
||||
return if DISABLE_SSL
|
||||
assert_ssl_client_error_match(nil) do |http|
|
||||
key = File.expand_path "../../examples/puma/client-certs/client.key", __FILE__
|
||||
crt = File.expand_path "../../examples/puma/client-certs/client.crt", __FILE__
|
||||
|
|
Loading…
Reference in a new issue