summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ext/openssl/extconf.rb1
-rw-r--r--ext/openssl/ossl_ssl.c78
-rw-r--r--test/openssl/test_ssl.rb89
3 files changed, 150 insertions, 18 deletions
diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb
index d014c60306..cc2b1f8ba2 100644
--- a/ext/openssl/extconf.rb
+++ b/ext/openssl/extconf.rb
@@ -169,6 +169,7 @@ have_func("SSL_CTX_set_post_handshake_auth")
# added in 1.1.1
have_func("EVP_PKEY_check")
+have_func("SSL_CTX_set_ciphersuites")
# added in 3.0.0
openssl_3 =
diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c
index 9a0682a7cd..af262d9f56 100644
--- a/ext/openssl/ossl_ssl.c
+++ b/ext/openssl/ossl_ssl.c
@@ -959,27 +959,13 @@ ossl_sslctx_get_ciphers(VALUE self)
return ary;
}
-/*
- * call-seq:
- * ctx.ciphers = "cipher1:cipher2:..."
- * ctx.ciphers = [name, ...]
- * ctx.ciphers = [[name, version, bits, alg_bits], ...]
- *
- * Sets the list of available cipher suites for this context. Note in a server
- * context some ciphers require the appropriate certificates. For example, an
- * RSA cipher suite can only be chosen when an RSA certificate is available.
- */
static VALUE
-ossl_sslctx_set_ciphers(VALUE self, VALUE v)
+build_cipher_string(VALUE v)
{
- SSL_CTX *ctx;
VALUE str, elem;
int i;
- rb_check_frozen(self);
- if (NIL_P(v))
- return v;
- else if (RB_TYPE_P(v, T_ARRAY)) {
+ if (RB_TYPE_P(v, T_ARRAY)) {
str = rb_str_new(0, 0);
for (i = 0; i < RARRAY_LEN(v); i++) {
elem = rb_ary_entry(v, i);
@@ -993,14 +979,67 @@ ossl_sslctx_set_ciphers(VALUE self, VALUE v)
StringValue(str);
}
+ return str;
+}
+
+/*
+ * call-seq:
+ * ctx.ciphers = "cipher1:cipher2:..."
+ * ctx.ciphers = [name, ...]
+ * ctx.ciphers = [[name, version, bits, alg_bits], ...]
+ *
+ * Sets the list of available cipher suites for this context. Note in a server
+ * context some ciphers require the appropriate certificates. For example, an
+ * RSA cipher suite can only be chosen when an RSA certificate is available.
+ */
+static VALUE
+ossl_sslctx_set_ciphers(VALUE self, VALUE v)
+{
+ SSL_CTX *ctx;
+ VALUE str;
+
+ rb_check_frozen(self);
+ if (NIL_P(v))
+ return v;
+
+ str = build_cipher_string(v);
+
GetSSLCTX(self, ctx);
- if (!SSL_CTX_set_cipher_list(ctx, StringValueCStr(str))) {
+ if (!SSL_CTX_set_cipher_list(ctx, StringValueCStr(str)))
ossl_raise(eSSLError, "SSL_CTX_set_cipher_list");
- }
return v;
}
+#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
+/*
+ * call-seq:
+ * ctx.ciphersuites = "cipher1:cipher2:..."
+ * ctx.ciphersuites = [name, ...]
+ * ctx.ciphersuites = [[name, version, bits, alg_bits], ...]
+ *
+ * Sets the list of available TLSv1.3 cipher suites for this context.
+ */
+static VALUE
+ossl_sslctx_set_ciphersuites(VALUE self, VALUE v)
+{
+ SSL_CTX *ctx;
+ VALUE str;
+
+ rb_check_frozen(self);
+ if (NIL_P(v))
+ return v;
+
+ str = build_cipher_string(v);
+
+ GetSSLCTX(self, ctx);
+ if (!SSL_CTX_set_ciphersuites(ctx, StringValueCStr(str)))
+ ossl_raise(eSSLError, "SSL_CTX_set_ciphersuites");
+
+ return v;
+}
+#endif
+
#ifndef OPENSSL_NO_DH
/*
* call-seq:
@@ -2703,6 +2742,9 @@ Init_ossl_ssl(void)
ossl_sslctx_set_minmax_proto_version, 2);
rb_define_method(cSSLContext, "ciphers", ossl_sslctx_get_ciphers, 0);
rb_define_method(cSSLContext, "ciphers=", ossl_sslctx_set_ciphers, 1);
+#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
+ rb_define_method(cSSLContext, "ciphersuites=", ossl_sslctx_set_ciphersuites, 1);
+#endif
#ifndef OPENSSL_NO_DH
rb_define_method(cSSLContext, "tmp_dh=", ossl_sslctx_set_tmp_dh, 1);
#endif
diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb
index 39964bf493..b3d7cba6e6 100644
--- a/test/openssl/test_ssl.rb
+++ b/test/openssl/test_ssl.rb
@@ -1569,6 +1569,95 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
end
end
+ def test_ciphersuites_method_tls_connection
+ ssl_ctx = OpenSSL::SSL::SSLContext.new
+ if !tls13_supported? || !ssl_ctx.respond_to?(:ciphersuites=)
+ pend 'TLS 1.3 not supported'
+ end
+
+ csuite = ['TLS_AES_128_GCM_SHA256', 'TLSv1.3', 128, 128]
+ inputs = [csuite[0], [csuite[0]], [csuite]]
+
+ start_server do |port|
+ inputs.each do |input|
+ cli_ctx = OpenSSL::SSL::SSLContext.new
+ cli_ctx.min_version = cli_ctx.max_version = OpenSSL::SSL::TLS1_3_VERSION
+ cli_ctx.ciphersuites = input
+
+ server_connect(port, cli_ctx) do |ssl|
+ assert_equal('TLSv1.3', ssl.ssl_version)
+ assert_equal(csuite[0], ssl.cipher[0])
+ ssl.puts('abc'); assert_equal("abc\n", ssl.gets)
+ end
+ end
+ end
+ end
+
+ def test_ciphersuites_method_nil_argument
+ ssl_ctx = OpenSSL::SSL::SSLContext.new
+ pend 'ciphersuites= method is missing' unless ssl_ctx.respond_to?(:ciphersuites=)
+
+ assert_nothing_raised { ssl_ctx.ciphersuites = nil }
+ end
+
+ def test_ciphersuites_method_frozen_object
+ ssl_ctx = OpenSSL::SSL::SSLContext.new
+ pend 'ciphersuites= method is missing' unless ssl_ctx.respond_to?(:ciphersuites=)
+
+ ssl_ctx.freeze
+ assert_raise(FrozenError) { ssl_ctx.ciphersuites = 'TLS_AES_256_GCM_SHA384' }
+ end
+
+ def test_ciphersuites_method_bogus_csuite
+ ssl_ctx = OpenSSL::SSL::SSLContext.new
+ pend 'ciphersuites= method is missing' unless ssl_ctx.respond_to?(:ciphersuites=)
+
+ assert_raise_with_message(
+ OpenSSL::SSL::SSLError,
+ /SSL_CTX_set_ciphersuites: no cipher match/i
+ ) { ssl_ctx.ciphersuites = 'BOGUS' }
+ end
+
+ def test_ciphers_method_tls_connection
+ csuite = ['ECDHE-RSA-AES256-GCM-SHA384', 'TLSv1.2', 256, 256]
+ inputs = [csuite[0], [csuite[0]], [csuite]]
+
+ start_server do |port|
+ inputs.each do |input|
+ cli_ctx = OpenSSL::SSL::SSLContext.new
+ cli_ctx.min_version = cli_ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
+ cli_ctx.ciphers = input
+
+ server_connect(port, cli_ctx) do |ssl|
+ assert_equal('TLSv1.2', ssl.ssl_version)
+ assert_equal(csuite[0], ssl.cipher[0])
+ ssl.puts('abc'); assert_equal("abc\n", ssl.gets)
+ end
+ end
+ end
+ end
+
+ def test_ciphers_method_nil_argument
+ ssl_ctx = OpenSSL::SSL::SSLContext.new
+ assert_nothing_raised { ssl_ctx.ciphers = nil }
+ end
+
+ def test_ciphers_method_frozen_object
+ ssl_ctx = OpenSSL::SSL::SSLContext.new
+
+ ssl_ctx.freeze
+ assert_raise(FrozenError) { ssl_ctx.ciphers = 'ECDHE-RSA-AES128-SHA' }
+ end
+
+ def test_ciphers_method_bogus_csuite
+ ssl_ctx = OpenSSL::SSL::SSLContext.new
+
+ assert_raise_with_message(
+ OpenSSL::SSL::SSLError,
+ /SSL_CTX_set_cipher_list: no cipher match/i
+ ) { ssl_ctx.ciphers = 'BOGUS' }
+ end
+
def test_connect_works_when_setting_dh_callback_to_nil
ctx_proc = -> ctx {
ctx.max_version = :TLS1_2