Skip to content

Commit e9a8700

Browse files
authored
Merge pull request #849 from rhenium/ky/ssl-version-min-max-separate
ssl: separate SSLContext#min_version= and #max_version=
2 parents 24a053e + 5766386 commit e9a8700

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)