summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKazuki Yamaguchi <[email protected]>2025-01-30 23:39:51 +0900
committergit <[email protected]>2025-02-03 09:46:03 +0000
commit5a14f536958d20e98c58606bd44bd2c0bed6da4b (patch)
treefd0f6ef9b763fdf00f680603d362c755fdfe3e29
parent8cbff4fe45abbca816867f388c12df19a211e7b9 (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.rb40
-rw-r--r--ext/openssl/ossl_ssl.c146
-rw-r--r--test/openssl/test_ssl.rb44
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