diff options
Diffstat (limited to 'test/openssl/test_ssl.rb')
| -rw-r--r-- | test/openssl/test_ssl.rb | 889 |
1 files changed, 659 insertions, 230 deletions
diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb index f011e881e9..e4fd581079 100644 --- a/test/openssl/test_ssl.rb +++ b/test/openssl/test_ssl.rb @@ -15,11 +15,16 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end end + def test_ctx_setup + ctx = OpenSSL::SSL::SSLContext.new + assert_equal true, ctx.setup + assert_predicate ctx, :frozen? + assert_equal nil, ctx.setup + end + def test_ctx_options ctx = OpenSSL::SSL::SSLContext.new - assert (OpenSSL::SSL::OP_ALL & ctx.options) == OpenSSL::SSL::OP_ALL, - "OP_ALL is set by default" ctx.options = 4 assert_equal 4, ctx.options & 4 if ctx.options != 4 @@ -33,6 +38,30 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase assert_equal nil, ctx.setup end + def test_ctx_options_config + omit "LibreSSL and AWS-LC do not support OPENSSL_CONF" if libressl? || aws_lc? + + 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] + Options = -SessionTicket + EOF + f.close + + assert_separately([{ "OPENSSL_CONF" => f.path }, "-ropenssl"], <<~"end;") + ctx = OpenSSL::SSL::SSLContext.new + assert_equal OpenSSL::SSL::OP_NO_TICKET, ctx.options & OpenSSL::SSL::OP_NO_TICKET + ctx.set_params + assert_equal OpenSSL::SSL::OP_NO_TICKET, ctx.options & OpenSSL::SSL::OP_NO_TICKET + end; + } + end + def test_ssl_with_server_cert ctx_proc = -> ctx { ctx.cert = @svr_cert @@ -201,6 +230,34 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end end + def test_extra_chain_cert_auto_chain + start_server { |port| + server_connect(port) { |ssl| + ssl.puts "abc"; assert_equal "abc\n", ssl.gets + assert_equal @svr_cert.to_der, ssl.peer_cert.to_der + assert_equal [@svr_cert], ssl.peer_cert_chain + } + } + + # AWS-LC enables SSL_MODE_NO_AUTO_CHAIN by default + unless aws_lc? + ctx_proc = -> ctx { + # Sanity check: start_server won't set extra_chain_cert + assert_nil ctx.extra_chain_cert + ctx.cert_store = OpenSSL::X509::Store.new.tap { |store| + store.add_cert(@ca_cert) + } + } + start_server(ctx_proc: ctx_proc) { |port| + server_connect(port) { |ssl| + ssl.puts "abc"; assert_equal "abc\n", ssl.gets + assert_equal @svr_cert.to_der, ssl.peer_cert.to_der + assert_equal [@svr_cert, @ca_cert], ssl.peer_cert_chain + } + } + end + end + def test_sysread_and_syswrite start_server { |port| server_connect(port) { |ssl| @@ -213,6 +270,11 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase ssl.syswrite(str) assert_same buf, ssl.sysread(str.size, buf) assert_equal(str, buf) + + obj = Object.new + obj.define_singleton_method(:to_str) { str } + ssl.syswrite(obj) + assert_equal(str, ssl.sysread(str.bytesize)) } } end @@ -226,11 +288,16 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase ssl.syswrite(str) assert_equal(str, ssl.sysread(str.bytesize)) - ssl.timeout = 1 - assert_raise(IO::TimeoutError) {ssl.read(1)} + ssl.timeout = 0.1 + assert_raise(IO::TimeoutError) { ssl.sysread(1) } ssl.syswrite(str) assert_equal(str, ssl.sysread(str.bytesize)) + + buf = "orig".b + assert_raise(IO::TimeoutError) { ssl.sysread(1, buf) } + assert_equal("orig", buf) + assert_nothing_raised { buf.clear } end end end @@ -288,6 +355,22 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end end + def test_sync_close_initialize_opt + start_server do |port| + begin + sock = TCPSocket.new("127.0.0.1", port) + ssl = OpenSSL::SSL::SSLSocket.new(sock, sync_close: true) + assert_equal true, ssl.sync_close + ssl.connect + ssl.puts "abc"; assert_equal "abc\n", ssl.gets + ssl.close + assert_predicate sock, :closed? + ensure + sock&.close + end + end + end + def test_copy_stream start_server do |port| server_connect(port) do |ssl| @@ -314,27 +397,27 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase empty_store = OpenSSL::X509::Store.new # Valid certificate, SSL_VERIFY_PEER + ctx = OpenSSL::SSL::SSLContext.new + ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER + ctx.cert_store = populated_store assert_nothing_raised { - ctx = OpenSSL::SSL::SSLContext.new - ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER - ctx.cert_store = populated_store server_connect(port, ctx) { |ssl| ssl.puts("abc"); ssl.gets } } # Invalid certificate, SSL_VERIFY_NONE + ctx = OpenSSL::SSL::SSLContext.new + ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE + ctx.cert_store = empty_store assert_nothing_raised { - ctx = OpenSSL::SSL::SSLContext.new - ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE - ctx.cert_store = empty_store server_connect(port, ctx) { |ssl| ssl.puts("abc"); ssl.gets } } # Invalid certificate, SSL_VERIFY_PEER - assert_handshake_error { - ctx = OpenSSL::SSL::SSLContext.new - ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER - ctx.cert_store = empty_store - server_connect(port, ctx) { |ssl| ssl.puts("abc"); ssl.gets } + ctx = OpenSSL::SSL::SSLContext.new + ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER + ctx.cert_store = empty_store + assert_raise(OpenSSL::SSL::SSLError) { + server_connect(port, ctx) } } end @@ -362,10 +445,15 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase def test_client_auth_success vflag = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT - start_server(verify_mode: vflag, - ctx_proc: proc { |ctx| - ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION if libressl?(3, 2, 0) - }) { |port| + ctx_proc = proc { |ctx| + store = OpenSSL::X509::Store.new + store.add_cert(@ca_cert) + store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT + ctx.cert_store = store + # LibreSSL doesn't support client_cert_cb in TLS 1.3 + ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION if libressl? + } + start_server(verify_mode: vflag, ctx_proc: ctx_proc) { |port| ctx = OpenSSL::SSL::SSLContext.new ctx.key = @cli_key ctx.cert = @cli_cert @@ -407,9 +495,13 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end def test_client_ca - pend "LibreSSL 3.2 has broken client CA support" if libressl?(3, 2, 0) + pend "LibreSSL doesn't support certificate_authorities" if libressl? ctx_proc = Proc.new do |ctx| + store = OpenSSL::X509::Store.new + store.add_cert(@ca_cert) + store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT + ctx.cert_store = store ctx.client_ca = [@ca_cert] end @@ -475,7 +567,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase ssl.sync_close = true begin assert_raise(OpenSSL::SSL::SSLError){ ssl.connect } - assert_equal(OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN, ssl.verify_result) + assert_equal(OpenSSL::X509::V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, ssl.verify_result) ensure ssl.close end @@ -579,12 +671,9 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase start_server(accept_proc: proc { |server| server_finished = server.finished_message server_peer_finished = server.peer_finished_message - }, ctx_proc: proc { |ctx| - ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION if libressl?(3, 2, 0) }) { |port| ctx = OpenSSL::SSL::SSLContext.new ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE - ctx.max_version = :TLS1_2 if libressl?(3, 2, 0) && !libressl?(3, 3, 0) server_connect(port, ctx) { |ssl| ssl.puts "abc"; ssl.gets @@ -612,8 +701,12 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end def test_post_connect_check_with_anon_ciphers + # DH missing the q value on unknown named parameters is not FIPS-approved. + omit_on_fips + omit "AWS-LC does not support DHE ciphersuites" if aws_lc? + ctx_proc = -> ctx { - ctx.ssl_version = :TLSv1_2 + ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION ctx.ciphers = "aNULL" ctx.tmp_dh = Fixtures.pkey("dh-1") ctx.security_level = 0 @@ -621,7 +714,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase start_server(ctx_proc: ctx_proc) { |port| ctx = OpenSSL::SSL::SSLContext.new - ctx.ssl_version = :TLSv1_2 + ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION ctx.ciphers = "aNULL" ctx.security_level = 0 server_connect(port, ctx) { |ssl| @@ -765,11 +858,6 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase # buzz.example.net, respectively). ... assert_equal(true, OpenSSL::SSL.verify_certificate_identity( create_cert_with_san('DNS:baz*.example.com'), 'baz1.example.com')) - - # LibreSSL 3.5.0+ doesn't support other wildcard certificates - # (it isn't required to, as RFC states MAY, not MUST) - return if libressl?(3, 5, 0) - assert_equal(true, OpenSSL::SSL.verify_certificate_identity( create_cert_with_san('DNS:*baz.example.com'), 'foobaz.example.com')) assert_equal(true, OpenSSL::SSL.verify_certificate_identity( @@ -853,11 +941,17 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end def create_cert_with_san(san) - ef = OpenSSL::X509::ExtensionFactory.new cert = OpenSSL::X509::Certificate.new cert.subject = OpenSSL::X509::Name.parse("/DC=some/DC=site/CN=Some Site") - ext = ef.create_ext('subjectAltName', san) - cert.add_extension(ext) + v = OpenSSL::ASN1::Sequence(san.split(",").map { |item| + type, value = item.split(":", 2) + case type + when "DNS" then OpenSSL::ASN1::IA5String(value, 2, :IMPLICIT) + when "IP" then OpenSSL::ASN1::OctetString(IPAddr.new(value).hton, 7, :IMPLICIT) + else raise "unsupported" + end + }) + cert.add_extension(OpenSSL::X509::Extension.new("subjectAltName", v)) cert end @@ -894,7 +988,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end def test_keylog_cb - pend "Keylog callback is not supported" if !openssl?(1, 1, 1) || libressl? + omit "Keylog callback is not supported" if libressl? prefix = 'CLIENT_RANDOM' context = OpenSSL::SSL::SSLContext.new @@ -914,30 +1008,28 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end end - if tls13_supported? - prefixes = [ - 'SERVER_HANDSHAKE_TRAFFIC_SECRET', - 'EXPORTER_SECRET', - 'SERVER_TRAFFIC_SECRET_0', - 'CLIENT_HANDSHAKE_TRAFFIC_SECRET', - 'CLIENT_TRAFFIC_SECRET_0', - ] - context = OpenSSL::SSL::SSLContext.new - context.min_version = context.max_version = OpenSSL::SSL::TLS1_3_VERSION - cb_called = false - context.keylog_cb = proc do |_sock, line| - cb_called = true - assert_not_nil(prefixes.delete(line.split.first)) - end + prefixes = [ + 'SERVER_HANDSHAKE_TRAFFIC_SECRET', + 'EXPORTER_SECRET', + 'SERVER_TRAFFIC_SECRET_0', + 'CLIENT_HANDSHAKE_TRAFFIC_SECRET', + 'CLIENT_TRAFFIC_SECRET_0', + ] + context = OpenSSL::SSL::SSLContext.new + context.min_version = context.max_version = OpenSSL::SSL::TLS1_3_VERSION + cb_called = false + context.keylog_cb = proc do |_sock, line| + cb_called = true + assert_not_nil(prefixes.delete(line.split.first)) + end - start_server do |port| - server_connect(port, context) do |ssl| - ssl.puts "abc" - assert_equal("abc\n", ssl.gets) - assert_equal(true, cb_called) - end - assert_equal(0, prefixes.size) + start_server do |port| + server_connect(port, context) do |ssl| + ssl.puts "abc" + assert_equal("abc\n", ssl.gets) + assert_equal(true, cb_called) end + assert_equal(0, prefixes.size) end end @@ -988,36 +1080,46 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end end - def test_servername_cb_raises_an_exception_on_unknown_objects - hostname = 'example.org' - - ctx2 = OpenSSL::SSL::SSLContext.new - ctx2.cert = @svr_cert - ctx2.key = @svr_key - ctx2.servername_cb = lambda { |args| Object.new } - + def test_servername_cb_exception sock1, sock2 = socketpair + t = Thread.new { + s1 = OpenSSL::SSL::SSLSocket.new(sock1) + s1.hostname = "localhost" + assert_raise_with_message(OpenSSL::SSL::SSLError, /unrecognized.name/i) { + s1.connect + } + } + + ctx2 = OpenSSL::SSL::SSLContext.new + ctx2.servername_cb = lambda { |args| raise RuntimeError, "foo" } s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2) + assert_raise_with_message(RuntimeError, "foo") { s2.accept } + assert t.join + ensure + sock1.close + sock2.close + t.kill.join + end - ctx1 = OpenSSL::SSL::SSLContext.new + def test_servername_cb_raises_an_exception_on_unknown_objects + sock1, sock2 = socketpair - s1 = OpenSSL::SSL::SSLSocket.new(sock1, ctx1) - s1.hostname = hostname t = Thread.new { - assert_raise(OpenSSL::SSL::SSLError) do - s1.connect - end + s1 = OpenSSL::SSL::SSLSocket.new(sock1) + s1.hostname = "localhost" + assert_raise(OpenSSL::SSL::SSLError) { s1.connect } } - assert_raise(ArgumentError) do - s2.accept - end - + ctx2 = OpenSSL::SSL::SSLContext.new + ctx2.servername_cb = lambda { |args| Object.new } + s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2) + assert_raise(ArgumentError) { s2.accept } assert t.join ensure - sock1.close if sock1 - sock2.close if sock2 + sock1.close + sock2.close + t.kill.join end def test_accept_errors_include_peeraddr @@ -1047,13 +1149,11 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase def test_verify_hostname_on_connect ctx_proc = proc { |ctx| - san = "DNS:a.example.com,DNS:*.b.example.com" - san += ",DNS:c*.example.com,DNS:d.*.example.com" unless libressl?(3, 2, 2) exts = [ ["keyUsage", "keyEncipherment,digitalSignature", true], - ["subjectAltName", san], + ["subjectAltName", "DNS:a.example.com,DNS:*.b.example.com," \ + "DNS:c*.example.com,DNS:d.*.example.com"], ] - ctx.cert = issue_cert(@svr, @svr_key, 4, exts, @ca_cert, @ca_key) ctx.key = @svr_key } @@ -1075,7 +1175,6 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase ["cx.example.com", true], ["d.x.example.com", false], ].each do |name, expected_ok| - next if name.start_with?('cx') if libressl?(3, 2, 2) begin sock = TCPSocket.new("127.0.0.1", port) ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) @@ -1084,7 +1183,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase ssl.connect ssl.puts "abc"; assert_equal "abc\n", ssl.gets else - assert_handshake_error { ssl.connect } + assert_raise(OpenSSL::SSL::SSLError) { ssl.connect } end ensure ssl.close if ssl @@ -1122,7 +1221,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase sock = TCPSocket.new("127.0.0.1", port) ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) ssl.hostname = "b.example.com" - assert_handshake_error { ssl.connect } + assert_raise(OpenSSL::SSL::SSLError) { ssl.connect } assert_equal false, verify_callback_ok assert_equal OpenSSL::X509::V_ERR_HOSTNAME_MISMATCH, verify_callback_err ensure @@ -1135,9 +1234,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase start_server(ignore_listener_error: true) { |port| ctx = OpenSSL::SSL::SSLContext.new ctx.set_params - # OpenSSL <= 1.1.0: "self signed certificate in certificate chain" - # OpenSSL >= 3.0.0: "self-signed certificate in certificate chain" - assert_raise_with_message(OpenSSL::SSL::SSLError, /self.signed/) { + assert_raise_with_message(OpenSSL::SSL::SSLError, /unable to get local issuer certificate/) { server_connect(port, ctx) } } @@ -1179,34 +1276,33 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase OpenSSL::SSL::TLS1_VERSION, OpenSSL::SSL::TLS1_1_VERSION, OpenSSL::SSL::TLS1_2_VERSION, - # OpenSSL 1.1.1 - defined?(OpenSSL::SSL::TLS1_3_VERSION) && OpenSSL::SSL::TLS1_3_VERSION, - ].compact + OpenSSL::SSL::TLS1_3_VERSION, + ] - # Prepare for testing & do sanity check supported = [] - possible_versions.each do |ver| - catch(:unsupported) { - ctx_proc = proc { |ctx| - begin - ctx.min_version = ctx.max_version = ver - rescue ArgumentError, OpenSSL::SSL::SSLError - throw :unsupported - end + ctx_proc = proc { |ctx| + # The default security level is 1 in OpenSSL <= 3.1, 2 in OpenSSL >= 3.2 + # In OpenSSL >= 3.0, TLS 1.1 or older is disabled at level 1 + ctx.security_level = 0 + # Explicitly reset them to avoid influenced by OPENSSL_CONF + ctx.min_version = ctx.max_version = nil + } + start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| + possible_versions.each do |ver| + ctx = OpenSSL::SSL::SSLContext.new + ctx.security_level = 0 + ctx.min_version = ctx.max_version = ver + server_connect(port, ctx) { |ssl| + ssl.puts "abc"; assert_equal "abc\n", ssl.gets } - start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| - begin - server_connect(port) { |ssl| - ssl.puts "abc"; assert_equal "abc\n", ssl.gets - } - rescue OpenSSL::SSL::SSLError, Errno::ECONNRESET - else - supported << ver - end - end - } + supported << ver + rescue OpenSSL::SSL::SSLError, Errno::ECONNRESET + end end - assert_not_empty supported + + # Sanity check: in our test suite we assume these are always supported + assert_include(supported, OpenSSL::SSL::TLS1_2_VERSION) + assert_include(supported, OpenSSL::SSL::TLS1_3_VERSION) supported end @@ -1224,7 +1320,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port| ctx = OpenSSL::SSL::SSLContext.new ctx.set_params(cert_store: store, verify_hostname: false) - assert_handshake_error { server_connect(port, ctx) { } } + assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx) } } end end @@ -1240,18 +1336,20 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase OpenSSL::SSL::TLS1_VERSION => { name: "TLSv1", method: "TLSv1" }, OpenSSL::SSL::TLS1_1_VERSION => { name: "TLSv1.1", method: "TLSv1_1" }, OpenSSL::SSL::TLS1_2_VERSION => { name: "TLSv1.2", method: "TLSv1_2" }, - # OpenSSL 1.1.1 - defined?(OpenSSL::SSL::TLS1_3_VERSION) && OpenSSL::SSL::TLS1_3_VERSION => - { name: "TLSv1.3", method: nil }, + OpenSSL::SSL::TLS1_3_VERSION => { name: "TLSv1.3", method: nil }, } # Server enables a single version supported.each do |ver| - ctx_proc = proc { |ctx| ctx.min_version = ctx.max_version = ver } + ctx_proc = proc { |ctx| + ctx.security_level = 0 + ctx.min_version = ctx.max_version = ver + } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port| supported.each do |cver| # Client enables a single version ctx1 = OpenSSL::SSL::SSLContext.new + ctx1.security_level = 0 ctx1.min_version = ctx1.max_version = cver if ver == cver server_connect(port, ctx1) { |ssl| @@ -1259,13 +1357,14 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase ssl.puts "abc"; assert_equal "abc\n", ssl.gets } else - assert_handshake_error { server_connect(port, ctx1) { } } + assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx1) } end # There is no version-specific SSL methods for TLS 1.3 if cver <= OpenSSL::SSL::TLS1_2_VERSION # Client enables a single version using #ssl_version= ctx2 = OpenSSL::SSL::SSLContext.new + ctx2.security_level = 0 ctx2.ssl_version = vmap[cver][:method] if ver == cver server_connect(port, ctx2) { |ssl| @@ -1273,13 +1372,14 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase ssl.puts "abc"; assert_equal "abc\n", ssl.gets } else - assert_handshake_error { server_connect(port, ctx2) { } } + assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx2) } end end end # Client enables all supported versions ctx3 = OpenSSL::SSL::SSLContext.new + ctx3.security_level = 0 ctx3.min_version = ctx3.max_version = nil server_connect(port, ctx3) { |ssl| assert_equal vmap[ver][:name], ssl.ssl_version @@ -1294,12 +1394,17 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase # Server sets min_version (earliest is disabled) sver = supported[1] - ctx_proc = proc { |ctx| ctx.min_version = sver } + ctx_proc = proc { |ctx| + ctx.security_level = 0 + ctx.min_version = sver + } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port| supported.each do |cver| # Client sets min_version ctx1 = OpenSSL::SSL::SSLContext.new + ctx1.security_level = 0 ctx1.min_version = cver + ctx1.max_version = 0 server_connect(port, ctx1) { |ssl| assert_equal vmap[supported.last][:name], ssl.ssl_version ssl.puts "abc"; assert_equal "abc\n", ssl.gets @@ -1307,6 +1412,8 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase # Client sets max_version ctx2 = OpenSSL::SSL::SSLContext.new + ctx2.security_level = 0 + ctx2.min_version = 0 ctx2.max_version = cver if cver >= sver server_connect(port, ctx2) { |ssl| @@ -1314,14 +1421,18 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase ssl.puts "abc"; assert_equal "abc\n", ssl.gets } else - assert_handshake_error { server_connect(port, ctx2) { } } + assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx2) } end end } # Server sets max_version (latest is disabled) sver = supported[-2] - ctx_proc = proc { |ctx| ctx.max_version = sver } + ctx_proc = proc { |ctx| + ctx.security_level = 0 + ctx.min_version = 0 + ctx.max_version = sver + } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port| supported.each do |cver| # Client sets min_version @@ -1333,11 +1444,13 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase ssl.puts "abc"; assert_equal "abc\n", ssl.gets } else - assert_handshake_error { server_connect(port, ctx1) { } } + assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx1) } end # Client sets max_version ctx2 = OpenSSL::SSL::SSLContext.new + ctx2.security_level = 0 + ctx2.min_version = 0 ctx2.max_version = cver server_connect(port, ctx2) { |ssl| if cver >= sver @@ -1351,15 +1464,106 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase } end + def test_minmax_version_system_default + omit "LibreSSL and AWS-LC do not support OPENSSL_CONF" if libressl? || aws_lc? + + 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_respect_system_default_min + omit "LibreSSL and AWS-LC do not support OPENSSL_CONF" if libressl? || aws_lc? + + 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] + MinProtocol = TLSv1.3 + EOF + f.close + + ctx_proc = proc { |ctx| + ctx.min_version = ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION + } + start_server(ctx_proc: ctx_proc, 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 + ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) + ssl.sync_close = true + assert_raise(OpenSSL::SSL::SSLError) do + ssl.connect + end + ssl.close + end; + end + + ctx_proc = proc { |ctx| + ctx.min_version = ctx.max_version = OpenSSL::SSL::TLS1_3_VERSION + } + start_server(ctx_proc: ctx_proc, 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 + 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 # are properly propagated to OpenSSL library. supported = check_supported_protocol_versions - if !defined?(OpenSSL::SSL::TLS1_3_VERSION) || - !supported.include?(OpenSSL::SSL::TLS1_2_VERSION) || - !supported.include?(OpenSSL::SSL::TLS1_3_VERSION) || - !defined?(OpenSSL::SSL::OP_NO_TLSv1_3) # LibreSSL < 3.4 + if !supported.include?(OpenSSL::SSL::TLS1_2_VERSION) || + !supported.include?(OpenSSL::SSL::TLS1_3_VERSION) pend "this test case requires both TLS 1.2 and TLS 1.3 to be supported " \ "and enabled by default" end @@ -1374,7 +1578,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase # Client only supports TLS 1.2 ctx1 = OpenSSL::SSL::SSLContext.new ctx1.min_version = ctx1.max_version = OpenSSL::SSL::TLS1_2_VERSION - assert_handshake_error { server_connect(port, ctx1) { } } + assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx1) } # Client only supports TLS 1.3 ctx2 = OpenSSL::SSL::SSLContext.new @@ -1390,7 +1594,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase # Client doesn't support TLS 1.2 ctx1 = OpenSSL::SSL::SSLContext.new ctx1.options |= OpenSSL::SSL::OP_NO_TLSv1_2 - assert_handshake_error { server_connect(port, ctx1) { } } + assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx1) } # Client supports TLS 1.2 by default ctx2 = OpenSSL::SSL::SSLContext.new @@ -1414,7 +1618,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase num_handshakes = 0 renegotiation_cb = Proc.new { |ssl| num_handshakes += 1 } ctx_proc = Proc.new { |ctx| ctx.renegotiation_cb = renegotiation_cb } - start_server_version(:SSLv23, ctx_proc) { |port| + start_server(ctx_proc: ctx_proc) { |port| server_connect(port) { |ssl| assert_equal(1, num_handshakes) ssl.puts "abc"; assert_equal "abc\n", ssl.gets @@ -1430,7 +1634,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase } ctx.alpn_protocols = advertised } - start_server_version(:SSLv23, ctx_proc) { |port| + start_server(ctx_proc: ctx_proc) { |port| ctx = OpenSSL::SSL::SSLContext.new ctx.alpn_protocols = advertised server_connect(port, ctx) { |ssl| @@ -1472,9 +1676,10 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase advertised = ["http/1.1", "spdy/2"] ctx_proc = proc { |ctx| ctx.npn_protocols = advertised } - start_server_version(:TLSv1_2, ctx_proc) { |port| + start_server(ctx_proc: ctx_proc) { |port| selector = lambda { |which| ctx = OpenSSL::SSL::SSLContext.new + ctx.max_version = :TLS1_2 ctx.npn_select_cb = -> (protocols) { protocols.send(which) } server_connect(port, ctx) { |ssl| assert_equal(advertised.send(which), ssl.npn_protocol) @@ -1494,9 +1699,10 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase yield "spdy/2" end ctx_proc = Proc.new { |ctx| ctx.npn_protocols = advertised } - start_server_version(:TLSv1_2, ctx_proc) { |port| + start_server(ctx_proc: ctx_proc) { |port| selector = lambda { |selected, which| ctx = OpenSSL::SSL::SSLContext.new + ctx.max_version = :TLS1_2 ctx.npn_select_cb = -> (protocols) { protocols.to_a.send(which) } server_connect(port, ctx) { |ssl| assert_equal(selected, ssl.npn_protocol) @@ -1511,8 +1717,9 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase return unless OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb) ctx_proc = Proc.new { |ctx| ctx.npn_protocols = ["http/1.1"] } - start_server_version(:TLSv1_2, ctx_proc) { |port| + start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port| ctx = OpenSSL::SSL::SSLContext.new + ctx.max_version = :TLS1_2 ctx.npn_select_cb = -> (protocols) { raise RuntimeError.new } assert_raise(RuntimeError) { server_connect(port, ctx) } } @@ -1521,22 +1728,22 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase def test_npn_advertised_protocol_too_long return unless OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb) - ctx_proc = Proc.new { |ctx| ctx.npn_protocols = ["a" * 256] } - start_server_version(:TLSv1_2, ctx_proc) { |port| - ctx = OpenSSL::SSL::SSLContext.new - ctx.npn_select_cb = -> (protocols) { protocols.first } - assert_handshake_error { server_connect(port, ctx) } - } + ctx = OpenSSL::SSL::SSLContext.new + assert_raise(OpenSSL::SSL::SSLError) do + ctx.npn_protocols = ["a" * 256] + ctx.setup + end end def test_npn_selected_protocol_too_long return unless OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb) ctx_proc = Proc.new { |ctx| ctx.npn_protocols = ["http/1.1"] } - start_server_version(:TLSv1_2, ctx_proc) { |port| + start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port| ctx = OpenSSL::SSL::SSLContext.new + ctx.max_version = :TLS1_2 ctx.npn_select_cb = -> (protocols) { "a" * 256 } - assert_handshake_error { server_connect(port, ctx) } + assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx) } } end @@ -1568,14 +1775,17 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end def test_get_ephemeral_key + # kRSA is not FIPS-approved. + omit_on_fips + # kRSA ctx_proc1 = proc { |ctx| - ctx.ssl_version = :TLSv1_2 + ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION ctx.ciphers = "kRSA" } start_server(ctx_proc: ctx_proc1, ignore_listener_error: true) do |port| ctx = OpenSSL::SSL::SSLContext.new - ctx.ssl_version = :TLSv1_2 + ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION ctx.ciphers = "kRSA" begin server_connect(port, ctx) { |ssl| assert_nil ssl.tmp_key } @@ -1586,30 +1796,27 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end # DHE - # TODO: How to test this with TLS 1.3? - ctx_proc2 = proc { |ctx| - ctx.ssl_version = :TLSv1_2 - ctx.ciphers = "EDH" - ctx.tmp_dh = Fixtures.pkey("dh-1") - } - start_server(ctx_proc: ctx_proc2) do |port| - ctx = OpenSSL::SSL::SSLContext.new - ctx.ssl_version = :TLSv1_2 - ctx.ciphers = "EDH" - server_connect(port, ctx) { |ssl| - assert_instance_of OpenSSL::PKey::DH, ssl.tmp_key - } + # OpenSSL 3.0 added support for named FFDHE groups in TLS 1.3 + # LibreSSL does not support named FFDHE groups currently + # AWS-LC does not support DHE ciphersuites + if openssl?(3, 0, 0) + start_server do |port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.groups = "ffdhe3072" + server_connect(port, ctx) { |ssl| + assert_instance_of OpenSSL::PKey::DH, ssl.tmp_key + assert_equal 3072, ssl.tmp_key.p.num_bits + ssl.puts "abc"; assert_equal "abc\n", ssl.gets + } + end end # ECDHE ctx_proc3 = proc { |ctx| - ctx.ciphers = "DEFAULT:!kRSA:!kEDH" - ctx.ecdh_curves = "P-256" + ctx.groups = "P-256" } start_server(ctx_proc: ctx_proc3) do |port| - ctx = OpenSSL::SSL::SSLContext.new - ctx.ciphers = "DEFAULT:!kRSA:!kEDH" - server_connect(port, ctx) { |ssl| + server_connect(port) { |ssl| assert_instance_of OpenSSL::PKey::EC, ssl.tmp_key ssl.puts "abc"; assert_equal "abc\n", ssl.gets } @@ -1618,11 +1825,11 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase def test_fallback_scsv supported = check_supported_protocol_versions - return unless supported.include?(OpenSSL::SSL::TLS1_1_VERSION) && - supported.include?(OpenSSL::SSL::TLS1_2_VERSION) + unless supported.include?(OpenSSL::SSL::TLS1_1_VERSION) + omit "TLS 1.1 support is required to run this test case" + end - pend "Fallback SCSV is not supported" unless \ - OpenSSL::SSL::SSLContext.method_defined?(:enable_fallback_scsv) + omit "Fallback SCSV is not supported" if libressl? start_server do |port| ctx = OpenSSL::SSL::SSLContext.new @@ -1633,11 +1840,15 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end ctx_proc = proc { |ctx| + ctx.security_level = 0 + ctx.min_version = 0 ctx.max_version = OpenSSL::SSL::TLS1_1_VERSION } start_server(ctx_proc: ctx_proc) do |port| ctx = OpenSSL::SSL::SSLContext.new ctx.enable_fallback_scsv + ctx.security_level = 0 + ctx.min_version = 0 ctx.max_version = OpenSSL::SSL::TLS1_1_VERSION # Here is OK too # TLS1.2 not supported, fallback to TLS1.1 and signaling the fallback @@ -1655,19 +1866,24 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase # Otherwise, this test fails when using openssl 1.1.1 (or later) that supports TLS1.3. # TODO: We may need another test for TLS1.3 because it seems to have a different mechanism. ctx1 = OpenSSL::SSL::SSLContext.new + ctx1.security_level = 0 + ctx1.min_version = 0 ctx1.max_version = OpenSSL::SSL::TLS1_2_VERSION s1 = OpenSSL::SSL::SSLSocket.new(sock1, ctx1) ctx2 = OpenSSL::SSL::SSLContext.new ctx2.enable_fallback_scsv + ctx2.security_level = 0 + ctx2.min_version = 0 ctx2.max_version = OpenSSL::SSL::TLS1_1_VERSION s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2) + # AWS-LC has slightly different error messages in all-caps. t = Thread.new { - assert_raise_with_message(OpenSSL::SSL::SSLError, /inappropriate fallback/) { + assert_raise_with_message(OpenSSL::SSL::SSLError, /inappropriate fallback|INAPPROPRIATE_FALLBACK/) { s2.connect } } - assert_raise_with_message(OpenSSL::SSL::SSLError, /inappropriate fallback/) { + assert_raise_with_message(OpenSSL::SSL::SSLError, /inappropriate fallback|INAPPROPRIATE_FALLBACK/) { s1.accept } t.join @@ -1678,6 +1894,10 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end def test_tmp_dh_callback + # DH missing the q value on unknown named parameters is not FIPS-approved. + omit_on_fips + omit "AWS-LC does not support DHE ciphersuites" if aws_lc? + dh = Fixtures.pkey("dh-1") called = false ctx_proc = -> ctx { @@ -1689,7 +1909,9 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase } } start_server(ctx_proc: ctx_proc) do |port| - server_connect(port) { |ssl| + ctx = OpenSSL::SSL::SSLContext.new + ctx.groups = "P-256" # Exclude RFC 7919 groups + server_connect(port, ctx) { |ssl| assert called, "dh callback should be called" assert_equal dh.to_der, ssl.tmp_key.to_der } @@ -1697,11 +1919,6 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase 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]] @@ -1713,11 +1930,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase server_connect(port, cli_ctx) do |ssl| assert_equal('TLSv1.3', ssl.ssl_version) - if libressl?(3, 4, 0) && !libressl?(3, 5, 0) - assert_equal("AEAD-AES128-GCM-SHA256", ssl.cipher[0]) - else - assert_equal(csuite[0], ssl.cipher[0]) - end + assert_equal(csuite[0], ssl.cipher[0]) ssl.puts('abc'); assert_equal("abc\n", ssl.gets) end end @@ -1726,26 +1939,21 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase 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=) - + # AWS-LC has slightly different error messages in all-caps. assert_raise_with_message( OpenSSL::SSL::SSLError, - /SSL_CTX_set_ciphersuites: no cipher match/i + /SSL_CTX_set_ciphersuites: (no cipher match|NO_CIPHER_MATCH)/i ) { ssl_ctx.ciphersuites = 'BOGUS' } end @@ -1781,34 +1989,184 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end def test_ciphers_method_bogus_csuite - omit "Old #{OpenSSL::OPENSSL_LIBRARY_VERSION}" if - year = OpenSSL::OPENSSL_LIBRARY_VERSION[/\A OpenSSL\s+[01]\..*\s\K\d+\z/x] and - year.to_i <= 2018 - ssl_ctx = OpenSSL::SSL::SSLContext.new + # AWS-LC has slightly different error messages in all-caps. assert_raise_with_message( OpenSSL::SSL::SSLError, - /SSL_CTX_set_cipher_list: no cipher match/i + /SSL_CTX_set_cipher_list: (no cipher match|NO_CIPHER_MATCH)/i ) { ssl_ctx.ciphers = 'BOGUS' } end + def test_sigalgs + omit "SSL_CTX_set1_sigalgs_list() not supported" if libressl? + + svr_exts = [ + ["keyUsage", "keyEncipherment,digitalSignature", true], + ["subjectAltName", "DNS:localhost", false], + ] + ecdsa_key = Fixtures.pkey("p256") + ecdsa_cert = issue_cert(@svr, ecdsa_key, 10, svr_exts, @ca_cert, @ca_key) + + ctx_proc = -> ctx { + # Unset values set by start_server + ctx.cert = ctx.key = ctx.extra_chain_cert = nil + ctx.add_certificate(@svr_cert, @svr_key, [@ca_cert]) # RSA + ctx.add_certificate(ecdsa_cert, ecdsa_key, [@ca_cert]) # ECDSA + } + start_server(ctx_proc: ctx_proc) do |port| + ctx1 = OpenSSL::SSL::SSLContext.new + ctx1.sigalgs = "rsa_pss_rsae_sha256" + server_connect(port, ctx1) { |ssl| + assert_kind_of(OpenSSL::PKey::RSA, ssl.peer_cert.public_key) + ssl.puts("abc"); ssl.gets + } + + ctx2 = OpenSSL::SSL::SSLContext.new + ctx2.sigalgs = "ed25519:ecdsa_secp256r1_sha256" + server_connect(port, ctx2) { |ssl| + assert_kind_of(OpenSSL::PKey::EC, ssl.peer_cert.public_key) + ssl.puts("abc"); ssl.gets + } + end + + # Frozen + ssl_ctx = OpenSSL::SSL::SSLContext.new + ssl_ctx.freeze + assert_raise(FrozenError) { ssl_ctx.sigalgs = "ECDSA+SHA256:RSA+SHA256" } + + # Bogus + ssl_ctx = OpenSSL::SSL::SSLContext.new + assert_raise(TypeError) { ssl_ctx.sigalgs = nil } + assert_raise(OpenSSL::SSL::SSLError) { ssl_ctx.sigalgs = "BOGUS" } + end + + def test_client_sigalgs + omit "SSL_CTX_set1_client_sigalgs_list() not supported" if libressl? || aws_lc? + + cli_exts = [ + ["keyUsage", "keyEncipherment,digitalSignature", true], + ["subjectAltName", "DNS:localhost", false], + ] + ecdsa_key = Fixtures.pkey("p256") + ecdsa_cert = issue_cert(@cli, ecdsa_key, 10, cli_exts, @ca_cert, @ca_key) + + ctx_proc = -> ctx { + store = OpenSSL::X509::Store.new + store.add_cert(@ca_cert) + store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT + ctx.cert_store = store + ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT + ctx.client_sigalgs = "ECDSA+SHA256" + } + start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| + ctx1 = OpenSSL::SSL::SSLContext.new + ctx1.add_certificate(@cli_cert, @cli_key) # RSA + assert_handshake_error { + server_connect(port, ctx1) { |ssl| + ssl.puts("abc"); ssl.gets + } + } + + ctx2 = OpenSSL::SSL::SSLContext.new + ctx2.add_certificate(ecdsa_cert, ecdsa_key) # ECDSA + server_connect(port, ctx2) { |ssl| + ssl.puts("abc"); ssl.gets + } + end + end + + def test_get_sigalg + # SSL_get0_signature_name() not supported + # SSL_get0_peer_signature_name() not supported + return unless openssl?(3, 5, 0) + + server_proc = -> (ctx, ssl) { + assert_equal('rsa_pss_rsae_sha256', ssl.sigalg) + assert_nil(ssl.peer_sigalg) + + readwrite_loop(ctx, ssl) + } + start_server(server_proc: server_proc) do |port| + cli_ctx = OpenSSL::SSL::SSLContext.new + server_connect(port, cli_ctx) do |ssl| + assert_nil(ssl.sigalg) + assert_equal('rsa_pss_rsae_sha256', ssl.peer_sigalg) + ssl.puts "abc"; ssl.gets + end + end + end + + def test_pqc_sigalg + # PQC algorithm ML-DSA (FIPS 204) is supported on OpenSSL 3.5 or later. + return unless openssl?(3, 5, 0) + + mldsa = Fixtures.pkey("mldsa65-1") + mldsa_ca_key = Fixtures.pkey("mldsa65-2") + mldsa_ca_cert = issue_cert(@ca, mldsa_ca_key, 1, @ca_exts, nil, nil, + digest: nil) + mldsa_cert = issue_cert(@svr, mldsa, 60, [], mldsa_ca_cert, mldsa_ca_key, + digest: nil) + rsa = Fixtures.pkey("rsa-1") + rsa_cert = issue_cert(@svr, rsa, 61, [], @ca_cert, @ca_key) + ctx_proc = -> ctx { + # Unset values set by start_server + ctx.cert = ctx.key = ctx.extra_chain_cert = nil + ctx.sigalgs = "rsa_pss_rsae_sha256:mldsa65" + ctx.add_certificate(mldsa_cert, mldsa) + ctx.add_certificate(rsa_cert, rsa) + } + + server_proc = -> (ctx, ssl) { + assert_equal('mldsa65', ssl.sigalg) + + readwrite_loop(ctx, ssl) + } + start_server(ctx_proc: ctx_proc, server_proc: server_proc) do |port| + ctx = OpenSSL::SSL::SSLContext.new + # Set signature algorithm because while OpenSSL may use ML-DSA by + # default, the system OpenSSL configuration affects the used signature + # algorithm. + ctx.sigalgs = 'mldsa65' + server_connect(port, ctx) { |ssl| + assert_equal('mldsa65', ssl.peer_sigalg) + ssl.puts "abc"; ssl.gets + } + end + + server_proc = -> (ctx, ssl) { + assert_equal('rsa_pss_rsae_sha256', ssl.sigalg) + + readwrite_loop(ctx, ssl) + } + start_server(ctx_proc: ctx_proc, server_proc: server_proc) do |port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.sigalgs = 'rsa_pss_rsae_sha256' + server_connect(port, ctx) { |ssl| + assert_equal('rsa_pss_rsae_sha256', ssl.peer_sigalg) + ssl.puts "abc"; ssl.gets + } + end + end + def test_connect_works_when_setting_dh_callback_to_nil + omit "AWS-LC does not support DHE ciphersuites" if aws_lc? + ctx_proc = -> ctx { ctx.max_version = :TLS1_2 ctx.ciphers = "DH:!NULL" # use DH ctx.tmp_dh_callback = nil } start_server(ctx_proc: ctx_proc) do |port| - EnvUtil.suppress_warning { # uses default callback - assert_nothing_raised { - server_connect(port) { } - } - } + assert_nothing_raised { server_connect(port) { } } end end def test_tmp_dh + # DH missing the q value on unknown named parameters is not FIPS-approved. + omit_on_fips + omit "AWS-LC does not support DHE ciphersuites" if aws_lc? + dh = Fixtures.pkey("dh-1") ctx_proc = -> ctx { ctx.max_version = :TLS1_2 @@ -1816,92 +2174,133 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase ctx.tmp_dh = dh } start_server(ctx_proc: ctx_proc) do |port| - server_connect(port) { |ssl| + ctx = OpenSSL::SSL::SSLContext.new + ctx.groups = "P-256" # Exclude RFC 7919 groups + server_connect(port, ctx) { |ssl| assert_equal dh.to_der, ssl.tmp_key.to_der } end end - def test_ecdh_curves_tls12 + def test_set_groups_tls12 ctx_proc = -> ctx { # Enable both ECDHE (~ TLS 1.2) cipher suites and TLS 1.3 ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION ctx.ciphers = "kEECDH" - ctx.ecdh_curves = "P-384:P-521" + ctx.groups = "P-384:P-521" } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| # Test 1: Client=P-256:P-384, Server=P-384:P-521 --> P-384 ctx = OpenSSL::SSL::SSLContext.new - ctx.ecdh_curves = "P-256:P-384" + ctx.groups = "P-256:P-384" server_connect(port, ctx) { |ssl| cs = ssl.cipher[0] assert_match (/\AECDH/), cs + # SSL_get0_group_name() is supported on OpenSSL 3.2 or later. + assert_equal "secp384r1", ssl.group if openssl?(3, 2, 0) assert_equal "secp384r1", ssl.tmp_key.group.curve_name ssl.puts "abc"; assert_equal "abc\n", ssl.gets } # Test 2: Client=P-256, Server=P-521:P-384 --> Fail ctx = OpenSSL::SSL::SSLContext.new - ctx.ecdh_curves = "P-256" + ctx.groups = "P-256" assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx) { } } # Test 3: Client=P-521:P-384, Server=P-521:P-384 --> P-521 ctx = OpenSSL::SSL::SSLContext.new - ctx.ecdh_curves = "P-521:P-384" + ctx.groups = "P-521:P-384" server_connect(port, ctx) { |ssl| assert_equal "secp521r1", ssl.tmp_key.group.curve_name ssl.puts "abc"; assert_equal "abc\n", ssl.gets } + + # Test 4: #ecdh_curves= alias + ctx = OpenSSL::SSL::SSLContext.new + ctx.ecdh_curves = "P-256:P-384" + server_connect(port, ctx) { |ssl| + assert_equal "secp384r1", ssl.tmp_key.group.curve_name + } end end - def test_ecdh_curves_tls13 - pend "TLS 1.3 not supported" unless tls13_supported? - + def test_set_groups_tls13 ctx_proc = -> ctx { # Assume TLS 1.3 is enabled and chosen by default - ctx.ecdh_curves = "P-384:P-521" + ctx.groups = "P-384:P-521" } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| ctx = OpenSSL::SSL::SSLContext.new - ctx.ecdh_curves = "P-256:P-384" # disable P-521 + ctx.groups = "P-256:P-384" # disable P-521 server_connect(port, ctx) { |ssl| assert_equal "TLSv1.3", ssl.ssl_version + # SSL_get0_group_name() is supported on OpenSSL 3.2 or later. + assert_equal "secp384r1", ssl.group if openssl?(3, 2, 0) assert_equal "secp384r1", ssl.tmp_key.group.curve_name ssl.puts "abc"; assert_equal "abc\n", ssl.gets } end end + def test_pqc_group + # PQC algorithm ML-KEM (FIPS 203) is supported on OpenSSL 3.5 or later. + return unless openssl?(3, 5, 0) + + [ + 'X25519MLKEM768', + 'SecP256r1MLKEM768', + 'SecP384r1MLKEM1024' + ].each do |group| + ctx_proc = -> ctx { + ctx.groups = group + } + start_server(ctx_proc: ctx_proc) do |port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.groups = group + server_connect(port, ctx) { |ssl| + assert_equal(group, ssl.group) + ssl.puts "abc"; ssl.gets + } + end + end + end + def test_security_level ctx = OpenSSL::SSL::SSLContext.new - begin - ctx.security_level = 1 - rescue NotImplementedError + ctx.security_level = 1 + if aws_lc? # AWS-LC does not support security levels. assert_equal(0, ctx.security_level) return end assert_equal(1, ctx.security_level) - dsa512 = Fixtures.pkey("dsa512") - dsa512_cert = issue_cert(@svr, dsa512, 50, [], @ca_cert, @ca_key) - rsa1024 = Fixtures.pkey("rsa1024") - rsa1024_cert = issue_cert(@svr, rsa1024, 51, [], @ca_cert, @ca_key) + # See SSL_CTX_set_security_level(3). Definitions of security levels may + # change in future OpenSSL versions. As of OpenSSL 1.1.0: + # - Level 1 requires 160-bit ECC keys or 1024-bit RSA keys. + # - Level 2 requires 224-bit ECC keys or 2048-bit RSA keys. + begin + ec112 = OpenSSL::PKey::EC.generate("secp112r1") + ec112_cert = issue_cert(@svr, ec112, 50, [], @ca_cert, @ca_key) + ec192 = OpenSSL::PKey::EC.generate("prime192v1") + ec192_cert = issue_cert(@svr, ec192, 51, [], @ca_cert, @ca_key) + rescue OpenSSL::PKey::PKeyError + # Distro-provided OpenSSL may refuse to generate small keys + return + end assert_raise(OpenSSL::SSL::SSLError) { - # 512 bit DSA key is rejected because it offers < 80 bits of security - ctx.add_certificate(dsa512_cert, dsa512) + ctx.add_certificate(ec112_cert, ec112) } assert_nothing_raised { - ctx.add_certificate(rsa1024_cert, rsa1024) + ctx.add_certificate(ec192_cert, ec192) } ctx.security_level = 2 assert_raise(OpenSSL::SSL::SSLError) { # < 112 bits of security - ctx.add_certificate(rsa1024_cert, rsa1024) + ctx.add_certificate(ec192_cert, ec192) } end @@ -1957,22 +2356,52 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end end - private + # OpenSSL::Buffering requires $/ accessible from non-main Ractors (Ruby 4.0) + # https://bugs.ruby-lang.org/issues/21109 + # + # Hangs on Windows + # https://bugs.ruby-lang.org/issues/21537 + if respond_to?(:ractor) && RUBY_VERSION >= "4.0" && RUBY_PLATFORM !~ /mswin|mingw/ + ractor + def test_ractor_client + start_server { |port| + s = Ractor.new(port, @ca_cert) { |port, ca_cert| + sock = TCPSocket.new("127.0.0.1", port) + ctx = OpenSSL::SSL::SSLContext.new + ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER + ctx.cert_store = OpenSSL::X509::Store.new.tap { |store| + store.add_cert(ca_cert) + } + begin + ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) + ssl.connect + ssl.puts("abc") + ssl.gets + ensure + ssl.close + sock.close + end + }.value + assert_equal("abc\n", s) + } + end - def start_server_version(version, ctx_proc = nil, - server_proc = method(:readwrite_loop), &blk) - ctx_wrap = Proc.new { |ctx| - ctx.ssl_version = version - ctx_proc.call(ctx) if ctx_proc - } - start_server( - ctx_proc: ctx_wrap, - server_proc: server_proc, - ignore_listener_error: true, - &blk - ) + ractor + def test_ractor_set_params + # We cannot actually test default stores in the test suite as it depends + # on the environment, but at least check that it does not raise an + # exception + ok = Ractor.new { + ctx = OpenSSL::SSL::SSLContext.new + ctx.set_params + ctx.cert_store.kind_of?(OpenSSL::X509::Store) + }.value + assert(ok, "ctx.cert_store is an instance of OpenSSL::X509::Store") + end end + private + def server_connect(port, ctx = nil) sock = TCPSocket.new("127.0.0.1", port) ssl = ctx ? OpenSSL::SSL::SSLSocket.new(sock, ctx) : OpenSSL::SSL::SSLSocket.new(sock) |
