mirror of
https://github.com/puma/puma.git
synced 2022-11-09 13:48:40 -05:00
[jruby] support setting TLS protocols + rename ssl_cipher_list (#2899)
* [jruby] support setting TLS protocols + rename ssl_cipher_list follow Java naming as we already do with keystore/truststore ... Context now does the string split and accepts an Array * [test] cipher_suites and protocols behavior * [jruby] support new TLS settings in DSL
This commit is contained in:
parent
0f0f6f5143
commit
e9f09ba1fe
7 changed files with 125 additions and 29 deletions
|
@ -1,6 +1,7 @@
|
||||||
package org.jruby.puma;
|
package org.jruby.puma;
|
||||||
|
|
||||||
import org.jruby.Ruby;
|
import org.jruby.Ruby;
|
||||||
|
import org.jruby.RubyArray;
|
||||||
import org.jruby.RubyClass;
|
import org.jruby.RubyClass;
|
||||||
import org.jruby.RubyModule;
|
import org.jruby.RubyModule;
|
||||||
import org.jruby.RubyObject;
|
import org.jruby.RubyObject;
|
||||||
|
@ -224,18 +225,25 @@ public class MiniSSL extends RubyObject { // MiniSSL::Engine
|
||||||
handshake = false;
|
handshake = false;
|
||||||
engine = sslCtx.createSSLEngine();
|
engine = sslCtx.createSSLEngine();
|
||||||
|
|
||||||
String[] protocols;
|
String[] enabledProtocols;
|
||||||
if (miniSSLContext.callMethod(context, "no_tlsv1").isTrue()) {
|
IRubyObject protocols = miniSSLContext.callMethod(context, "protocols");
|
||||||
protocols = new String[] { "TLSv1.1", "TLSv1.2", "TLSv1.3" };
|
if (protocols.isNil()) {
|
||||||
|
if (miniSSLContext.callMethod(context, "no_tlsv1").isTrue()) {
|
||||||
|
enabledProtocols = new String[] { "TLSv1.1", "TLSv1.2", "TLSv1.3" };
|
||||||
|
} else {
|
||||||
|
enabledProtocols = new String[] { "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (miniSSLContext.callMethod(context, "no_tlsv1_1").isTrue()) {
|
||||||
|
enabledProtocols = new String[] { "TLSv1.2", "TLSv1.3" };
|
||||||
|
}
|
||||||
|
} else if (protocols instanceof RubyArray) {
|
||||||
|
enabledProtocols = (String[]) ((RubyArray) protocols).toArray(new String[0]);
|
||||||
} else {
|
} else {
|
||||||
protocols = new String[] { "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" };
|
throw context.runtime.newTypeError(protocols, context.runtime.getArray());
|
||||||
}
|
}
|
||||||
|
engine.setEnabledProtocols(enabledProtocols);
|
||||||
|
|
||||||
if (miniSSLContext.callMethod(context, "no_tlsv1_1").isTrue()) {
|
|
||||||
protocols = new String[] { "TLSv1.2", "TLSv1.3" };
|
|
||||||
}
|
|
||||||
|
|
||||||
engine.setEnabledProtocols(protocols);
|
|
||||||
engine.setUseClientMode(false);
|
engine.setUseClientMode(false);
|
||||||
|
|
||||||
long verify_mode = miniSSLContext.callMethod(context, "verify_mode").convertToInteger("to_i").getLongValue();
|
long verify_mode = miniSSLContext.callMethod(context, "verify_mode").convertToInteger("to_i").getLongValue();
|
||||||
|
@ -246,10 +254,11 @@ public class MiniSSL extends RubyObject { // MiniSSL::Engine
|
||||||
engine.setNeedClientAuth(true);
|
engine.setNeedClientAuth(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
IRubyObject sslCipherListObject = miniSSLContext.callMethod(context, "ssl_cipher_list");
|
IRubyObject cipher_suites = miniSSLContext.callMethod(context, "cipher_suites");
|
||||||
if (!sslCipherListObject.isNil()) {
|
if (cipher_suites instanceof RubyArray) {
|
||||||
String[] sslCipherList = sslCipherListObject.convertToString().asJavaString().split(",");
|
engine.setEnabledCipherSuites((String[]) ((RubyArray) cipher_suites).toArray(new String[0]));
|
||||||
engine.setEnabledCipherSuites(sslCipherList);
|
} else if (!cipher_suites.isNil()) {
|
||||||
|
throw context.runtime.newTypeError(cipher_suites, context.runtime.getArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
SSLSession session = engine.getSession();
|
SSLSession session = engine.getSession();
|
||||||
|
|
|
@ -52,8 +52,9 @@ module Puma
|
||||||
backlog_str = opts[:backlog] ? "&backlog=#{Integer(opts[:backlog])}" : ''
|
backlog_str = opts[:backlog] ? "&backlog=#{Integer(opts[:backlog])}" : ''
|
||||||
|
|
||||||
if defined?(JRUBY_VERSION)
|
if defined?(JRUBY_VERSION)
|
||||||
ssl_cipher_list = opts[:ssl_cipher_list] ?
|
cipher_suites = opts[:ssl_cipher_list] ? "&ssl_cipher_list=#{opts[:ssl_cipher_list]}" : nil # old name
|
||||||
"&ssl_cipher_list=#{opts[:ssl_cipher_list]}" : nil
|
cipher_suites = "#{cipher_suites}&cipher_suites=#{opts[:cipher_suites]}" if opts[:cipher_suites]
|
||||||
|
protocols = opts[:protocols] ? "&protocols=#{opts[:protocols]}" : nil
|
||||||
|
|
||||||
keystore_additions = "keystore=#{opts[:keystore]}&keystore-pass=#{opts[:keystore_pass]}"
|
keystore_additions = "keystore=#{opts[:keystore]}&keystore-pass=#{opts[:keystore_pass]}"
|
||||||
keystore_additions = "#{keystore_additions}&keystore-type=#{opts[:keystore_type]}" if opts[:keystore_type]
|
keystore_additions = "#{keystore_additions}&keystore-type=#{opts[:keystore_type]}" if opts[:keystore_type]
|
||||||
|
@ -63,20 +64,17 @@ module Puma
|
||||||
truststore_additions = "#{truststore_additions}&truststore-type=#{opts[:truststore_type]}" if opts[:truststore_type]
|
truststore_additions = "#{truststore_additions}&truststore-type=#{opts[:truststore_type]}" if opts[:truststore_type]
|
||||||
end
|
end
|
||||||
|
|
||||||
"ssl://#{host}:#{port}?#{keystore_additions}#{truststore_additions}#{ssl_cipher_list}" \
|
"ssl://#{host}:#{port}?#{keystore_additions}#{truststore_additions}#{cipher_suites}#{protocols}" \
|
||||||
"&verify_mode=#{verify}#{tls_str}#{ca_additions}#{backlog_str}"
|
"&verify_mode=#{verify}#{tls_str}#{ca_additions}#{backlog_str}"
|
||||||
else
|
else
|
||||||
ssl_cipher_filter = opts[:ssl_cipher_filter] ?
|
ssl_cipher_filter = opts[:ssl_cipher_filter] ? "&ssl_cipher_filter=#{opts[:ssl_cipher_filter]}" : nil
|
||||||
"&ssl_cipher_filter=#{opts[:ssl_cipher_filter]}" : nil
|
v_flags = (ary = opts[:verification_flags]) ? "&verification_flags=#{Array(ary).join ','}" : nil
|
||||||
|
|
||||||
v_flags = (ary = opts[:verification_flags]) ?
|
|
||||||
"&verification_flags=#{Array(ary).join ','}" : nil
|
|
||||||
|
|
||||||
cert_flags = (cert = opts[:cert]) ? "cert=#{Puma::Util.escape(cert)}" : nil
|
cert_flags = (cert = opts[:cert]) ? "cert=#{Puma::Util.escape(cert)}" : nil
|
||||||
key_flags = (key = opts[:key]) ? "&key=#{Puma::Util.escape(key)}" : nil
|
key_flags = (key = opts[:key]) ? "&key=#{Puma::Util.escape(key)}" : nil
|
||||||
|
|
||||||
"ssl://#{host}:#{port}?#{cert_flags}#{key_flags}" \
|
"ssl://#{host}:#{port}?#{cert_flags}#{key_flags}#{ssl_cipher_filter}" \
|
||||||
"#{ssl_cipher_filter}&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}#{backlog_str}"
|
"&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}#{backlog_str}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -223,7 +223,8 @@ module Puma
|
||||||
attr_reader :truststore
|
attr_reader :truststore
|
||||||
attr_reader :truststore_type
|
attr_reader :truststore_type
|
||||||
attr_accessor :truststore_pass
|
attr_accessor :truststore_pass
|
||||||
attr_accessor :ssl_cipher_list
|
attr_reader :cipher_suites
|
||||||
|
attr_reader :protocols
|
||||||
|
|
||||||
def keystore=(keystore)
|
def keystore=(keystore)
|
||||||
check_file keystore, 'Keystore'
|
check_file keystore, 'Keystore'
|
||||||
|
@ -249,6 +250,20 @@ module Puma
|
||||||
@truststore_type = type
|
@truststore_type = type
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def cipher_suites=(list)
|
||||||
|
list = list.split(',').map(&:strip) if list.is_a?(String)
|
||||||
|
@cipher_suites = list
|
||||||
|
end
|
||||||
|
|
||||||
|
# aliases for backwards compatibility
|
||||||
|
alias_method :ssl_cipher_list, :cipher_suites
|
||||||
|
alias_method :ssl_cipher_list=, :cipher_suites=
|
||||||
|
|
||||||
|
def protocols=(list)
|
||||||
|
list = list.split(',').map(&:strip) if list.is_a?(String)
|
||||||
|
@protocols = list
|
||||||
|
end
|
||||||
|
|
||||||
def check
|
def check
|
||||||
raise "Keystore not configured" unless @keystore
|
raise "Keystore not configured" unless @keystore
|
||||||
# @truststore defaults to @keystore due backwards compatibility
|
# @truststore defaults to @keystore due backwards compatibility
|
||||||
|
|
|
@ -29,7 +29,8 @@ module Puma
|
||||||
ctx.truststore_type = params['truststore-type']
|
ctx.truststore_type = params['truststore-type']
|
||||||
end
|
end
|
||||||
|
|
||||||
ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list']
|
ctx.cipher_suites = params['cipher_suites'] || params['ssl_cipher_list']
|
||||||
|
ctx.protocols = params['protocols'] if params['protocols']
|
||||||
else
|
else
|
||||||
if params['key'].nil? && params['key_pem'].nil?
|
if params['key'].nil? && params['key_pem'].nil?
|
||||||
log_writer.error "Please specify the SSL key via 'key=' or 'key_pem='"
|
log_writer.error "Please specify the SSL key via 'key=' or 'key_pem='"
|
||||||
|
|
|
@ -478,13 +478,26 @@ class TestBinderJRuby < TestBinderBase
|
||||||
def test_binder_parses_jruby_ssl_options
|
def test_binder_parses_jruby_ssl_options
|
||||||
skip_unless :ssl
|
skip_unless :ssl
|
||||||
|
|
||||||
keystore = File.expand_path "../../examples/puma/keystore.jks", __FILE__
|
cipher_suites = ['TLS_DHE_RSA_WITH_AES_128_CBC_SHA', 'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256']
|
||||||
ssl_cipher_list = "TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
|
|
||||||
|
|
||||||
@binder.parse ["ssl://0.0.0.0:8080?#{ssl_query}"], @log_writer
|
@binder.parse ["ssl://0.0.0.0:8080?#{ssl_query}"], @log_writer
|
||||||
|
|
||||||
assert_equal keystore, ssl_context_for_binder.keystore
|
assert_equal @keystore, ssl_context_for_binder.keystore
|
||||||
assert_equal ssl_cipher_list, ssl_context_for_binder.ssl_cipher_list
|
assert_equal cipher_suites, ssl_context_for_binder.cipher_suites
|
||||||
|
assert_equal cipher_suites, ssl_context_for_binder.ssl_cipher_list
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_binder_parses_jruby_ssl_protocols_and_cipher_suites_options
|
||||||
|
skip_unless :ssl
|
||||||
|
|
||||||
|
keystore = File.expand_path "../../examples/puma/keystore.jks", __FILE__
|
||||||
|
cipher = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
|
||||||
|
ssl_query = "keystore=#{keystore}&keystore-pass=jruby_puma&cipher_suites=#{cipher}&protocols=TLSv1.3,TLSv1.2"
|
||||||
|
|
||||||
|
@binder.parse ["ssl://0.0.0.0:8080?#{ssl_query}"], @log_writer
|
||||||
|
|
||||||
|
assert_equal [ 'TLSv1.3', 'TLSv1.2' ], ssl_context_for_binder.protocols
|
||||||
|
assert_equal [ cipher ], ssl_context_for_binder.cipher_suites
|
||||||
end
|
end
|
||||||
end if ::Puma::IS_JRUBY
|
end if ::Puma::IS_JRUBY
|
||||||
|
|
||||||
|
|
|
@ -149,6 +149,30 @@ class TestConfigFile < TestConfigFileBase
|
||||||
skip_unless :jruby
|
skip_unless :jruby
|
||||||
skip_unless :ssl
|
skip_unless :ssl
|
||||||
|
|
||||||
|
ciphers = "TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
|
||||||
|
|
||||||
|
conf = Puma::Configuration.new do |c|
|
||||||
|
c.ssl_bind "0.0.0.0", "9292", {
|
||||||
|
keystore: "/path/to/keystore",
|
||||||
|
keystore_pass: "password",
|
||||||
|
cipher_suites: ciphers,
|
||||||
|
protocols: 'TLSv1.2',
|
||||||
|
verify_mode: "the_verify_mode"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
conf.load
|
||||||
|
|
||||||
|
ssl_binding = "ssl://0.0.0.0:9292?keystore=/path/to/keystore" \
|
||||||
|
"&keystore-pass=password&cipher_suites=#{ciphers}&protocols=TLSv1.2" \
|
||||||
|
"&verify_mode=the_verify_mode"
|
||||||
|
assert_equal [ssl_binding], conf.options[:binds]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_ssl_bind_jruby_with_ssl_cipher_list
|
||||||
|
skip_unless :jruby
|
||||||
|
skip_unless :ssl
|
||||||
|
|
||||||
cipher_list = "TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
|
cipher_list = "TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
|
||||||
|
|
||||||
conf = Puma::Configuration.new do |c|
|
conf = Puma::Configuration.new do |c|
|
||||||
|
|
|
@ -453,6 +453,42 @@ class TestPumaServerSSLClient < Minitest::Test
|
||||||
end
|
end
|
||||||
end if Puma.jruby?
|
end if Puma.jruby?
|
||||||
|
|
||||||
|
def test_allows_to_specify_cipher_suites_and_protocols
|
||||||
|
ctx = CTX.dup
|
||||||
|
ctx.cipher_suites = [ 'TLS_RSA_WITH_AES_128_GCM_SHA256' ]
|
||||||
|
ctx.protocols = 'TLSv1.2'
|
||||||
|
|
||||||
|
assert_ssl_client_error_match(false, context: ctx) do |http|
|
||||||
|
key = "#{CERT_PATH}/client.key"
|
||||||
|
crt = "#{CERT_PATH}/client.crt"
|
||||||
|
http.key = OpenSSL::PKey::RSA.new File.read(key)
|
||||||
|
http.cert = OpenSSL::X509::Certificate.new File.read(crt)
|
||||||
|
http.ca_file = "#{CERT_PATH}/ca.crt"
|
||||||
|
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
||||||
|
|
||||||
|
http.ssl_version = :TLSv1_2
|
||||||
|
http.ciphers = [ 'TLS_RSA_WITH_AES_128_GCM_SHA256' ]
|
||||||
|
end
|
||||||
|
end if Puma.jruby?
|
||||||
|
|
||||||
|
def test_fails_when_no_cipher_suites_in_common
|
||||||
|
ctx = CTX.dup
|
||||||
|
ctx.cipher_suites = [ 'TLS_RSA_WITH_AES_128_GCM_SHA256' ]
|
||||||
|
ctx.protocols = 'TLSv1.2'
|
||||||
|
|
||||||
|
assert_ssl_client_error_match(/no cipher suites in common/, context: ctx) do |http|
|
||||||
|
key = "#{CERT_PATH}/client.key"
|
||||||
|
crt = "#{CERT_PATH}/client.crt"
|
||||||
|
http.key = OpenSSL::PKey::RSA.new File.read(key)
|
||||||
|
http.cert = OpenSSL::X509::Certificate.new File.read(crt)
|
||||||
|
http.ca_file = "#{CERT_PATH}/ca.crt"
|
||||||
|
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
||||||
|
|
||||||
|
http.ssl_version = :TLSv1_2
|
||||||
|
http.ciphers = [ 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384' ]
|
||||||
|
end
|
||||||
|
end if Puma.jruby?
|
||||||
|
|
||||||
end if ::Puma::HAS_SSL
|
end if ::Puma::HAS_SSL
|
||||||
|
|
||||||
class TestPumaServerSSLWithCertPemAndKeyPem < Minitest::Test
|
class TestPumaServerSSLWithCertPemAndKeyPem < Minitest::Test
|
||||||
|
|
Loading…
Add table
Reference in a new issue