diff options
author | Kazuki Yamaguchi <[email protected]> | 2025-01-30 23:39:51 +0900 |
---|---|---|
committer | git <[email protected]> | 2025-02-03 09:46:03 +0000 |
commit | 5a14f536958d20e98c58606bd44bd2c0bed6da4b (patch) | |
tree | fd0f6ef9b763fdf00f680603d362c755fdfe3e29 | |
parent | 8cbff4fe45abbca816867f388c12df19a211e7b9 (diff) |
[ruby/openssl] 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 https://github.com/ruby/openssl/commit/18603949d316 [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 https://github.com/ruby/openssl/commit/00bec0d905d5 and commit https://github.com/ruby/openssl/commit/77c3db2d6587 [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] https://github.com/ruby/openssl/pull/142
[2] https://github.com/ruby/openssl/pull/767
https://github.com/ruby/openssl/commit/5766386321
-rw-r--r-- | ext/openssl/lib/openssl/ssl.rb | 40 | ||||
-rw-r--r-- | ext/openssl/ossl_ssl.c | 146 | ||||
-rw-r--r-- | test/openssl/test_ssl.rb | 44 |
3 files changed, 134 insertions, 96 deletions
diff --git a/ext/openssl/lib/openssl/ssl.rb b/ext/openssl/lib/openssl/ssl.rb index 1cc16d9b10..a0ad5dc3a6 100644 --- a/ext/openssl/lib/openssl/ssl.rb +++ b/ext/openssl/lib/openssl/ssl.rb @@ -154,43 +154,6 @@ ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== end # call-seq: - # ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION - # ctx.min_version = :TLS1_2 - # ctx.min_version = nil - # - # Sets the lower bound on the supported SSL/TLS protocol version. The - # version may be specified by an integer constant named - # OpenSSL::SSL::*_VERSION, a Symbol, or +nil+ which means "any version". - # - # Be careful that you don't overwrite OpenSSL::SSL::OP_NO_{SSL,TLS}v* - # options by #options= once you have called #min_version= or - # #max_version=. - # - # === Example - # ctx = OpenSSL::SSL::SSLContext.new - # ctx.min_version = OpenSSL::SSL::TLS1_1_VERSION - # ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION - # - # sock = OpenSSL::SSL::SSLSocket.new(tcp_sock, ctx) - # sock.connect # Initiates a connection using either TLS 1.1 or TLS 1.2 - def min_version=(version) - set_minmax_proto_version(version, @max_proto_version ||= nil) - @min_proto_version = version - end - - # call-seq: - # ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION - # ctx.max_version = :TLS1_2 - # ctx.max_version = nil - # - # Sets the upper bound of the supported SSL/TLS protocol version. See - # #min_version= for the possible values. - def max_version=(version) - set_minmax_proto_version(@min_proto_version ||= nil, version) - @max_proto_version = version - end - - # call-seq: # ctx.ssl_version = :TLSv1 # ctx.ssl_version = "SSLv23" # @@ -214,8 +177,7 @@ ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== end version = METHODS_MAP[meth.intern] or raise ArgumentError, "unknown SSL method `%s'" % meth - set_minmax_proto_version(version, version) - @min_proto_version = @max_proto_version = version + self.min_version = self.max_version = version end METHODS_MAP = { diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index af51d23708..cb8e5d7635 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -96,61 +96,6 @@ ossl_sslctx_s_alloc(VALUE klass) return obj; } -static int -parse_proto_version(VALUE str) -{ - int i; - static const struct { - const char *name; - int version; - } map[] = { - { "SSL2", SSL2_VERSION }, - { "SSL3", SSL3_VERSION }, - { "TLS1", TLS1_VERSION }, - { "TLS1_1", TLS1_1_VERSION }, - { "TLS1_2", TLS1_2_VERSION }, - { "TLS1_3", TLS1_3_VERSION }, - }; - - if (NIL_P(str)) - return 0; - if (RB_INTEGER_TYPE_P(str)) - return NUM2INT(str); - - if (SYMBOL_P(str)) - str = rb_sym2str(str); - StringValue(str); - for (i = 0; i < numberof(map); i++) - if (!strncmp(map[i].name, RSTRING_PTR(str), RSTRING_LEN(str))) - return map[i].version; - rb_raise(rb_eArgError, "unrecognized version %+"PRIsVALUE, str); -} - -/* - * call-seq: - * ctx.set_minmax_proto_version(min, max) -> nil - * - * Sets the minimum and maximum supported protocol versions. See #min_version= - * and #max_version=. - */ -static VALUE -ossl_sslctx_set_minmax_proto_version(VALUE self, VALUE min_v, VALUE max_v) -{ - SSL_CTX *ctx; - int min, max; - - GetSSLCTX(self, ctx); - min = parse_proto_version(min_v); - max = parse_proto_version(max_v); - - if (!SSL_CTX_set_min_proto_version(ctx, min)) - ossl_raise(eSSLError, "SSL_CTX_set_min_proto_version"); - if (!SSL_CTX_set_max_proto_version(ctx, max)) - ossl_raise(eSSLError, "SSL_CTX_set_max_proto_version"); - - return Qnil; -} - static VALUE ossl_call_client_cert_cb(VALUE obj) { @@ -915,6 +860,93 @@ ossl_sslctx_setup(VALUE self) return Qtrue; } +static int +parse_proto_version(VALUE str) +{ + int i; + static const struct { + const char *name; + int version; + } map[] = { + { "SSL2", SSL2_VERSION }, + { "SSL3", SSL3_VERSION }, + { "TLS1", TLS1_VERSION }, + { "TLS1_1", TLS1_1_VERSION }, + { "TLS1_2", TLS1_2_VERSION }, + { "TLS1_3", TLS1_3_VERSION }, + }; + + if (NIL_P(str)) + return 0; + if (RB_INTEGER_TYPE_P(str)) + return NUM2INT(str); + + if (SYMBOL_P(str)) + str = rb_sym2str(str); + StringValue(str); + for (i = 0; i < numberof(map); i++) + if (!strncmp(map[i].name, RSTRING_PTR(str), RSTRING_LEN(str))) + return map[i].version; + rb_raise(rb_eArgError, "unrecognized version %+"PRIsVALUE, str); +} + +/* + * call-seq: + * ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION + * ctx.min_version = :TLS1_2 + * ctx.min_version = nil + * + * Sets the lower bound on the supported SSL/TLS protocol version. The + * version may be specified by an integer constant named + * OpenSSL::SSL::*_VERSION, a Symbol, or +nil+ which means "any version". + * + * === Example + * ctx = OpenSSL::SSL::SSLContext.new + * ctx.min_version = OpenSSL::SSL::TLS1_1_VERSION + * ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION + * + * sock = OpenSSL::SSL::SSLSocket.new(tcp_sock, ctx) + * sock.connect # Initiates a connection using either TLS 1.1 or TLS 1.2 + */ +static VALUE +ossl_sslctx_set_min_version(VALUE self, VALUE v) +{ + SSL_CTX *ctx; + int version; + + rb_check_frozen(self); + GetSSLCTX(self, ctx); + version = parse_proto_version(v); + + if (!SSL_CTX_set_min_proto_version(ctx, version)) + ossl_raise(eSSLError, "SSL_CTX_set_min_proto_version"); + return v; +} + +/* + * call-seq: + * ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION + * ctx.max_version = :TLS1_2 + * ctx.max_version = nil + * + * Sets the upper bound of the supported SSL/TLS protocol version. See + * #min_version= for the possible values. + */ +static VALUE +ossl_sslctx_set_max_version(VALUE self, VALUE v) +{ + SSL_CTX *ctx; + int version; + + rb_check_frozen(self); + GetSSLCTX(self, ctx); + version = parse_proto_version(v); + + if (!SSL_CTX_set_max_proto_version(ctx, version)) + ossl_raise(eSSLError, "SSL_CTX_set_max_proto_version"); + return v; +} + static VALUE ossl_ssl_cipher_to_ary(const SSL_CIPHER *cipher) { @@ -2846,8 +2878,8 @@ Init_ossl_ssl(void) rb_define_alias(cSSLContext, "ssl_timeout", "timeout"); rb_define_alias(cSSLContext, "ssl_timeout=", "timeout="); - rb_define_private_method(cSSLContext, "set_minmax_proto_version", - ossl_sslctx_set_minmax_proto_version, 2); + rb_define_method(cSSLContext, "min_version=", ossl_sslctx_set_min_version, 1); + rb_define_method(cSSLContext, "max_version=", ossl_sslctx_set_max_version, 1); rb_define_method(cSSLContext, "ciphers", ossl_sslctx_get_ciphers, 0); rb_define_method(cSSLContext, "ciphers=", ossl_sslctx_set_ciphers, 1); rb_define_method(cSSLContext, "ciphersuites=", ossl_sslctx_set_ciphersuites, 1); diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb index 8a94ec9924..7ee6760bec 100644 --- a/test/openssl/test_ssl.rb +++ b/test/openssl/test_ssl.rb @@ -1375,6 +1375,50 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase } end + def test_minmax_version_system_default + omit "LibreSSL does not support OPENSSL_CONF" if libressl? + + Tempfile.create("openssl.cnf") { |f| + f.puts(<<~EOF) + openssl_conf = default_conf + [default_conf] + ssl_conf = ssl_sect + [ssl_sect] + system_default = ssl_default_sect + [ssl_default_sect] + MaxProtocol = TLSv1.2 + EOF + f.close + + start_server(ignore_listener_error: true) do |port| + assert_separately([{ "OPENSSL_CONF" => f.path }, "-ropenssl", "-", port.to_s], <<~"end;") + sock = TCPSocket.new("127.0.0.1", ARGV[0].to_i) + ctx = OpenSSL::SSL::SSLContext.new + ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION + ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) + ssl.sync_close = true + ssl.connect + assert_equal("TLSv1.2", ssl.ssl_version) + ssl.puts("abc"); assert_equal("abc\n", ssl.gets) + ssl.close + end; + + assert_separately([{ "OPENSSL_CONF" => f.path }, "-ropenssl", "-", port.to_s], <<~"end;") + sock = TCPSocket.new("127.0.0.1", ARGV[0].to_i) + ctx = OpenSSL::SSL::SSLContext.new + ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION + ctx.max_version = nil + ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) + ssl.sync_close = true + ssl.connect + assert_equal("TLSv1.3", ssl.ssl_version) + ssl.puts("abc"); assert_equal("abc\n", ssl.gets) + ssl.close + end; + end + } + end + def test_options_disable_versions # It's recommended to use SSLContext#{min,max}_version= instead in real # applications. The purpose of this test case is to check that SSL options |