1
0
Fork 0
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:
Karol Bucek 2022-07-04 16:16:31 +02:00 committed by GitHub
parent 0f0f6f5143
commit e9f09ba1fe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 125 additions and 29 deletions

View file

@ -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();

View file

@ -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

View file

@ -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

View file

@ -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='"

View file

@ -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

View file

@ -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|

View file

@ -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