Skip to content

Commit 8647fe8

Browse files
Fix OpenSSL::SSL::SSLContext#min_version= failure (#215)
1 parent 73516e1 commit 8647fe8

File tree

5 files changed

+115
-2
lines changed

5 files changed

+115
-2
lines changed

src/main/java/org/jruby/ext/openssl/SSLContext.java

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import org.jruby.Ruby;
5454
import org.jruby.RubyArray;
5555
import org.jruby.RubyClass;
56+
import org.jruby.RubyFixnum;
5657
import org.jruby.RubyHash;
5758
import org.jruby.RubyInteger;
5859
import org.jruby.RubyModule;
@@ -98,6 +99,10 @@ public class SSLContext extends RubyObject {
9899
private static final HashMap<String, String> SSL_VERSION_OSSL2JSSE;
99100
// Mapping table for JSEE's enabled protocols for the algorithm.
100101
private static final Map<String, String[]> ENABLED_PROTOCOLS;
102+
// Mapping table from CRuby parse_proto_version(VALUE str)
103+
private static final Map<String, Integer> PROTO_VERSION_MAP;
104+
105+
private static final Map<String, Integer> JSSE_TO_VERSION;
101106

102107
static {
103108
SSL_VERSION_OSSL2JSSE = new LinkedHashMap<String, String>(20, 1);
@@ -142,6 +147,22 @@ public class SSLContext extends RubyObject {
142147
SSL_VERSION_OSSL2JSSE.put("TLSv1.2", "TLSv1.2"); // just for completeness
143148
SSL_VERSION_OSSL2JSSE.put("TLSv1_2_server", "TLSv1.2");
144149
SSL_VERSION_OSSL2JSSE.put("TLSv1_2_client", "TLSv1.2");
150+
151+
PROTO_VERSION_MAP = new HashMap<String, Integer>();
152+
PROTO_VERSION_MAP.put("SSL2", SSL.SSL2_VERSION);
153+
PROTO_VERSION_MAP.put("SSL3", SSL.SSL3_VERSION);
154+
PROTO_VERSION_MAP.put("TLS1", SSL.TLS1_VERSION);
155+
PROTO_VERSION_MAP.put("TLS1_1", SSL.TLS1_1_VERSION);
156+
PROTO_VERSION_MAP.put("TLS1_2", SSL.TLS1_2_VERSION);
157+
PROTO_VERSION_MAP.put("TLS1_3", SSL.TLS1_3_VERSION);
158+
159+
JSSE_TO_VERSION = new HashMap<String, Integer>();
160+
JSSE_TO_VERSION.put("SSLv2", SSL.SSL2_VERSION);
161+
JSSE_TO_VERSION.put("SSLv3", SSL.SSL3_VERSION);
162+
JSSE_TO_VERSION.put("TLSv1", SSL.TLS1_VERSION);
163+
JSSE_TO_VERSION.put("TLSv1.1", SSL.TLS1_1_VERSION);
164+
JSSE_TO_VERSION.put("TLSv1.2", SSL.TLS1_2_VERSION);
165+
JSSE_TO_VERSION.put("TLSv1.3", SSL.TLS1_3_VERSION);
145166
}
146167

147168
private static ObjectAllocator SSLCONTEXT_ALLOCATOR = new ObjectAllocator() {
@@ -270,6 +291,8 @@ public SSLContext(Ruby runtime, RubyClass type) {
270291
private String protocol = "SSL"; // SSLv23 in OpenSSL by default
271292
private boolean protocolForServer = true;
272293
private boolean protocolForClient = true;
294+
private int minProtocolVersion = 0;
295+
private int maxProtocolVersion = 0;
273296
private PKey t_key;
274297
private X509Cert t_cert;
275298

@@ -464,7 +487,7 @@ public RubyArray ciphers(final ThreadContext context) {
464487
private RubyArray matchedCiphers(final ThreadContext context) {
465488
final Ruby runtime = context.runtime;
466489
try {
467-
final String[] supported = getSupportedCipherSuites(this.protocol);
490+
final String[] supported = getSupportedCipherSuites(protocol);
468491
final Collection<CipherStrings.Def> cipherDefs =
469492
CipherStrings.matchingCiphers(this.ciphers, supported, false);
470493

@@ -527,6 +550,31 @@ public IRubyObject set_ssl_version(IRubyObject version) {
527550
return version;
528551
}
529552

553+
@JRubyMethod(name = "set_minmax_proto_version")
554+
public IRubyObject set_minmax_proto_version(ThreadContext context, IRubyObject minVersion, IRubyObject maxVersion) {
555+
minProtocolVersion = parseProtoVersion(minVersion);
556+
maxProtocolVersion = parseProtoVersion(maxVersion);
557+
558+
return context.nil;
559+
}
560+
561+
private int parseProtoVersion(IRubyObject version) {
562+
if (version.isNil())
563+
return 0;
564+
if (version instanceof RubyFixnum) {
565+
return RubyFixnum.fix2int(version);
566+
}
567+
568+
String string = version.asString().asJavaString();
569+
Integer sslVersion = PROTO_VERSION_MAP.get(string);
570+
571+
if (sslVersion == null) {
572+
throw getRuntime().newArgumentError("unrecognized version \"" + string + "\"");
573+
}
574+
575+
return sslVersion;
576+
}
577+
530578
final String getProtocol() { return this.protocol; }
531579

532580
@JRubyMethod(name = "session_cache_mode")
@@ -651,6 +699,10 @@ private String[] getEnabledProtocols(final SSLEngine engine) {
651699
final String[] engineProtocols = engine.getEnabledProtocols();
652700
final List<String> protocols = new ArrayList<String>(enabledProtocols.length);
653701
for ( final String enabled : enabledProtocols ) {
702+
int protocolVersion = JSSE_TO_VERSION.get(enabled);
703+
if (minProtocolVersion != 0 && protocolVersion < minProtocolVersion) continue;
704+
if (maxProtocolVersion != 0 && protocolVersion > maxProtocolVersion) continue;
705+
654706
if (((options & OP_NO_SSLv2) != 0) && enabled.equals("SSLv2")) continue;
655707
if (((options & OP_NO_SSLv3) != 0) && enabled.equals("SSLv3")) continue;
656708
if (((options & OP_NO_TLSv1) != 0) && enabled.equals("TLSv1")) continue;

src/test/integration/ssl_test.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,26 @@ def test_connect_net_http_1
3838
puts http.get('/')
3939
end
4040

41+
def test_connect_ssl_minmax_version
42+
require 'openssl'
43+
require 'socket'
44+
45+
puts "\n"
46+
puts "------------------------------------------------------------"
47+
puts "-- SSL min/max version ... 'https://google.co.uk'"
48+
puts "------------------------------------------------------------"
49+
50+
ctx = OpenSSL::SSL::SSLContext.new()
51+
ctx.min_version = OpenSSL::SSL::TLS1_VERSION
52+
ctx.max_version = OpenSSL::SSL::TLS1_1_VERSION
53+
client = TCPSocket.new('google.co.uk', 443)
54+
ssl = OpenSSL::SSL::SSLSocket.new(client, ctx)
55+
ssl.sync_close = true
56+
ssl.connect
57+
begin
58+
assert_equal 'TLSv1.1', ssl.ssl_version
59+
ensure
60+
ssl.sysclose
61+
end
62+
end
4163
end

src/test/ruby/ssl/test_context.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ def test_context_set_ssl_version
105105
assert_raises(TypeError) { context.ssl_version = 12 }
106106
end
107107

108+
def test_context_minmax_version
109+
context = OpenSSL::SSL::SSLContext.new
110+
context.min_version = OpenSSL::SSL::TLS1_VERSION
111+
context.max_version = OpenSSL::SSL::TLS1_2_VERSION
112+
end if RUBY_VERSION > '2.3'
113+
108114
def test_context_ciphers
109115
self.class.disable_security_restrictions
110116

src/test/ruby/ssl/test_helper.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require File.expand_path('../test_helper', File.dirname(__FILE__))
2+
require 'openssl'
23

34
module SSLTestHelper
45

@@ -7,7 +8,7 @@ module SSLTestHelper
78
PORT = 20443
89
ITERATIONS = ($0 == __FILE__) ? 100 : 10
910

10-
def setup; require 'openssl'
11+
def setup;
1112

1213
@ca_key = OpenSSL::PKey::RSA.new TEST_KEY_RSA2048
1314
@svr_key = OpenSSL::PKey::RSA.new TEST_KEY_RSA1024

src/test/ruby/ssl/test_ssl.rb

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,38 @@ def test_ssl_version_tlsv1_2
137137
end
138138
end
139139

140+
# Ruby supports TLSv1.3 already. Java - TLSv1.2.
141+
MAX_SSL_VERSION = if defined? JRUBY_VERSION
142+
"TLSv1.2"
143+
else
144+
"TLSv1.3"
145+
end
146+
[
147+
[OpenSSL::SSL::TLS1_VERSION, nil, MAX_SSL_VERSION, "(TLSv1,)"],
148+
[OpenSSL::SSL::TLS1_1_VERSION, nil, MAX_SSL_VERSION, "(TLSv1.1,)"],
149+
[OpenSSL::SSL::TLS1_2_VERSION, nil, MAX_SSL_VERSION, "(TLSv1.2,)"],
150+
[nil, OpenSSL::SSL::TLS1_VERSION, "TLSv1", "(,TLSv1)"],
151+
[nil, OpenSSL::SSL::TLS1_1_VERSION, "TLSv1.1", "(,TLSv1.1)"],
152+
[nil, OpenSSL::SSL::TLS1_2_VERSION, "TLSv1.2", "(,TLSv1.2)"],
153+
[OpenSSL::SSL::TLS1_VERSION, OpenSSL::SSL::TLS1_VERSION, "TLSv1", "(TLSv1,TLSv1)"],
154+
[OpenSSL::SSL::TLS1_VERSION, OpenSSL::SSL::TLS1_1_VERSION, "TLSv1.1", "(TLSv1,TLSv1.1)"],
155+
[OpenSSL::SSL::TLS1_VERSION, OpenSSL::SSL::TLS1_2_VERSION, "TLSv1.2", "(TLSv1,TLSv1.2)"]
156+
].each do |min_version, max_version, expected_version, desc|
157+
define_method("test_ssl_minmax_#{desc}") do
158+
ctx_proc = Proc.new do |ctx|
159+
ctx.min_version = min_version unless min_version.nil?
160+
ctx.max_version = max_version unless max_version.nil?
161+
end
162+
start_server0(PORT, OpenSSL::SSL::VERIFY_NONE, true, :ctx_proc => ctx_proc) do |server, port|
163+
sock = TCPSocket.new("127.0.0.1", port)
164+
ssl = OpenSSL::SSL::SSLSocket.new(sock)
165+
ssl.connect
166+
assert_equal(expected_version, ssl.ssl_version)
167+
ssl.close
168+
end
169+
end
170+
end if RUBY_VERSION > '2.3'
171+
140172
def test_read_nonblock_would_block
141173
start_server0(PORT, OpenSSL::SSL::VERIFY_NONE, true) do |server, port|
142174
sock = TCPSocket.new("127.0.0.1", port)

0 commit comments

Comments
 (0)