Skip to content

Fix OpenSSL::SSL::SSLContext#min_version= failure #215

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 53 additions & 1 deletion src/main/java/org/jruby/ext/openssl/SSLContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -98,6 +99,10 @@ public class SSLContext extends RubyObject {
private static final HashMap<String, String> SSL_VERSION_OSSL2JSSE;
// Mapping table for JSEE's enabled protocols for the algorithm.
private static final Map<String, String[]> ENABLED_PROTOCOLS;
// Mapping table from CRuby parse_proto_version(VALUE str)
private static final Map<String, Integer> PROTO_VERSION_MAP;

private static final Map<String, Integer> JSSE_TO_VERSION;

static {
SSL_VERSION_OSSL2JSSE = new LinkedHashMap<String, String>(20, 1);
Expand Down Expand Up @@ -142,6 +147,22 @@ public class SSLContext extends RubyObject {
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<String, Integer>();
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);

JSSE_TO_VERSION = new HashMap<String, Integer>();
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() {
Expand Down Expand Up @@ -270,6 +291,8 @@ public SSLContext(Ruby runtime, RubyClass type) {
private String protocol = "SSL"; // SSLv23 in OpenSSL by default
private boolean protocolForServer = true;
private boolean protocolForClient = true;
private int minProtocolVersion = 0;
private int maxProtocolVersion = 0;
private PKey t_key;
private X509Cert t_cert;

Expand Down Expand Up @@ -464,7 +487,7 @@ public RubyArray ciphers(final ThreadContext context) {
private RubyArray matchedCiphers(final ThreadContext context) {
final Ruby runtime = context.runtime;
try {
final String[] supported = getSupportedCipherSuites(this.protocol);
final String[] supported = getSupportedCipherSuites(protocol);
final Collection<CipherStrings.Def> cipherDefs =
CipherStrings.matchingCiphers(this.ciphers, supported, false);

Expand Down Expand Up @@ -527,6 +550,31 @@ public IRubyObject set_ssl_version(IRubyObject version) {
return version;
}

@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 RubyFixnum.fix2int(version);
}

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")
Expand Down Expand Up @@ -651,6 +699,10 @@ private String[] getEnabledProtocols(final SSLEngine engine) {
final String[] engineProtocols = engine.getEnabledProtocols();
final List<String> protocols = new ArrayList<String>(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;
Expand Down
22 changes: 22 additions & 0 deletions src/test/integration/ssl_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 6 additions & 0 deletions src/test/ruby/ssl/test_context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ def test_context_set_ssl_version
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 if RUBY_VERSION > '2.3'

def test_context_ciphers
self.class.disable_security_restrictions

Expand Down
3 changes: 2 additions & 1 deletion src/test/ruby/ssl/test_helper.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require File.expand_path('../test_helper', File.dirname(__FILE__))
require 'openssl'

module SSLTestHelper

Expand All @@ -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
Expand Down
32 changes: 32 additions & 0 deletions src/test/ruby/ssl/test_ssl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down