Skip to content

Commit 80cc099

Browse files
committed
ssl: add SSLContext#min_version= and #max_version=
Add methods that sets 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 SSLContext#ssl_version= that cannot support multiple protocol versions. SSLContext::DEFAULT_PARAMS is also updated to use the new SSLContext#min_version=.
1 parent 023696b commit 80cc099

File tree

4 files changed

+323
-81
lines changed

4 files changed

+323
-81
lines changed

ext/openssl/ossl_ssl.c

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,80 @@ 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+
for (i = 0; i < numberof(map); i++) {
222+
if (!strcmp(map[i].name, StringValueCStr(str)))
223+
return map[i].version;
224+
}
225+
rb_raise(rb_eArgError, "unrecognized version %+"PRIsVALUE, str);
226+
}
227+
228+
static VALUE
229+
ossl_sslctx_set_minmax_proto_version(VALUE self, VALUE min_v, VALUE max_v)
230+
{
231+
SSL_CTX *ctx;
232+
int min, max;
233+
#ifndef HAVE_SSL_CTX_SET_MIN_PROTO_VERSION
234+
unsigned long sum = 0, opts = 0;
235+
int i;
236+
static const struct { int ver; unsigned long opts; } options_map[] = {
237+
{ SSL2_VERSION, SSL_OP_NO_SSLv2 },
238+
{ SSL3_VERSION, SSL_OP_NO_SSLv3 },
239+
{ TLS1_VERSION, SSL_OP_NO_TLSv1 },
240+
{ TLS1_1_VERSION, SSL_OP_NO_TLSv1_1 },
241+
{ TLS1_2_VERSION, SSL_OP_NO_TLSv1_2 },
242+
#ifdef TLS1_3_VERSION
243+
{ TLS1_3_VERSION, SSL_OP_NO_TLSv1_3 },
244+
#endif
245+
};
246+
#endif
247+
248+
GetSSLCTX(self, ctx);
249+
min = parse_proto_version(min_v);
250+
max = parse_proto_version(max_v);
251+
252+
#ifdef HAVE_SSL_CTX_SET_MIN_PROTO_VERSION
253+
if (!SSL_CTX_set_min_proto_version(ctx, min))
254+
ossl_raise(eSSLError, "SSL_CTX_set_min_proto_version");
255+
if (!SSL_CTX_set_max_proto_version(ctx, max))
256+
ossl_raise(eSSLError, "SSL_CTX_set_max_proto_version");
257+
#else
258+
for (i = 0; i < numberof(options_map); i++) {
259+
sum |= options_map[i].opts;
260+
if (min && min > options_map[i].ver || max && max < options_map[i].ver)
261+
opts |= options_map[i].opts;
262+
}
263+
SSL_CTX_clear_options(ctx, sum);
264+
SSL_CTX_set_options(ctx, opts);
265+
#endif
266+
267+
return Qnil;
268+
}
269+
196270
static VALUE
197271
ossl_call_client_cert_cb(VALUE obj)
198272
{
@@ -2544,6 +2618,8 @@ Init_ossl_ssl(void)
25442618
rb_define_alias(cSSLContext, "ssl_timeout", "timeout");
25452619
rb_define_alias(cSSLContext, "ssl_timeout=", "timeout=");
25462620
rb_define_method(cSSLContext, "ssl_version=", ossl_sslctx_set_ssl_version, 1);
2621+
rb_define_private_method(cSSLContext, "set_minmax_proto_version",
2622+
ossl_sslctx_set_minmax_proto_version, 2);
25472623
rb_define_method(cSSLContext, "ciphers", ossl_sslctx_get_ciphers, 0);
25482624
rb_define_method(cSSLContext, "ciphers=", ossl_sslctx_set_ciphers, 1);
25492625
rb_define_method(cSSLContext, "ecdh_curves=", ossl_sslctx_set_ecdh_curves, 1);
@@ -2747,6 +2823,26 @@ Init_ossl_ssl(void)
27472823
rb_define_const(mSSL, "OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG", ULONG2NUM(SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG));
27482824

27492825

2826+
/*
2827+
* SSL/TLS version constants. Used by SSLContext#min_version= and
2828+
* #max_version=
2829+
*/
2830+
/* SSL 2 */
2831+
rb_define_const(mSSL, "SSL2_VERSION", INT2NUM(SSL2_VERSION));
2832+
/* SSL 3 */
2833+
rb_define_const(mSSL, "SSL3_VERSION", INT2NUM(SSL3_VERSION));
2834+
/* TLS 1.0 */
2835+
rb_define_const(mSSL, "TLS1_VERSION", INT2NUM(TLS1_VERSION));
2836+
/* TLS 1.1 */
2837+
rb_define_const(mSSL, "TLS1_1_VERSION", INT2NUM(TLS1_1_VERSION));
2838+
/* TLS 1.2 */
2839+
rb_define_const(mSSL, "TLS1_2_VERSION", INT2NUM(TLS1_2_VERSION));
2840+
#ifdef TLS1_3_VERSION /* OpenSSL 1.1.1 */
2841+
/* TLS 1.3 */
2842+
rb_define_const(mSSL, "TLS1_3_VERSION", INT2NUM(TLS1_3_VERSION));
2843+
#endif
2844+
2845+
27502846
sym_exception = ID2SYM(rb_intern("exception"));
27512847
sym_wait_readable = ID2SYM(rb_intern("wait_readable"));
27522848
sym_wait_writable = ID2SYM(rb_intern("wait_writable"));

lib/openssl/ssl.rb

Lines changed: 48 additions & 4 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
}
@@ -124,9 +123,13 @@ class SSLContext
124123
# call-seq:
125124
# SSLContext.new => ctx
126125
# SSLContext.new(:TLSv1) => ctx
127-
# SSLContext.new("SSLv23_client") => ctx
126+
# SSLContext.new("SSLv23") => ctx
128127
#
129-
# You can get a list of valid methods with OpenSSL::SSL::SSLContext::METHODS
128+
# Creates a new SSL context.
129+
#
130+
# If an argument is given, #ssl_version= is called with the value. Note
131+
# that this is deprecated and new applications should use #min_version=
132+
# and #max_version= instead.
130133
def initialize(version = nil)
131134
self.options |= OpenSSL::SSL::OP_ALL
132135
self.ssl_version = version if version
@@ -154,6 +157,47 @@ def set_params(params={})
154157
end
155158
return params
156159
end
160+
161+
# call-seq:
162+
# ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION
163+
# ctx.min_version = :TLS1_2
164+
# ctx.min_version = nil
165+
#
166+
# Sets the lower bound on the supported SSL/TLS protocol version. The
167+
# version may be specified by an integer constant named
168+
# OpenSSL::SSL::*_VERSION, a Symbol, or +nil+ which means "any version".
169+
#
170+
# If an invalid value is given, ArgumentError will be raised.
171+
#
172+
# Be careful that you don't overwrite OpenSSL::SSL::OP_NO_{SSL,TLS}v*
173+
# options by #options= once you have called #min_version= and/or
174+
# #max_version=.
175+
#
176+
# === Example
177+
# ctx = OpenSSL::SSL::SSLContext.new
178+
# ctx.min_version = OpenSSL::SSL::TLS1_1_VERSION
179+
# ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
180+
#
181+
# sock = OpenSSL::SSL::SSLSocket.new(tcp_sock, ctx)
182+
# sock.connect # Will negotiate with either TLS 1.1 or TLS 1.2
183+
def min_version=(version)
184+
set_minmax_proto_version(version, @max_proto_version ||= nil)
185+
@min_proto_version = version
186+
end
187+
188+
# call-seq:
189+
# ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
190+
# ctx.max_version = :TLS1_2
191+
# ctx.max_version = nil
192+
#
193+
# Sets the upper bound of the supported SSL/TLS protocol version. See
194+
# #min_version= for the possible values. The default is +nil+; any
195+
# version which is equal or newer than the version specified by
196+
# #min_version= and supported by the OpenSSL will be accepted.
197+
def max_version=(version)
198+
set_minmax_proto_version(@min_proto_version ||= nil, version)
199+
@max_proto_version = version
200+
end
157201
end
158202

159203
module SocketForwarder

0 commit comments

Comments
 (0)