Skip to content

Commit 5766386

Browse files
committed
ssl: separate SSLContext#min_version= and #max_version=
Make these methods simple wrappers around SSL_CTX_set_{min,max}_proto_version(). When we introduced these methods in commit 1860394 [1], which went to v2.1.0, we added a private method to SSLContext that set both the minimum and maximum protocol versions at the same time. This was to allow emulating the behavior using SSL options on older OpenSSL versions that lack SSL_CTX_set_{min,max}_proto_version(). Since we no longer support OpenSSL 1.0.2, the related code has already been removed. In OpenSSL 1.1.1 or later, setting the minimum or maximum version to 0 is not equivalent to leaving it unset. Similar to SSL options, which we avoid overwriting as of commit 00bec0d and commit 77c3db2 [2], a system-wide configuration file may define a default protocol version bounds. Setting the minimum version should not unset the maximum version, and vice versa. [1] #142 [2] #767
1 parent 5f0e60e commit 5766386

File tree

3 files changed

+134
-96
lines changed

3 files changed

+134
-96
lines changed

ext/openssl/ossl_ssl.c

+89-57
Original file line numberDiff line numberDiff line change
@@ -96,61 +96,6 @@ ossl_sslctx_s_alloc(VALUE klass)
9696
return obj;
9797
}
9898

99-
static int
100-
parse_proto_version(VALUE str)
101-
{
102-
int i;
103-
static const struct {
104-
const char *name;
105-
int version;
106-
} map[] = {
107-
{ "SSL2", SSL2_VERSION },
108-
{ "SSL3", SSL3_VERSION },
109-
{ "TLS1", TLS1_VERSION },
110-
{ "TLS1_1", TLS1_1_VERSION },
111-
{ "TLS1_2", TLS1_2_VERSION },
112-
{ "TLS1_3", TLS1_3_VERSION },
113-
};
114-
115-
if (NIL_P(str))
116-
return 0;
117-
if (RB_INTEGER_TYPE_P(str))
118-
return NUM2INT(str);
119-
120-
if (SYMBOL_P(str))
121-
str = rb_sym2str(str);
122-
StringValue(str);
123-
for (i = 0; i < numberof(map); i++)
124-
if (!strncmp(map[i].name, RSTRING_PTR(str), RSTRING_LEN(str)))
125-
return map[i].version;
126-
rb_raise(rb_eArgError, "unrecognized version %+"PRIsVALUE, str);
127-
}
128-
129-
/*
130-
* call-seq:
131-
* ctx.set_minmax_proto_version(min, max) -> nil
132-
*
133-
* Sets the minimum and maximum supported protocol versions. See #min_version=
134-
* and #max_version=.
135-
*/
136-
static VALUE
137-
ossl_sslctx_set_minmax_proto_version(VALUE self, VALUE min_v, VALUE max_v)
138-
{
139-
SSL_CTX *ctx;
140-
int min, max;
141-
142-
GetSSLCTX(self, ctx);
143-
min = parse_proto_version(min_v);
144-
max = parse_proto_version(max_v);
145-
146-
if (!SSL_CTX_set_min_proto_version(ctx, min))
147-
ossl_raise(eSSLError, "SSL_CTX_set_min_proto_version");
148-
if (!SSL_CTX_set_max_proto_version(ctx, max))
149-
ossl_raise(eSSLError, "SSL_CTX_set_max_proto_version");
150-
151-
return Qnil;
152-
}
153-
15499
static VALUE
155100
ossl_call_client_cert_cb(VALUE obj)
156101
{
@@ -915,6 +860,93 @@ ossl_sslctx_setup(VALUE self)
915860
return Qtrue;
916861
}
917862

863+
static int
864+
parse_proto_version(VALUE str)
865+
{
866+
int i;
867+
static const struct {
868+
const char *name;
869+
int version;
870+
} map[] = {
871+
{ "SSL2", SSL2_VERSION },
872+
{ "SSL3", SSL3_VERSION },
873+
{ "TLS1", TLS1_VERSION },
874+
{ "TLS1_1", TLS1_1_VERSION },
875+
{ "TLS1_2", TLS1_2_VERSION },
876+
{ "TLS1_3", TLS1_3_VERSION },
877+
};
878+
879+
if (NIL_P(str))
880+
return 0;
881+
if (RB_INTEGER_TYPE_P(str))
882+
return NUM2INT(str);
883+
884+
if (SYMBOL_P(str))
885+
str = rb_sym2str(str);
886+
StringValue(str);
887+
for (i = 0; i < numberof(map); i++)
888+
if (!strncmp(map[i].name, RSTRING_PTR(str), RSTRING_LEN(str)))
889+
return map[i].version;
890+
rb_raise(rb_eArgError, "unrecognized version %+"PRIsVALUE, str);
891+
}
892+
893+
/*
894+
* call-seq:
895+
* ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION
896+
* ctx.min_version = :TLS1_2
897+
* ctx.min_version = nil
898+
*
899+
* Sets the lower bound on the supported SSL/TLS protocol version. The
900+
* version may be specified by an integer constant named
901+
* OpenSSL::SSL::*_VERSION, a Symbol, or +nil+ which means "any version".
902+
*
903+
* === Example
904+
* ctx = OpenSSL::SSL::SSLContext.new
905+
* ctx.min_version = OpenSSL::SSL::TLS1_1_VERSION
906+
* ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
907+
*
908+
* sock = OpenSSL::SSL::SSLSocket.new(tcp_sock, ctx)
909+
* sock.connect # Initiates a connection using either TLS 1.1 or TLS 1.2
910+
*/
911+
static VALUE
912+
ossl_sslctx_set_min_version(VALUE self, VALUE v)
913+
{
914+
SSL_CTX *ctx;
915+
int version;
916+
917+
rb_check_frozen(self);
918+
GetSSLCTX(self, ctx);
919+
version = parse_proto_version(v);
920+
921+
if (!SSL_CTX_set_min_proto_version(ctx, version))
922+
ossl_raise(eSSLError, "SSL_CTX_set_min_proto_version");
923+
return v;
924+
}
925+
926+
/*
927+
* call-seq:
928+
* ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
929+
* ctx.max_version = :TLS1_2
930+
* ctx.max_version = nil
931+
*
932+
* Sets the upper bound of the supported SSL/TLS protocol version. See
933+
* #min_version= for the possible values.
934+
*/
935+
static VALUE
936+
ossl_sslctx_set_max_version(VALUE self, VALUE v)
937+
{
938+
SSL_CTX *ctx;
939+
int version;
940+
941+
rb_check_frozen(self);
942+
GetSSLCTX(self, ctx);
943+
version = parse_proto_version(v);
944+
945+
if (!SSL_CTX_set_max_proto_version(ctx, version))
946+
ossl_raise(eSSLError, "SSL_CTX_set_max_proto_version");
947+
return v;
948+
}
949+
918950
static VALUE
919951
ossl_ssl_cipher_to_ary(const SSL_CIPHER *cipher)
920952
{
@@ -2846,8 +2878,8 @@ Init_ossl_ssl(void)
28462878

28472879
rb_define_alias(cSSLContext, "ssl_timeout", "timeout");
28482880
rb_define_alias(cSSLContext, "ssl_timeout=", "timeout=");
2849-
rb_define_private_method(cSSLContext, "set_minmax_proto_version",
2850-
ossl_sslctx_set_minmax_proto_version, 2);
2881+
rb_define_method(cSSLContext, "min_version=", ossl_sslctx_set_min_version, 1);
2882+
rb_define_method(cSSLContext, "max_version=", ossl_sslctx_set_max_version, 1);
28512883
rb_define_method(cSSLContext, "ciphers", ossl_sslctx_get_ciphers, 0);
28522884
rb_define_method(cSSLContext, "ciphers=", ossl_sslctx_set_ciphers, 1);
28532885
rb_define_method(cSSLContext, "ciphersuites=", ossl_sslctx_set_ciphersuites, 1);

lib/openssl/ssl.rb

+1-39
Original file line numberDiff line numberDiff line change
@@ -153,43 +153,6 @@ def set_params(params={})
153153
return params
154154
end
155155

156-
# call-seq:
157-
# ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION
158-
# ctx.min_version = :TLS1_2
159-
# ctx.min_version = nil
160-
#
161-
# Sets the lower bound on the supported SSL/TLS protocol version. The
162-
# version may be specified by an integer constant named
163-
# OpenSSL::SSL::*_VERSION, a Symbol, or +nil+ which means "any version".
164-
#
165-
# Be careful that you don't overwrite OpenSSL::SSL::OP_NO_{SSL,TLS}v*
166-
# options by #options= once you have called #min_version= or
167-
# #max_version=.
168-
#
169-
# === Example
170-
# ctx = OpenSSL::SSL::SSLContext.new
171-
# ctx.min_version = OpenSSL::SSL::TLS1_1_VERSION
172-
# ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
173-
#
174-
# sock = OpenSSL::SSL::SSLSocket.new(tcp_sock, ctx)
175-
# sock.connect # Initiates a connection using either TLS 1.1 or TLS 1.2
176-
def min_version=(version)
177-
set_minmax_proto_version(version, @max_proto_version ||= nil)
178-
@min_proto_version = version
179-
end
180-
181-
# call-seq:
182-
# ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
183-
# ctx.max_version = :TLS1_2
184-
# ctx.max_version = nil
185-
#
186-
# Sets the upper bound of the supported SSL/TLS protocol version. See
187-
# #min_version= for the possible values.
188-
def max_version=(version)
189-
set_minmax_proto_version(@min_proto_version ||= nil, version)
190-
@max_proto_version = version
191-
end
192-
193156
# call-seq:
194157
# ctx.ssl_version = :TLSv1
195158
# ctx.ssl_version = "SSLv23"
@@ -214,8 +177,7 @@ def ssl_version=(meth)
214177
end
215178
version = METHODS_MAP[meth.intern] or
216179
raise ArgumentError, "unknown SSL method `%s'" % meth
217-
set_minmax_proto_version(version, version)
218-
@min_proto_version = @max_proto_version = version
180+
self.min_version = self.max_version = version
219181
end
220182

221183
METHODS_MAP = {

test/openssl/test_ssl.rb

+44
Original file line numberDiff line numberDiff line change
@@ -1375,6 +1375,50 @@ def test_minmax_version
13751375
}
13761376
end
13771377

1378+
def test_minmax_version_system_default
1379+
omit "LibreSSL does not support OPENSSL_CONF" if libressl?
1380+
1381+
Tempfile.create("openssl.cnf") { |f|
1382+
f.puts(<<~EOF)
1383+
openssl_conf = default_conf
1384+
[default_conf]
1385+
ssl_conf = ssl_sect
1386+
[ssl_sect]
1387+
system_default = ssl_default_sect
1388+
[ssl_default_sect]
1389+
MaxProtocol = TLSv1.2
1390+
EOF
1391+
f.close
1392+
1393+
start_server(ignore_listener_error: true) do |port|
1394+
assert_separately([{ "OPENSSL_CONF" => f.path }, "-ropenssl", "-", port.to_s], <<~"end;")
1395+
sock = TCPSocket.new("127.0.0.1", ARGV[0].to_i)
1396+
ctx = OpenSSL::SSL::SSLContext.new
1397+
ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION
1398+
ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
1399+
ssl.sync_close = true
1400+
ssl.connect
1401+
assert_equal("TLSv1.2", ssl.ssl_version)
1402+
ssl.puts("abc"); assert_equal("abc\n", ssl.gets)
1403+
ssl.close
1404+
end;
1405+
1406+
assert_separately([{ "OPENSSL_CONF" => f.path }, "-ropenssl", "-", port.to_s], <<~"end;")
1407+
sock = TCPSocket.new("127.0.0.1", ARGV[0].to_i)
1408+
ctx = OpenSSL::SSL::SSLContext.new
1409+
ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION
1410+
ctx.max_version = nil
1411+
ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
1412+
ssl.sync_close = true
1413+
ssl.connect
1414+
assert_equal("TLSv1.3", ssl.ssl_version)
1415+
ssl.puts("abc"); assert_equal("abc\n", ssl.gets)
1416+
ssl.close
1417+
end;
1418+
end
1419+
}
1420+
end
1421+
13781422
def test_options_disable_versions
13791423
# It's recommended to use SSLContext#{min,max}_version= instead in real
13801424
# applications. The purpose of this test case is to check that SSL options

0 commit comments

Comments
 (0)