Skip to content

Commit 1860394

Browse files
committed
ssl: add SSLContext#min_version= and #max_version=
Add methods that set the minimum and maximum supported protocol versions for the SSL context. If the OpenSSL library supports, use SSL_CTX_set_{min,max}_proto_version() that do the exact thing. Otherwise, simulate by combining SSL_OP_NO_{SSL,TLS}v* flags. The new methods are meant to replace the deprecated #ssl_version= that cannot support multiple protocol versions. SSLContext::DEFAULT_PARAMS is also updated to use the new SSLContext#min_version=.
1 parent 5b753d4 commit 1860394

File tree

4 files changed

+332
-83
lines changed

4 files changed

+332
-83
lines changed

ext/openssl/ossl_ssl.c

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,91 @@ ossl_sslctx_set_ssl_version(VALUE self, VALUE ssl_method)
193193
ossl_raise(rb_eArgError, "unknown SSL method `%"PRIsVALUE"'.", m);
194194
}
195195

196+
static int
197+
parse_proto_version(VALUE str)
198+
{
199+
int i;
200+
static const struct {
201+
const char *name;
202+
int version;
203+
} map[] = {
204+
{ "SSL2", SSL2_VERSION },
205+
{ "SSL3", SSL3_VERSION },
206+
{ "TLS1", TLS1_VERSION },
207+
{ "TLS1_1", TLS1_1_VERSION },
208+
{ "TLS1_2", TLS1_2_VERSION },
209+
#ifdef TLS1_3_VERSION
210+
{ "TLS1_3", TLS1_3_VERSION },
211+
#endif
212+
};
213+
214+
if (NIL_P(str))
215+
return 0;
216+
if (RB_INTEGER_TYPE_P(str))
217+
return NUM2INT(str);
218+
219+
if (SYMBOL_P(str))
220+
str = rb_sym2str(str);
221+
StringValue(str);
222+
for (i = 0; i < numberof(map); i++)
223+
if (!strncmp(map[i].name, RSTRING_PTR(str), RSTRING_LEN(str)))
224+
return map[i].version;
225+
rb_raise(rb_eArgError, "unrecognized version %+"PRIsVALUE, str);
226+
}
227+
228+
/*
229+
* call-seq:
230+
* ctx.set_minmax_proto_version(min, max) -> nil
231+
*
232+
* Sets the minimum and maximum supported protocol versions. See #min_version=
233+
* and #max_version=.
234+
*/
235+
static VALUE
236+
ossl_sslctx_set_minmax_proto_version(VALUE self, VALUE min_v, VALUE max_v)
237+
{
238+
SSL_CTX *ctx;
239+
int min, max;
240+
241+
GetSSLCTX(self, ctx);
242+
min = parse_proto_version(min_v);
243+
max = parse_proto_version(max_v);
244+
245+
#ifdef HAVE_SSL_CTX_SET_MIN_PROTO_VERSION
246+
if (!SSL_CTX_set_min_proto_version(ctx, min))
247+
ossl_raise(eSSLError, "SSL_CTX_set_min_proto_version");
248+
if (!SSL_CTX_set_max_proto_version(ctx, max))
249+
ossl_raise(eSSLError, "SSL_CTX_set_max_proto_version");
250+
#else
251+
{
252+
unsigned long sum = 0, opts = 0;
253+
int i;
254+
static const struct {
255+
int ver;
256+
unsigned long opts;
257+
} options_map[] = {
258+
{ SSL2_VERSION, SSL_OP_NO_SSLv2 },
259+
{ SSL3_VERSION, SSL_OP_NO_SSLv3 },
260+
{ TLS1_VERSION, SSL_OP_NO_TLSv1 },
261+
{ TLS1_1_VERSION, SSL_OP_NO_TLSv1_1 },
262+
{ TLS1_2_VERSION, SSL_OP_NO_TLSv1_2 },
263+
# if defined(TLS1_3_VERSION)
264+
{ TLS1_3_VERSION, SSL_OP_NO_TLSv1_3 },
265+
# endif
266+
};
267+
268+
for (i = 0; i < numberof(options_map); i++) {
269+
sum |= options_map[i].opts;
270+
if (min && min > options_map[i].ver || max && max < options_map[i].ver)
271+
opts |= options_map[i].opts;
272+
}
273+
SSL_CTX_clear_options(ctx, sum);
274+
SSL_CTX_set_options(ctx, opts);
275+
}
276+
#endif
277+
278+
return Qnil;
279+
}
280+
196281
static VALUE
197282
ossl_call_client_cert_cb(VALUE obj)
198283
{
@@ -2548,6 +2633,8 @@ Init_ossl_ssl(void)
25482633
rb_define_alias(cSSLContext, "ssl_timeout", "timeout");
25492634
rb_define_alias(cSSLContext, "ssl_timeout=", "timeout=");
25502635
rb_define_method(cSSLContext, "ssl_version=", ossl_sslctx_set_ssl_version, 1);
2636+
rb_define_private_method(cSSLContext, "set_minmax_proto_version",
2637+
ossl_sslctx_set_minmax_proto_version, 2);
25512638
rb_define_method(cSSLContext, "ciphers", ossl_sslctx_get_ciphers, 0);
25522639
rb_define_method(cSSLContext, "ciphers=", ossl_sslctx_set_ciphers, 1);
25532640
rb_define_method(cSSLContext, "ecdh_curves=", ossl_sslctx_set_ecdh_curves, 1);
@@ -2751,6 +2838,26 @@ Init_ossl_ssl(void)
27512838
rb_define_const(mSSL, "OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG", ULONG2NUM(SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG));
27522839

27532840

2841+
/*
2842+
* SSL/TLS version constants. Used by SSLContext#min_version= and
2843+
* #max_version=
2844+
*/
2845+
/* SSL 2.0 */
2846+
rb_define_const(mSSL, "SSL2_VERSION", INT2NUM(SSL2_VERSION));
2847+
/* SSL 3.0 */
2848+
rb_define_const(mSSL, "SSL3_VERSION", INT2NUM(SSL3_VERSION));
2849+
/* TLS 1.0 */
2850+
rb_define_const(mSSL, "TLS1_VERSION", INT2NUM(TLS1_VERSION));
2851+
/* TLS 1.1 */
2852+
rb_define_const(mSSL, "TLS1_1_VERSION", INT2NUM(TLS1_1_VERSION));
2853+
/* TLS 1.2 */
2854+
rb_define_const(mSSL, "TLS1_2_VERSION", INT2NUM(TLS1_2_VERSION));
2855+
#ifdef TLS1_3_VERSION /* OpenSSL 1.1.1 */
2856+
/* TLS 1.3 */
2857+
rb_define_const(mSSL, "TLS1_3_VERSION", INT2NUM(TLS1_3_VERSION));
2858+
#endif
2859+
2860+
27542861
sym_exception = ID2SYM(rb_intern("exception"));
27552862
sym_wait_readable = ID2SYM(rb_intern("wait_readable"));
27562863
sym_wait_writable = ID2SYM(rb_intern("wait_writable"));

lib/openssl/ssl.rb

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,13 @@ module OpenSSL
1717
module SSL
1818
class SSLContext
1919
DEFAULT_PARAMS = { # :nodoc:
20-
:ssl_version => "SSLv23",
20+
:min_version => OpenSSL::SSL::TLS1_VERSION,
2121
:verify_mode => OpenSSL::SSL::VERIFY_PEER,
2222
:verify_hostname => true,
2323
:options => -> {
2424
opts = OpenSSL::SSL::OP_ALL
2525
opts &= ~OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS
2626
opts |= OpenSSL::SSL::OP_NO_COMPRESSION
27-
opts |= OpenSSL::SSL::OP_NO_SSLv2 | OpenSSL::SSL::OP_NO_SSLv3
2827
opts
2928
}.call
3029
}
@@ -109,11 +108,15 @@ class SSLContext
109108
attr_accessor :servername_cb
110109

111110
# call-seq:
112-
# SSLContext.new => ctx
113-
# SSLContext.new(:TLSv1) => ctx
114-
# SSLContext.new("SSLv23_client") => ctx
111+
# SSLContext.new -> ctx
112+
# SSLContext.new(:TLSv1) -> ctx
113+
# SSLContext.new("SSLv23") -> ctx
115114
#
116-
# You can get a list of valid methods with OpenSSL::SSL::SSLContext::METHODS
115+
# Creates a new SSL context.
116+
#
117+
# If an argument is given, #ssl_version= is called with the value. Note
118+
# that this form is deprecated. New applications should use #min_version=
119+
# and #max_version= as necessary.
117120
def initialize(version = nil)
118121
self.options |= OpenSSL::SSL::OP_ALL
119122
self.ssl_version = version if version
@@ -141,6 +144,43 @@ def set_params(params={})
141144
end
142145
return params
143146
end
147+
148+
# call-seq:
149+
# ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION
150+
# ctx.min_version = :TLS1_2
151+
# ctx.min_version = nil
152+
#
153+
# Sets the lower bound on the supported SSL/TLS protocol version. The
154+
# version may be specified by an integer constant named
155+
# OpenSSL::SSL::*_VERSION, a Symbol, or +nil+ which means "any version".
156+
#
157+
# Be careful that you don't overwrite OpenSSL::SSL::OP_NO_{SSL,TLS}v*
158+
# options by #options= once you have called #min_version= or
159+
# #max_version=.
160+
#
161+
# === Example
162+
# ctx = OpenSSL::SSL::SSLContext.new
163+
# ctx.min_version = OpenSSL::SSL::TLS1_1_VERSION
164+
# ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
165+
#
166+
# sock = OpenSSL::SSL::SSLSocket.new(tcp_sock, ctx)
167+
# sock.connect # Initiates a connection using either TLS 1.1 or TLS 1.2
168+
def min_version=(version)
169+
set_minmax_proto_version(version, @max_proto_version ||= nil)
170+
@min_proto_version = version
171+
end
172+
173+
# call-seq:
174+
# ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
175+
# ctx.max_version = :TLS1_2
176+
# ctx.max_version = nil
177+
#
178+
# Sets the upper bound of the supported SSL/TLS protocol version. See
179+
# #min_version= for the possible values.
180+
def max_version=(version)
181+
set_minmax_proto_version(@min_proto_version ||= nil, version)
182+
@max_proto_version = version
183+
end
144184
end
145185

146186
module SocketForwarder

0 commit comments

Comments
 (0)