Skip to content

Commit 1ff15d2

Browse files
committed
Fix OpenSSL::SSL::SSLContext#min_version= failure
1 parent dd260b6 commit 1ff15d2

File tree

4 files changed

+162
-9
lines changed

4 files changed

+162
-9
lines changed

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

Lines changed: 111 additions & 4 deletions
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;
@@ -96,31 +97,44 @@ public class SSLContext extends RubyObject {
9697

9798
// Mapping table for OpenSSL's SSL_METHOD -> JSSE's SSLContext algorithm.
9899
private static final HashMap<String, String> SSL_VERSION_OSSL2JSSE;
100+
// Inverse mapping (incomplete, does not map 1-1 with SSL_VERSION_OSSL2JSSE)
101+
private static final HashMap<String, String> SSL_VERSION_OSSL2JSSE_INV;
99102
// Mapping table for JSEE's enabled protocols for the algorithm.
100103
private static final Map<String, String[]> ENABLED_PROTOCOLS;
104+
// Mapping table from CRuby parse_proto_version(VALUE str)
105+
private static final Map<String, Integer> PROTO_VERSION_MAP;
106+
// same as METHODS_MAP from ssl.rb
107+
private static final Map<String, Integer> METHODS_MAP;
108+
private static final Map<Integer, String> METHODS_MAP_INV;
101109

102110
static {
103111
SSL_VERSION_OSSL2JSSE = new LinkedHashMap<String, String>(20, 1);
104112
ENABLED_PROTOCOLS = new HashMap<String, String[]>(8, 1);
105113

114+
SSL_VERSION_OSSL2JSSE_INV = new HashMap<String, String>();
115+
106116
SSL_VERSION_OSSL2JSSE.put("TLSv1", "TLSv1");
107117
SSL_VERSION_OSSL2JSSE.put("TLSv1_server", "TLSv1");
108118
SSL_VERSION_OSSL2JSSE.put("TLSv1_client", "TLSv1");
109119
ENABLED_PROTOCOLS.put("TLSv1", new String[] { "TLSv1" });
120+
SSL_VERSION_OSSL2JSSE_INV.put("TLSv1", "TLSv1_1");
110121

111122
SSL_VERSION_OSSL2JSSE.put("SSLv2", "SSLv2");
112123
SSL_VERSION_OSSL2JSSE.put("SSLv2_server", "SSLv2");
113124
SSL_VERSION_OSSL2JSSE.put("SSLv2_client", "SSLv2");
114125
ENABLED_PROTOCOLS.put("SSLv2", new String[] { "SSLv2" });
126+
SSL_VERSION_OSSL2JSSE_INV.put("SSLv2", "SSLv2");
115127

116128
SSL_VERSION_OSSL2JSSE.put("SSLv3", "SSLv3");
117129
SSL_VERSION_OSSL2JSSE.put("SSLv3_server", "SSLv3");
118130
SSL_VERSION_OSSL2JSSE.put("SSLv3_client", "SSLv3");
119131
ENABLED_PROTOCOLS.put("SSLv3", new String[] { "SSLv3" });
132+
SSL_VERSION_OSSL2JSSE_INV.put("SSLv3", "SSLv3");
120133

121134
SSL_VERSION_OSSL2JSSE.put("SSLv23", "SSL");
122135
SSL_VERSION_OSSL2JSSE.put("SSLv23_server", "SSL");
123136
SSL_VERSION_OSSL2JSSE.put("SSLv23_client", "SSL");
137+
SSL_VERSION_OSSL2JSSE_INV.put("SSL", "SSLv23");
124138

125139
ENABLED_PROTOCOLS.put("SSL", new String[] { "SSLv2", "SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2" });
126140

@@ -138,10 +152,34 @@ public class SSLContext extends RubyObject {
138152
SSL_VERSION_OSSL2JSSE.put("TLSv1_1", "TLSv1.1"); // supported on MRI 2.x
139153
SSL_VERSION_OSSL2JSSE.put("TLSv1_2", "TLSv1.2"); // supported on MRI 2.x
140154
ENABLED_PROTOCOLS.put("TLSv1.2", new String[] { "TLSv1.2" });
155+
SSL_VERSION_OSSL2JSSE_INV.put("TLSv1.1", "TLSv1_1");
156+
SSL_VERSION_OSSL2JSSE_INV.put("TLSv1.2", "TLSv1_2");
141157

142158
SSL_VERSION_OSSL2JSSE.put("TLSv1.2", "TLSv1.2"); // just for completeness
143159
SSL_VERSION_OSSL2JSSE.put("TLSv1_2_server", "TLSv1.2");
144160
SSL_VERSION_OSSL2JSSE.put("TLSv1_2_client", "TLSv1.2");
161+
162+
PROTO_VERSION_MAP = new HashMap<String, Integer>();
163+
PROTO_VERSION_MAP.put("SSL2", SSL.SSL2_VERSION);
164+
PROTO_VERSION_MAP.put("SSL3", SSL.SSL3_VERSION);
165+
PROTO_VERSION_MAP.put("TLS1", SSL.TLS1_VERSION);
166+
PROTO_VERSION_MAP.put("TLS1_1", SSL.TLS1_1_VERSION);
167+
PROTO_VERSION_MAP.put("TLS1_2", SSL.TLS1_2_VERSION);
168+
PROTO_VERSION_MAP.put("TLS1_3", SSL.TLS1_3_VERSION);
169+
170+
METHODS_MAP = new HashMap<String, Integer>();
171+
METHODS_MAP.put("SSLv23", 0);
172+
METHODS_MAP.put("SSLv2", SSL.SSL2_VERSION);
173+
METHODS_MAP.put("SSLv3", SSL.SSL3_VERSION);
174+
METHODS_MAP.put("TLSv1", SSL.TLS1_VERSION);
175+
METHODS_MAP.put("TLSv1_1", SSL.TLS1_1_VERSION);
176+
METHODS_MAP.put("TLSv1_2", SSL.TLS1_2_VERSION);
177+
METHODS_MAP.put("TLSv1_3", SSL.TLS1_3_VERSION);
178+
179+
METHODS_MAP_INV = new HashMap<Integer, String>();
180+
for(Map.Entry<String, Integer> e: METHODS_MAP.entrySet()) {
181+
METHODS_MAP_INV.put(e.getValue(), e.getKey());
182+
}
145183
}
146184

147185
private static ObjectAllocator SSLCONTEXT_ALLOCATOR = new ObjectAllocator() {
@@ -267,9 +305,14 @@ public SSLContext(Ruby runtime, RubyClass type) {
267305
}
268306

269307
private String ciphers = CipherStrings.SSL_DEFAULT_CIPHER_LIST;
270-
private String protocol = "SSL"; // SSLv23 in OpenSSL by default
308+
private static final String DEFAULT_PROTOCOL = "SSL"; // SSLv23 in OpenSSL by default
309+
private static final int DEFAULT_PROTOCOL_VERSION = 0;
310+
private String protocol = null;
311+
private Integer protocolVersion = null;
271312
private boolean protocolForServer = true;
272313
private boolean protocolForClient = true;
314+
private int minProtocolVersion = 0;
315+
private int maxProtocolVersion = 0;
273316
private PKey t_key;
274317
private X509Cert t_cert;
275318

@@ -446,6 +489,8 @@ public IRubyObject setup(final ThreadContext context) {
446489
}
447490
*/
448491

492+
setupProtocolVersion(context);
493+
449494
try {
450495
internalContext = createInternalContext(context, cert, key, store, clientCert, extraChainCert, verifyMode, timeout);
451496
}
@@ -456,6 +501,37 @@ public IRubyObject setup(final ThreadContext context) {
456501
return runtime.getTrue();
457502
}
458503

504+
private void setupProtocolVersion(final ThreadContext context) {
505+
if (protocolVersion == null) {
506+
String ssl_version = null;
507+
if (maxProtocolVersion != 0) {
508+
ssl_version = METHODS_MAP_INV.get(maxProtocolVersion);
509+
set_ssl_version(ssl_version);
510+
} else {
511+
ssl_version = getMaxSupportedProtocolVersion();
512+
set_ssl_version(ssl_version);
513+
}
514+
}
515+
516+
if (minProtocolVersion !=0 || maxProtocolVersion !=0 ) {
517+
if (minProtocolVersion != 0 && protocolVersion < minProtocolVersion) {
518+
throw newSSLError(context.runtime, "no protocols available");
519+
}
520+
if (maxProtocolVersion != 0 && protocolVersion > maxProtocolVersion) {
521+
throw newSSLError(context.runtime, "no protocols available");
522+
}
523+
}
524+
}
525+
526+
private String getMaxSupportedProtocolVersion() {
527+
//TODO: probably needs to be computed from
528+
//javax.net.ssl.SSLContext.getSupportedSSLParameters().getProtocols()
529+
//some changes prob. needed in dummySSLEngine and SecurityHelper.getSSLContext
530+
//Hardcoding now.
531+
//Nonetheless, TLS1.3 needs more changes in jruby-openssl
532+
return "TLSv1_2";
533+
}
534+
459535
@JRubyMethod
460536
public RubyArray ciphers(final ThreadContext context) {
461537
return matchedCiphers(context);
@@ -464,7 +540,8 @@ public RubyArray ciphers(final ThreadContext context) {
464540
private RubyArray matchedCiphers(final ThreadContext context) {
465541
final Ruby runtime = context.runtime;
466542
try {
467-
final String[] supported = getSupportedCipherSuites(this.protocol);
543+
setupProtocolVersion(context);
544+
final String[] supported = getSupportedCipherSuites(protocol);
468545
final Collection<CipherStrings.Def> cipherDefs =
469546
CipherStrings.matchingCiphers(this.ciphers, supported, false);
470547

@@ -517,17 +594,47 @@ public IRubyObject set_ssl_version(IRubyObject version) {
517594
} else {
518595
versionStr = version.convertToString().toString();
519596
}
597+
set_ssl_version(versionStr);
598+
return version;
599+
}
600+
601+
private void set_ssl_version(final String versionStr) {
520602
final String protocol = SSL_VERSION_OSSL2JSSE.get(versionStr);
521603
if ( protocol == null ) {
522604
throw getRuntime().newArgumentError("unknown SSL method `"+ versionStr +"'");
523605
}
524606
this.protocol = protocol;
607+
this.protocolVersion = METHODS_MAP.get(versionStr);
525608
protocolForServer = ! versionStr.endsWith("_client");
526609
protocolForClient = ! versionStr.endsWith("_server");
527-
return version;
528610
}
529611

530-
final String getProtocol() { return this.protocol; }
612+
@JRubyMethod(name = "set_minmax_proto_version")
613+
public IRubyObject set_minmax_proto_version(ThreadContext context, IRubyObject minVersion, IRubyObject maxVersion) {
614+
minProtocolVersion = parseProtoVersion(minVersion);
615+
maxProtocolVersion = parseProtoVersion(maxVersion);
616+
617+
return context.nil;
618+
}
619+
620+
private int parseProtoVersion(IRubyObject version) {
621+
if (version.isNil())
622+
return 0;
623+
if (version instanceof RubyFixnum) {
624+
return (int) ((RubyFixnum) version).getLongValue();
625+
}
626+
627+
String string = version.asString().asJavaString();
628+
Integer sslVersion = PROTO_VERSION_MAP.get(string);
629+
630+
if (sslVersion == null) {
631+
throw getRuntime().newArgumentError("unrecognized version \"" + string + "\"");
632+
}
633+
634+
return sslVersion;
635+
}
636+
637+
final String getProtocol() { return this.protocol; }
531638

532639
@JRubyMethod(name = "session_cache_mode")
533640
public IRubyObject session_cache_mode() {

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
@@ -104,6 +104,12 @@ def test_context_set_ssl_version
104104
end
105105
assert_raises(TypeError) { context.ssl_version = 12 }
106106
end
107+
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
107113

108114
def test_context_ciphers
109115
self.class.disable_security_restrictions

src/test/ruby/ssl/test_socket.rb

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,21 @@ def test_connect_nonblock
136136
end
137137
end if RUBY_VERSION > '2.2'
138138

139+
def test_minmax_ssl_version
140+
# WIP: this test should setup it's own server to prove that SSLContext.min_version and SSLContext.max_version work as expected
141+
# However below line is failing with "no cipher match" even on master
142+
# When "no cipher match" is fixed, then this test need cleanup
143+
144+
#ssl_server = server
145+
begin
146+
ssl_client = client(443, host: "google.com", min_version: OpenSSL::SSL::TLS1_VERSION, max_version: OpenSSL::SSL::TLS1_1_VERSION, ciphers: nil)
147+
#ssl_client = client(server_port(ssl_server), min_version: OpenSSL::SSL::TLS1_VERSION, max_version: OpenSSL::SSL::TLS1_1_VERSION)
148+
assert_equal 'TLSv1.1', ssl_client.ssl_version
149+
ensure
150+
ssl_client.sysclose unless ssl_client.nil?
151+
end
152+
end if RUBY_VERSION > '2.3'
153+
139154
def test_inherited_socket; require 'socket'
140155
inheritedSSLSocket = Class.new(OpenSSL::SSL::SSLSocket)
141156

@@ -150,18 +165,21 @@ def test_inherited_socket; require 'socket'
150165

151166
private
152167

153-
def server; require 'socket'
168+
def server(ssl_version: nil); require 'socket'
154169
host = "127.0.0.1"; port = 0
155170
ctx = OpenSSL::SSL::SSLContext.new()
156-
ctx.ciphers = "ADH"
171+
ctx.ssl_version = ssl_version unless ssl_version.nil?
172+
ctx.ciphers = "ADH"
157173
server = TCPServer.new(host, port)
158174
OpenSSL::SSL::SSLServer.new(server, ctx)
159175
end
160176

161-
def client(port); require 'socket'
162-
host = "127.0.0.1"
177+
def client(port, host: "127.0.0.1", min_version: nil, max_version: nil, ciphers: "ADH")
178+
require 'socket'
163179
ctx = OpenSSL::SSL::SSLContext.new()
164-
ctx.ciphers = "ADH"
180+
ctx.min_version = min_version unless min_version.nil?
181+
ctx.max_version = max_version unless max_version.nil?
182+
ctx.ciphers = ciphers unless ciphers.nil?
165183
client = TCPSocket.new(host, port)
166184
ssl = OpenSSL::SSL::SSLSocket.new(client, ctx)
167185
ssl.connect

0 commit comments

Comments
 (0)