Skip to content

Commit

Permalink
Add 'ciphersuites=' method to allow setting of TLSv1.3 cipher suites …
Browse files Browse the repository at this point in the history
…along with some unit tests (#493)

Add OpenSSL::SSL::SSLContext#ciphersuites= method along with unit tests.
  • Loading branch information
kmdz1 authored Feb 1, 2022
1 parent ee64d93 commit 12250c7
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 18 deletions.
1 change: 1 addition & 0 deletions ext/openssl/extconf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ def find_openssl_library

# added in 1.1.1
have_func("EVP_PKEY_check")
have_func("SSL_CTX_set_ciphersuites")

# added in 3.0.0
have_func("SSL_set0_tmp_dh_pkey")
Expand Down
78 changes: 60 additions & 18 deletions ext/openssl/ossl_ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down
89 changes: 89 additions & 0 deletions test/openssl/test_ssl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1569,6 +1569,95 @@ def test_tmp_dh_callback
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
Expand Down

0 comments on commit 12250c7

Please sign in to comment.