From 4ddcdb0aa8117139edcaa68c14e21a607e914c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20=C4=86wik=C5=82a?= Date: Sat, 24 Oct 2020 17:56:50 +0200 Subject: [PATCH 1/2] Fix OpenSSL::SSL::SSLContext#min_version= failure --- .../org/jruby/ext/openssl/SSLContext.java | 115 +++++++++++++++++- src/test/integration/ssl_test.rb | 22 ++++ src/test/ruby/ssl/test_context.rb | 6 + src/test/ruby/ssl/test_socket.rb | 28 ++++- 4 files changed, 162 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/jruby/ext/openssl/SSLContext.java b/src/main/java/org/jruby/ext/openssl/SSLContext.java index 2405675d..664b3965 100644 --- a/src/main/java/org/jruby/ext/openssl/SSLContext.java +++ b/src/main/java/org/jruby/ext/openssl/SSLContext.java @@ -53,6 +53,7 @@ import org.jruby.Ruby; import org.jruby.RubyArray; import org.jruby.RubyClass; +import org.jruby.RubyFixnum; import org.jruby.RubyHash; import org.jruby.RubyInteger; import org.jruby.RubyModule; @@ -96,31 +97,44 @@ public class SSLContext extends RubyObject { // Mapping table for OpenSSL's SSL_METHOD -> JSSE's SSLContext algorithm. private static final HashMap SSL_VERSION_OSSL2JSSE; + // Inverse mapping (incomplete, does not map 1-1 with SSL_VERSION_OSSL2JSSE) + private static final HashMap SSL_VERSION_OSSL2JSSE_INV; // Mapping table for JSEE's enabled protocols for the algorithm. private static final Map ENABLED_PROTOCOLS; + // Mapping table from CRuby parse_proto_version(VALUE str) + private static final Map PROTO_VERSION_MAP; + // same as METHODS_MAP from ssl.rb + private static final Map METHODS_MAP; + private static final Map METHODS_MAP_INV; static { SSL_VERSION_OSSL2JSSE = new LinkedHashMap(20, 1); ENABLED_PROTOCOLS = new HashMap(8, 1); + SSL_VERSION_OSSL2JSSE_INV = new HashMap(); + SSL_VERSION_OSSL2JSSE.put("TLSv1", "TLSv1"); SSL_VERSION_OSSL2JSSE.put("TLSv1_server", "TLSv1"); SSL_VERSION_OSSL2JSSE.put("TLSv1_client", "TLSv1"); ENABLED_PROTOCOLS.put("TLSv1", new String[] { "TLSv1" }); + SSL_VERSION_OSSL2JSSE_INV.put("TLSv1", "TLSv1_1"); SSL_VERSION_OSSL2JSSE.put("SSLv2", "SSLv2"); SSL_VERSION_OSSL2JSSE.put("SSLv2_server", "SSLv2"); SSL_VERSION_OSSL2JSSE.put("SSLv2_client", "SSLv2"); ENABLED_PROTOCOLS.put("SSLv2", new String[] { "SSLv2" }); + SSL_VERSION_OSSL2JSSE_INV.put("SSLv2", "SSLv2"); SSL_VERSION_OSSL2JSSE.put("SSLv3", "SSLv3"); SSL_VERSION_OSSL2JSSE.put("SSLv3_server", "SSLv3"); SSL_VERSION_OSSL2JSSE.put("SSLv3_client", "SSLv3"); ENABLED_PROTOCOLS.put("SSLv3", new String[] { "SSLv3" }); + SSL_VERSION_OSSL2JSSE_INV.put("SSLv3", "SSLv3"); SSL_VERSION_OSSL2JSSE.put("SSLv23", "SSL"); SSL_VERSION_OSSL2JSSE.put("SSLv23_server", "SSL"); SSL_VERSION_OSSL2JSSE.put("SSLv23_client", "SSL"); + SSL_VERSION_OSSL2JSSE_INV.put("SSL", "SSLv23"); ENABLED_PROTOCOLS.put("SSL", new String[] { "SSLv2", "SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2" }); @@ -138,10 +152,34 @@ public class SSLContext extends RubyObject { SSL_VERSION_OSSL2JSSE.put("TLSv1_1", "TLSv1.1"); // supported on MRI 2.x SSL_VERSION_OSSL2JSSE.put("TLSv1_2", "TLSv1.2"); // supported on MRI 2.x ENABLED_PROTOCOLS.put("TLSv1.2", new String[] { "TLSv1.2" }); + SSL_VERSION_OSSL2JSSE_INV.put("TLSv1.1", "TLSv1_1"); + SSL_VERSION_OSSL2JSSE_INV.put("TLSv1.2", "TLSv1_2"); SSL_VERSION_OSSL2JSSE.put("TLSv1.2", "TLSv1.2"); // just for completeness SSL_VERSION_OSSL2JSSE.put("TLSv1_2_server", "TLSv1.2"); SSL_VERSION_OSSL2JSSE.put("TLSv1_2_client", "TLSv1.2"); + + PROTO_VERSION_MAP = new HashMap(); + PROTO_VERSION_MAP.put("SSL2", SSL.SSL2_VERSION); + PROTO_VERSION_MAP.put("SSL3", SSL.SSL3_VERSION); + PROTO_VERSION_MAP.put("TLS1", SSL.TLS1_VERSION); + PROTO_VERSION_MAP.put("TLS1_1", SSL.TLS1_1_VERSION); + PROTO_VERSION_MAP.put("TLS1_2", SSL.TLS1_2_VERSION); + PROTO_VERSION_MAP.put("TLS1_3", SSL.TLS1_3_VERSION); + + METHODS_MAP = new HashMap(); + METHODS_MAP.put("SSLv23", 0); + METHODS_MAP.put("SSLv2", SSL.SSL2_VERSION); + METHODS_MAP.put("SSLv3", SSL.SSL3_VERSION); + METHODS_MAP.put("TLSv1", SSL.TLS1_VERSION); + METHODS_MAP.put("TLSv1_1", SSL.TLS1_1_VERSION); + METHODS_MAP.put("TLSv1_2", SSL.TLS1_2_VERSION); + METHODS_MAP.put("TLSv1_3", SSL.TLS1_3_VERSION); + + METHODS_MAP_INV = new HashMap(); + for(Map.Entry e: METHODS_MAP.entrySet()) { + METHODS_MAP_INV.put(e.getValue(), e.getKey()); + } } private static ObjectAllocator SSLCONTEXT_ALLOCATOR = new ObjectAllocator() { @@ -267,9 +305,14 @@ public SSLContext(Ruby runtime, RubyClass type) { } private String ciphers = CipherStrings.SSL_DEFAULT_CIPHER_LIST; - private String protocol = "SSL"; // SSLv23 in OpenSSL by default + private static final String DEFAULT_PROTOCOL = "SSL"; // SSLv23 in OpenSSL by default + private static final int DEFAULT_PROTOCOL_VERSION = 0; + private String protocol = null; + private Integer protocolVersion = null; private boolean protocolForServer = true; private boolean protocolForClient = true; + private int minProtocolVersion = 0; + private int maxProtocolVersion = 0; private PKey t_key; private X509Cert t_cert; @@ -446,6 +489,8 @@ public IRubyObject setup(final ThreadContext context) { } */ + setupProtocolVersion(context); + try { internalContext = createInternalContext(context, cert, key, store, clientCert, extraChainCert, verifyMode, timeout); } @@ -456,6 +501,37 @@ public IRubyObject setup(final ThreadContext context) { return runtime.getTrue(); } + private void setupProtocolVersion(final ThreadContext context) { + if (protocolVersion == null) { + String ssl_version = null; + if (maxProtocolVersion != 0) { + ssl_version = METHODS_MAP_INV.get(maxProtocolVersion); + set_ssl_version(ssl_version); + } else { + ssl_version = getMaxSupportedProtocolVersion(); + set_ssl_version(ssl_version); + } + } + + if (minProtocolVersion !=0 || maxProtocolVersion !=0 ) { + if (minProtocolVersion != 0 && protocolVersion < minProtocolVersion) { + throw newSSLError(context.runtime, "no protocols available"); + } + if (maxProtocolVersion != 0 && protocolVersion > maxProtocolVersion) { + throw newSSLError(context.runtime, "no protocols available"); + } + } + } + + private String getMaxSupportedProtocolVersion() { + //TODO: probably needs to be computed from + //javax.net.ssl.SSLContext.getSupportedSSLParameters().getProtocols() + //some changes prob. needed in dummySSLEngine and SecurityHelper.getSSLContext + //Hardcoding now. + //Nonetheless, TLS1.3 needs more changes in jruby-openssl + return "TLSv1_2"; + } + @JRubyMethod public RubyArray ciphers(final ThreadContext context) { return matchedCiphers(context); @@ -464,7 +540,8 @@ public RubyArray ciphers(final ThreadContext context) { private RubyArray matchedCiphers(final ThreadContext context) { final Ruby runtime = context.runtime; try { - final String[] supported = getSupportedCipherSuites(this.protocol); + setupProtocolVersion(context); + final String[] supported = getSupportedCipherSuites(protocol); final Collection cipherDefs = CipherStrings.matchingCiphers(this.ciphers, supported, false); @@ -517,17 +594,47 @@ public IRubyObject set_ssl_version(IRubyObject version) { } else { versionStr = version.convertToString().toString(); } + set_ssl_version(versionStr); + return version; + } + + private void set_ssl_version(final String versionStr) { final String protocol = SSL_VERSION_OSSL2JSSE.get(versionStr); if ( protocol == null ) { throw getRuntime().newArgumentError("unknown SSL method `"+ versionStr +"'"); } this.protocol = protocol; + this.protocolVersion = METHODS_MAP.get(versionStr); protocolForServer = ! versionStr.endsWith("_client"); protocolForClient = ! versionStr.endsWith("_server"); - return version; } - final String getProtocol() { return this.protocol; } + @JRubyMethod(name = "set_minmax_proto_version") + public IRubyObject set_minmax_proto_version(ThreadContext context, IRubyObject minVersion, IRubyObject maxVersion) { + minProtocolVersion = parseProtoVersion(minVersion); + maxProtocolVersion = parseProtoVersion(maxVersion); + + return context.nil; + } + + private int parseProtoVersion(IRubyObject version) { + if (version.isNil()) + return 0; + if (version instanceof RubyFixnum) { + return (int) ((RubyFixnum) version).getLongValue(); + } + + String string = version.asString().asJavaString(); + Integer sslVersion = PROTO_VERSION_MAP.get(string); + + if (sslVersion == null) { + throw getRuntime().newArgumentError("unrecognized version \"" + string + "\""); + } + + return sslVersion; + } + + final String getProtocol() { return this.protocol; } @JRubyMethod(name = "session_cache_mode") public IRubyObject session_cache_mode() { diff --git a/src/test/integration/ssl_test.rb b/src/test/integration/ssl_test.rb index 7256ff9f..cdc90f13 100644 --- a/src/test/integration/ssl_test.rb +++ b/src/test/integration/ssl_test.rb @@ -38,4 +38,26 @@ def test_connect_net_http_1 puts http.get('/') end + def test_connect_ssl_minmax_version + require 'openssl' + require 'socket' + + puts "\n" + puts "------------------------------------------------------------" + puts "-- SSL min/max version ... 'https://google.co.uk'" + puts "------------------------------------------------------------" + + ctx = OpenSSL::SSL::SSLContext.new() + ctx.min_version = OpenSSL::SSL::TLS1_VERSION + ctx.max_version = OpenSSL::SSL::TLS1_1_VERSION + client = TCPSocket.new('google.co.uk', 443) + ssl = OpenSSL::SSL::SSLSocket.new(client, ctx) + ssl.sync_close = true + ssl.connect + begin + assert_equal 'TLSv1.1', ssl.ssl_version + ensure + ssl.sysclose + end + end end \ No newline at end of file diff --git a/src/test/ruby/ssl/test_context.rb b/src/test/ruby/ssl/test_context.rb index 9d6158d5..5d248cc6 100644 --- a/src/test/ruby/ssl/test_context.rb +++ b/src/test/ruby/ssl/test_context.rb @@ -104,6 +104,12 @@ def test_context_set_ssl_version end assert_raises(TypeError) { context.ssl_version = 12 } end + + def test_context_minmax_version + context = OpenSSL::SSL::SSLContext.new + context.min_version = OpenSSL::SSL::TLS1_VERSION + context.max_version = OpenSSL::SSL::TLS1_2_VERSION + end def test_context_ciphers self.class.disable_security_restrictions diff --git a/src/test/ruby/ssl/test_socket.rb b/src/test/ruby/ssl/test_socket.rb index c7dc3a80..e0135ab2 100644 --- a/src/test/ruby/ssl/test_socket.rb +++ b/src/test/ruby/ssl/test_socket.rb @@ -136,6 +136,21 @@ def test_connect_nonblock end end if RUBY_VERSION > '2.2' + def test_minmax_ssl_version + # WIP: this test should setup it's own server to prove that SSLContext.min_version and SSLContext.max_version work as expected + # However below line is failing with "no cipher match" even on master + # When "no cipher match" is fixed, then this test need cleanup + + #ssl_server = server + begin + ssl_client = client(443, host: "google.com", min_version: OpenSSL::SSL::TLS1_VERSION, max_version: OpenSSL::SSL::TLS1_1_VERSION, ciphers: nil) + #ssl_client = client(server_port(ssl_server), min_version: OpenSSL::SSL::TLS1_VERSION, max_version: OpenSSL::SSL::TLS1_1_VERSION) + assert_equal 'TLSv1.1', ssl_client.ssl_version + ensure + ssl_client.sysclose unless ssl_client.nil? + end + end if RUBY_VERSION > '2.3' + def test_inherited_socket; require 'socket' inheritedSSLSocket = Class.new(OpenSSL::SSL::SSLSocket) @@ -150,18 +165,21 @@ def test_inherited_socket; require 'socket' private - def server; require 'socket' + def server(ssl_version: nil); require 'socket' host = "127.0.0.1"; port = 0 ctx = OpenSSL::SSL::SSLContext.new() - ctx.ciphers = "ADH" + ctx.ssl_version = ssl_version unless ssl_version.nil? + ctx.ciphers = "ADH" server = TCPServer.new(host, port) OpenSSL::SSL::SSLServer.new(server, ctx) end - def client(port); require 'socket' - host = "127.0.0.1" + def client(port, host: "127.0.0.1", min_version: nil, max_version: nil, ciphers: "ADH") + require 'socket' ctx = OpenSSL::SSL::SSLContext.new() - ctx.ciphers = "ADH" + ctx.min_version = min_version unless min_version.nil? + ctx.max_version = max_version unless max_version.nil? + ctx.ciphers = ciphers unless ciphers.nil? client = TCPSocket.new(host, port) ssl = OpenSSL::SSL::SSLSocket.new(client, ctx) ssl.connect From 2d91787af6347bacf1aefa865b674d6d99732db3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20=C4=86wikla?= Date: Wed, 28 Oct 2020 17:15:04 +0100 Subject: [PATCH 2/2] Rework: Fix OpenSSL::SSL::SSLContext#min_version= failure --- .../org/jruby/ext/openssl/SSLContext.java | 89 ++++--------------- src/test/ruby/ssl/test_context.rb | 4 +- src/test/ruby/ssl/test_helper.rb | 3 +- src/test/ruby/ssl/test_socket.rb | 28 ++---- src/test/ruby/ssl/test_ssl.rb | 32 +++++++ 5 files changed, 58 insertions(+), 98 deletions(-) diff --git a/src/main/java/org/jruby/ext/openssl/SSLContext.java b/src/main/java/org/jruby/ext/openssl/SSLContext.java index 664b3965..cb02f440 100644 --- a/src/main/java/org/jruby/ext/openssl/SSLContext.java +++ b/src/main/java/org/jruby/ext/openssl/SSLContext.java @@ -97,44 +97,35 @@ public class SSLContext extends RubyObject { // Mapping table for OpenSSL's SSL_METHOD -> JSSE's SSLContext algorithm. private static final HashMap SSL_VERSION_OSSL2JSSE; - // Inverse mapping (incomplete, does not map 1-1 with SSL_VERSION_OSSL2JSSE) - private static final HashMap SSL_VERSION_OSSL2JSSE_INV; // Mapping table for JSEE's enabled protocols for the algorithm. private static final Map ENABLED_PROTOCOLS; // Mapping table from CRuby parse_proto_version(VALUE str) private static final Map PROTO_VERSION_MAP; - // same as METHODS_MAP from ssl.rb - private static final Map METHODS_MAP; - private static final Map METHODS_MAP_INV; + + private static final Map JSSE_TO_VERSION; static { SSL_VERSION_OSSL2JSSE = new LinkedHashMap(20, 1); ENABLED_PROTOCOLS = new HashMap(8, 1); - SSL_VERSION_OSSL2JSSE_INV = new HashMap(); - SSL_VERSION_OSSL2JSSE.put("TLSv1", "TLSv1"); SSL_VERSION_OSSL2JSSE.put("TLSv1_server", "TLSv1"); SSL_VERSION_OSSL2JSSE.put("TLSv1_client", "TLSv1"); ENABLED_PROTOCOLS.put("TLSv1", new String[] { "TLSv1" }); - SSL_VERSION_OSSL2JSSE_INV.put("TLSv1", "TLSv1_1"); SSL_VERSION_OSSL2JSSE.put("SSLv2", "SSLv2"); SSL_VERSION_OSSL2JSSE.put("SSLv2_server", "SSLv2"); SSL_VERSION_OSSL2JSSE.put("SSLv2_client", "SSLv2"); ENABLED_PROTOCOLS.put("SSLv2", new String[] { "SSLv2" }); - SSL_VERSION_OSSL2JSSE_INV.put("SSLv2", "SSLv2"); SSL_VERSION_OSSL2JSSE.put("SSLv3", "SSLv3"); SSL_VERSION_OSSL2JSSE.put("SSLv3_server", "SSLv3"); SSL_VERSION_OSSL2JSSE.put("SSLv3_client", "SSLv3"); ENABLED_PROTOCOLS.put("SSLv3", new String[] { "SSLv3" }); - SSL_VERSION_OSSL2JSSE_INV.put("SSLv3", "SSLv3"); SSL_VERSION_OSSL2JSSE.put("SSLv23", "SSL"); SSL_VERSION_OSSL2JSSE.put("SSLv23_server", "SSL"); SSL_VERSION_OSSL2JSSE.put("SSLv23_client", "SSL"); - SSL_VERSION_OSSL2JSSE_INV.put("SSL", "SSLv23"); ENABLED_PROTOCOLS.put("SSL", new String[] { "SSLv2", "SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2" }); @@ -152,8 +143,6 @@ public class SSLContext extends RubyObject { SSL_VERSION_OSSL2JSSE.put("TLSv1_1", "TLSv1.1"); // supported on MRI 2.x SSL_VERSION_OSSL2JSSE.put("TLSv1_2", "TLSv1.2"); // supported on MRI 2.x ENABLED_PROTOCOLS.put("TLSv1.2", new String[] { "TLSv1.2" }); - SSL_VERSION_OSSL2JSSE_INV.put("TLSv1.1", "TLSv1_1"); - SSL_VERSION_OSSL2JSSE_INV.put("TLSv1.2", "TLSv1_2"); SSL_VERSION_OSSL2JSSE.put("TLSv1.2", "TLSv1.2"); // just for completeness SSL_VERSION_OSSL2JSSE.put("TLSv1_2_server", "TLSv1.2"); @@ -167,19 +156,13 @@ public class SSLContext extends RubyObject { PROTO_VERSION_MAP.put("TLS1_2", SSL.TLS1_2_VERSION); PROTO_VERSION_MAP.put("TLS1_3", SSL.TLS1_3_VERSION); - METHODS_MAP = new HashMap(); - METHODS_MAP.put("SSLv23", 0); - METHODS_MAP.put("SSLv2", SSL.SSL2_VERSION); - METHODS_MAP.put("SSLv3", SSL.SSL3_VERSION); - METHODS_MAP.put("TLSv1", SSL.TLS1_VERSION); - METHODS_MAP.put("TLSv1_1", SSL.TLS1_1_VERSION); - METHODS_MAP.put("TLSv1_2", SSL.TLS1_2_VERSION); - METHODS_MAP.put("TLSv1_3", SSL.TLS1_3_VERSION); - - METHODS_MAP_INV = new HashMap(); - for(Map.Entry e: METHODS_MAP.entrySet()) { - METHODS_MAP_INV.put(e.getValue(), e.getKey()); - } + JSSE_TO_VERSION = new HashMap(); + JSSE_TO_VERSION.put("SSLv2", SSL.SSL2_VERSION); + JSSE_TO_VERSION.put("SSLv3", SSL.SSL3_VERSION); + JSSE_TO_VERSION.put("TLSv1", SSL.TLS1_VERSION); + JSSE_TO_VERSION.put("TLSv1.1", SSL.TLS1_1_VERSION); + JSSE_TO_VERSION.put("TLSv1.2", SSL.TLS1_2_VERSION); + JSSE_TO_VERSION.put("TLSv1.3", SSL.TLS1_3_VERSION); } private static ObjectAllocator SSLCONTEXT_ALLOCATOR = new ObjectAllocator() { @@ -305,10 +288,7 @@ public SSLContext(Ruby runtime, RubyClass type) { } private String ciphers = CipherStrings.SSL_DEFAULT_CIPHER_LIST; - private static final String DEFAULT_PROTOCOL = "SSL"; // SSLv23 in OpenSSL by default - private static final int DEFAULT_PROTOCOL_VERSION = 0; - private String protocol = null; - private Integer protocolVersion = null; + private String protocol = "SSL"; // SSLv23 in OpenSSL by default private boolean protocolForServer = true; private boolean protocolForClient = true; private int minProtocolVersion = 0; @@ -489,8 +469,6 @@ public IRubyObject setup(final ThreadContext context) { } */ - setupProtocolVersion(context); - try { internalContext = createInternalContext(context, cert, key, store, clientCert, extraChainCert, verifyMode, timeout); } @@ -501,37 +479,6 @@ public IRubyObject setup(final ThreadContext context) { return runtime.getTrue(); } - private void setupProtocolVersion(final ThreadContext context) { - if (protocolVersion == null) { - String ssl_version = null; - if (maxProtocolVersion != 0) { - ssl_version = METHODS_MAP_INV.get(maxProtocolVersion); - set_ssl_version(ssl_version); - } else { - ssl_version = getMaxSupportedProtocolVersion(); - set_ssl_version(ssl_version); - } - } - - if (minProtocolVersion !=0 || maxProtocolVersion !=0 ) { - if (minProtocolVersion != 0 && protocolVersion < minProtocolVersion) { - throw newSSLError(context.runtime, "no protocols available"); - } - if (maxProtocolVersion != 0 && protocolVersion > maxProtocolVersion) { - throw newSSLError(context.runtime, "no protocols available"); - } - } - } - - private String getMaxSupportedProtocolVersion() { - //TODO: probably needs to be computed from - //javax.net.ssl.SSLContext.getSupportedSSLParameters().getProtocols() - //some changes prob. needed in dummySSLEngine and SecurityHelper.getSSLContext - //Hardcoding now. - //Nonetheless, TLS1.3 needs more changes in jruby-openssl - return "TLSv1_2"; - } - @JRubyMethod public RubyArray ciphers(final ThreadContext context) { return matchedCiphers(context); @@ -540,7 +487,6 @@ public RubyArray ciphers(final ThreadContext context) { private RubyArray matchedCiphers(final ThreadContext context) { final Ruby runtime = context.runtime; try { - setupProtocolVersion(context); final String[] supported = getSupportedCipherSuites(protocol); final Collection cipherDefs = CipherStrings.matchingCiphers(this.ciphers, supported, false); @@ -594,19 +540,14 @@ public IRubyObject set_ssl_version(IRubyObject version) { } else { versionStr = version.convertToString().toString(); } - set_ssl_version(versionStr); - return version; - } - - private void set_ssl_version(final String versionStr) { final String protocol = SSL_VERSION_OSSL2JSSE.get(versionStr); if ( protocol == null ) { throw getRuntime().newArgumentError("unknown SSL method `"+ versionStr +"'"); } this.protocol = protocol; - this.protocolVersion = METHODS_MAP.get(versionStr); protocolForServer = ! versionStr.endsWith("_client"); protocolForClient = ! versionStr.endsWith("_server"); + return version; } @JRubyMethod(name = "set_minmax_proto_version") @@ -621,7 +562,7 @@ private int parseProtoVersion(IRubyObject version) { if (version.isNil()) return 0; if (version instanceof RubyFixnum) { - return (int) ((RubyFixnum) version).getLongValue(); + return RubyFixnum.fix2int(version); } String string = version.asString().asJavaString(); @@ -634,7 +575,7 @@ private int parseProtoVersion(IRubyObject version) { return sslVersion; } - final String getProtocol() { return this.protocol; } + final String getProtocol() { return this.protocol; } @JRubyMethod(name = "session_cache_mode") public IRubyObject session_cache_mode() { @@ -758,6 +699,10 @@ private String[] getEnabledProtocols(final SSLEngine engine) { final String[] engineProtocols = engine.getEnabledProtocols(); final List protocols = new ArrayList(enabledProtocols.length); for ( final String enabled : enabledProtocols ) { + int protocolVersion = JSSE_TO_VERSION.get(enabled); + if (minProtocolVersion != 0 && protocolVersion < minProtocolVersion) continue; + if (maxProtocolVersion != 0 && protocolVersion > maxProtocolVersion) continue; + if (((options & OP_NO_SSLv2) != 0) && enabled.equals("SSLv2")) continue; if (((options & OP_NO_SSLv3) != 0) && enabled.equals("SSLv3")) continue; if (((options & OP_NO_TLSv1) != 0) && enabled.equals("TLSv1")) continue; diff --git a/src/test/ruby/ssl/test_context.rb b/src/test/ruby/ssl/test_context.rb index 5d248cc6..a441c622 100644 --- a/src/test/ruby/ssl/test_context.rb +++ b/src/test/ruby/ssl/test_context.rb @@ -104,12 +104,12 @@ def test_context_set_ssl_version end assert_raises(TypeError) { context.ssl_version = 12 } end - + def test_context_minmax_version context = OpenSSL::SSL::SSLContext.new context.min_version = OpenSSL::SSL::TLS1_VERSION context.max_version = OpenSSL::SSL::TLS1_2_VERSION - end + end if RUBY_VERSION > '2.3' def test_context_ciphers self.class.disable_security_restrictions diff --git a/src/test/ruby/ssl/test_helper.rb b/src/test/ruby/ssl/test_helper.rb index d82f012b..29297dbb 100644 --- a/src/test/ruby/ssl/test_helper.rb +++ b/src/test/ruby/ssl/test_helper.rb @@ -1,4 +1,5 @@ require File.expand_path('../test_helper', File.dirname(__FILE__)) +require 'openssl' module SSLTestHelper @@ -7,7 +8,7 @@ module SSLTestHelper PORT = 20443 ITERATIONS = ($0 == __FILE__) ? 100 : 10 - def setup; require 'openssl' + def setup; @ca_key = OpenSSL::PKey::RSA.new TEST_KEY_RSA2048 @svr_key = OpenSSL::PKey::RSA.new TEST_KEY_RSA1024 diff --git a/src/test/ruby/ssl/test_socket.rb b/src/test/ruby/ssl/test_socket.rb index e0135ab2..c7dc3a80 100644 --- a/src/test/ruby/ssl/test_socket.rb +++ b/src/test/ruby/ssl/test_socket.rb @@ -136,21 +136,6 @@ def test_connect_nonblock end end if RUBY_VERSION > '2.2' - def test_minmax_ssl_version - # WIP: this test should setup it's own server to prove that SSLContext.min_version and SSLContext.max_version work as expected - # However below line is failing with "no cipher match" even on master - # When "no cipher match" is fixed, then this test need cleanup - - #ssl_server = server - begin - ssl_client = client(443, host: "google.com", min_version: OpenSSL::SSL::TLS1_VERSION, max_version: OpenSSL::SSL::TLS1_1_VERSION, ciphers: nil) - #ssl_client = client(server_port(ssl_server), min_version: OpenSSL::SSL::TLS1_VERSION, max_version: OpenSSL::SSL::TLS1_1_VERSION) - assert_equal 'TLSv1.1', ssl_client.ssl_version - ensure - ssl_client.sysclose unless ssl_client.nil? - end - end if RUBY_VERSION > '2.3' - def test_inherited_socket; require 'socket' inheritedSSLSocket = Class.new(OpenSSL::SSL::SSLSocket) @@ -165,21 +150,18 @@ def test_inherited_socket; require 'socket' private - def server(ssl_version: nil); require 'socket' + def server; require 'socket' host = "127.0.0.1"; port = 0 ctx = OpenSSL::SSL::SSLContext.new() - ctx.ssl_version = ssl_version unless ssl_version.nil? - ctx.ciphers = "ADH" + ctx.ciphers = "ADH" server = TCPServer.new(host, port) OpenSSL::SSL::SSLServer.new(server, ctx) end - def client(port, host: "127.0.0.1", min_version: nil, max_version: nil, ciphers: "ADH") - require 'socket' + def client(port); require 'socket' + host = "127.0.0.1" ctx = OpenSSL::SSL::SSLContext.new() - ctx.min_version = min_version unless min_version.nil? - ctx.max_version = max_version unless max_version.nil? - ctx.ciphers = ciphers unless ciphers.nil? + ctx.ciphers = "ADH" client = TCPSocket.new(host, port) ssl = OpenSSL::SSL::SSLSocket.new(client, ctx) ssl.connect diff --git a/src/test/ruby/ssl/test_ssl.rb b/src/test/ruby/ssl/test_ssl.rb index 9088b662..5bac79c0 100644 --- a/src/test/ruby/ssl/test_ssl.rb +++ b/src/test/ruby/ssl/test_ssl.rb @@ -137,6 +137,38 @@ def test_ssl_version_tlsv1_2 end end + # Ruby supports TLSv1.3 already. Java - TLSv1.2. + MAX_SSL_VERSION = if defined? JRUBY_VERSION + "TLSv1.2" + else + "TLSv1.3" + end + [ + [OpenSSL::SSL::TLS1_VERSION, nil, MAX_SSL_VERSION, "(TLSv1,)"], + [OpenSSL::SSL::TLS1_1_VERSION, nil, MAX_SSL_VERSION, "(TLSv1.1,)"], + [OpenSSL::SSL::TLS1_2_VERSION, nil, MAX_SSL_VERSION, "(TLSv1.2,)"], + [nil, OpenSSL::SSL::TLS1_VERSION, "TLSv1", "(,TLSv1)"], + [nil, OpenSSL::SSL::TLS1_1_VERSION, "TLSv1.1", "(,TLSv1.1)"], + [nil, OpenSSL::SSL::TLS1_2_VERSION, "TLSv1.2", "(,TLSv1.2)"], + [OpenSSL::SSL::TLS1_VERSION, OpenSSL::SSL::TLS1_VERSION, "TLSv1", "(TLSv1,TLSv1)"], + [OpenSSL::SSL::TLS1_VERSION, OpenSSL::SSL::TLS1_1_VERSION, "TLSv1.1", "(TLSv1,TLSv1.1)"], + [OpenSSL::SSL::TLS1_VERSION, OpenSSL::SSL::TLS1_2_VERSION, "TLSv1.2", "(TLSv1,TLSv1.2)"] + ].each do |min_version, max_version, expected_version, desc| + define_method("test_ssl_minmax_#{desc}") do + ctx_proc = Proc.new do |ctx| + ctx.min_version = min_version unless min_version.nil? + ctx.max_version = max_version unless max_version.nil? + end + start_server0(PORT, OpenSSL::SSL::VERIFY_NONE, true, :ctx_proc => ctx_proc) do |server, port| + sock = TCPSocket.new("127.0.0.1", port) + ssl = OpenSSL::SSL::SSLSocket.new(sock) + ssl.connect + assert_equal(expected_version, ssl.ssl_version) + ssl.close + end + end + end if RUBY_VERSION > '2.3' + def test_read_nonblock_would_block start_server0(PORT, OpenSSL::SSL::VERIFY_NONE, true) do |server, port| sock = TCPSocket.new("127.0.0.1", port)