Skip to content

Commit 12250c7

Browse files
authored
Add 'ciphersuites=' method to allow setting of TLSv1.3 cipher suites along with some unit tests (#493)
Add OpenSSL::SSL::SSLContext#ciphersuites= method along with unit tests.
1 parent ee64d93 commit 12250c7

File tree

3 files changed

+150
-18
lines changed

3 files changed

+150
-18
lines changed

ext/openssl/extconf.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ def find_openssl_library
169169

170170
# added in 1.1.1
171171
have_func("EVP_PKEY_check")
172+
have_func("SSL_CTX_set_ciphersuites")
172173

173174
# added in 3.0.0
174175
have_func("SSL_set0_tmp_dh_pkey")

ext/openssl/ossl_ssl.c

Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -959,27 +959,13 @@ ossl_sslctx_get_ciphers(VALUE self)
959959
return ary;
960960
}
961961

962-
/*
963-
* call-seq:
964-
* ctx.ciphers = "cipher1:cipher2:..."
965-
* ctx.ciphers = [name, ...]
966-
* ctx.ciphers = [[name, version, bits, alg_bits], ...]
967-
*
968-
* Sets the list of available cipher suites for this context. Note in a server
969-
* context some ciphers require the appropriate certificates. For example, an
970-
* RSA cipher suite can only be chosen when an RSA certificate is available.
971-
*/
972962
static VALUE
973-
ossl_sslctx_set_ciphers(VALUE self, VALUE v)
963+
build_cipher_string(VALUE v)
974964
{
975-
SSL_CTX *ctx;
976965
VALUE str, elem;
977966
int i;
978967

979-
rb_check_frozen(self);
980-
if (NIL_P(v))
981-
return v;
982-
else if (RB_TYPE_P(v, T_ARRAY)) {
968+
if (RB_TYPE_P(v, T_ARRAY)) {
983969
str = rb_str_new(0, 0);
984970
for (i = 0; i < RARRAY_LEN(v); i++) {
985971
elem = rb_ary_entry(v, i);
@@ -993,14 +979,67 @@ ossl_sslctx_set_ciphers(VALUE self, VALUE v)
993979
StringValue(str);
994980
}
995981

982+
return str;
983+
}
984+
985+
/*
986+
* call-seq:
987+
* ctx.ciphers = "cipher1:cipher2:..."
988+
* ctx.ciphers = [name, ...]
989+
* ctx.ciphers = [[name, version, bits, alg_bits], ...]
990+
*
991+
* Sets the list of available cipher suites for this context. Note in a server
992+
* context some ciphers require the appropriate certificates. For example, an
993+
* RSA cipher suite can only be chosen when an RSA certificate is available.
994+
*/
995+
static VALUE
996+
ossl_sslctx_set_ciphers(VALUE self, VALUE v)
997+
{
998+
SSL_CTX *ctx;
999+
VALUE str;
1000+
1001+
rb_check_frozen(self);
1002+
if (NIL_P(v))
1003+
return v;
1004+
1005+
str = build_cipher_string(v);
1006+
9961007
GetSSLCTX(self, ctx);
997-
if (!SSL_CTX_set_cipher_list(ctx, StringValueCStr(str))) {
1008+
if (!SSL_CTX_set_cipher_list(ctx, StringValueCStr(str)))
9981009
ossl_raise(eSSLError, "SSL_CTX_set_cipher_list");
999-
}
10001010

10011011
return v;
10021012
}
10031013

1014+
#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
1015+
/*
1016+
* call-seq:
1017+
* ctx.ciphersuites = "cipher1:cipher2:..."
1018+
* ctx.ciphersuites = [name, ...]
1019+
* ctx.ciphersuites = [[name, version, bits, alg_bits], ...]
1020+
*
1021+
* Sets the list of available TLSv1.3 cipher suites for this context.
1022+
*/
1023+
static VALUE
1024+
ossl_sslctx_set_ciphersuites(VALUE self, VALUE v)
1025+
{
1026+
SSL_CTX *ctx;
1027+
VALUE str;
1028+
1029+
rb_check_frozen(self);
1030+
if (NIL_P(v))
1031+
return v;
1032+
1033+
str = build_cipher_string(v);
1034+
1035+
GetSSLCTX(self, ctx);
1036+
if (!SSL_CTX_set_ciphersuites(ctx, StringValueCStr(str)))
1037+
ossl_raise(eSSLError, "SSL_CTX_set_ciphersuites");
1038+
1039+
return v;
1040+
}
1041+
#endif
1042+
10041043
#ifndef OPENSSL_NO_DH
10051044
/*
10061045
* call-seq:
@@ -2703,6 +2742,9 @@ Init_ossl_ssl(void)
27032742
ossl_sslctx_set_minmax_proto_version, 2);
27042743
rb_define_method(cSSLContext, "ciphers", ossl_sslctx_get_ciphers, 0);
27052744
rb_define_method(cSSLContext, "ciphers=", ossl_sslctx_set_ciphers, 1);
2745+
#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
2746+
rb_define_method(cSSLContext, "ciphersuites=", ossl_sslctx_set_ciphersuites, 1);
2747+
#endif
27062748
#ifndef OPENSSL_NO_DH
27072749
rb_define_method(cSSLContext, "tmp_dh=", ossl_sslctx_set_tmp_dh, 1);
27082750
#endif

test/openssl/test_ssl.rb

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1569,6 +1569,95 @@ def test_tmp_dh_callback
15691569
end
15701570
end
15711571

1572+
def test_ciphersuites_method_tls_connection
1573+
ssl_ctx = OpenSSL::SSL::SSLContext.new
1574+
if !tls13_supported? || !ssl_ctx.respond_to?(:ciphersuites=)
1575+
pend 'TLS 1.3 not supported'
1576+
end
1577+
1578+
csuite = ['TLS_AES_128_GCM_SHA256', 'TLSv1.3', 128, 128]
1579+
inputs = [csuite[0], [csuite[0]], [csuite]]
1580+
1581+
start_server do |port|
1582+
inputs.each do |input|
1583+
cli_ctx = OpenSSL::SSL::SSLContext.new
1584+
cli_ctx.min_version = cli_ctx.max_version = OpenSSL::SSL::TLS1_3_VERSION
1585+
cli_ctx.ciphersuites = input
1586+
1587+
server_connect(port, cli_ctx) do |ssl|
1588+
assert_equal('TLSv1.3', ssl.ssl_version)
1589+
assert_equal(csuite[0], ssl.cipher[0])
1590+
ssl.puts('abc'); assert_equal("abc\n", ssl.gets)
1591+
end
1592+
end
1593+
end
1594+
end
1595+
1596+
def test_ciphersuites_method_nil_argument
1597+
ssl_ctx = OpenSSL::SSL::SSLContext.new
1598+
pend 'ciphersuites= method is missing' unless ssl_ctx.respond_to?(:ciphersuites=)
1599+
1600+
assert_nothing_raised { ssl_ctx.ciphersuites = nil }
1601+
end
1602+
1603+
def test_ciphersuites_method_frozen_object
1604+
ssl_ctx = OpenSSL::SSL::SSLContext.new
1605+
pend 'ciphersuites= method is missing' unless ssl_ctx.respond_to?(:ciphersuites=)
1606+
1607+
ssl_ctx.freeze
1608+
assert_raise(FrozenError) { ssl_ctx.ciphersuites = 'TLS_AES_256_GCM_SHA384' }
1609+
end
1610+
1611+
def test_ciphersuites_method_bogus_csuite
1612+
ssl_ctx = OpenSSL::SSL::SSLContext.new
1613+
pend 'ciphersuites= method is missing' unless ssl_ctx.respond_to?(:ciphersuites=)
1614+
1615+
assert_raise_with_message(
1616+
OpenSSL::SSL::SSLError,
1617+
/SSL_CTX_set_ciphersuites: no cipher match/i
1618+
) { ssl_ctx.ciphersuites = 'BOGUS' }
1619+
end
1620+
1621+
def test_ciphers_method_tls_connection
1622+
csuite = ['ECDHE-RSA-AES256-GCM-SHA384', 'TLSv1.2', 256, 256]
1623+
inputs = [csuite[0], [csuite[0]], [csuite]]
1624+
1625+
start_server do |port|
1626+
inputs.each do |input|
1627+
cli_ctx = OpenSSL::SSL::SSLContext.new
1628+
cli_ctx.min_version = cli_ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
1629+
cli_ctx.ciphers = input
1630+
1631+
server_connect(port, cli_ctx) do |ssl|
1632+
assert_equal('TLSv1.2', ssl.ssl_version)
1633+
assert_equal(csuite[0], ssl.cipher[0])
1634+
ssl.puts('abc'); assert_equal("abc\n", ssl.gets)
1635+
end
1636+
end
1637+
end
1638+
end
1639+
1640+
def test_ciphers_method_nil_argument
1641+
ssl_ctx = OpenSSL::SSL::SSLContext.new
1642+
assert_nothing_raised { ssl_ctx.ciphers = nil }
1643+
end
1644+
1645+
def test_ciphers_method_frozen_object
1646+
ssl_ctx = OpenSSL::SSL::SSLContext.new
1647+
1648+
ssl_ctx.freeze
1649+
assert_raise(FrozenError) { ssl_ctx.ciphers = 'ECDHE-RSA-AES128-SHA' }
1650+
end
1651+
1652+
def test_ciphers_method_bogus_csuite
1653+
ssl_ctx = OpenSSL::SSL::SSLContext.new
1654+
1655+
assert_raise_with_message(
1656+
OpenSSL::SSL::SSLError,
1657+
/SSL_CTX_set_cipher_list: no cipher match/i
1658+
) { ssl_ctx.ciphers = 'BOGUS' }
1659+
end
1660+
15721661
def test_connect_works_when_setting_dh_callback_to_nil
15731662
ctx_proc = -> ctx {
15741663
ctx.max_version = :TLS1_2

0 commit comments

Comments
 (0)