From 297a003651206e354334eb2e01e6f6df38495b62 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Tue, 30 Dec 2025 17:37:03 +0900 Subject: [PATCH 1/5] ssl: move tests for IO-like methods to test_pair.rb r8081 originally intended test_pair.rb for testing methods that behave like IO. Move tests for #{get,read}byte, #sys{read,write}, #close_write, and IO.copy_stream from test_ssl.rb to test_pair.rb. Similarly, move tests for methods that are specific to SSLSocket and not for IO compatibility to test_ssl.rb. --- test/openssl/test_pair.rb | 159 ++++++++++--------------------- test/openssl/test_ssl.rb | 192 ++++++++++++++++++++++---------------- 2 files changed, 160 insertions(+), 191 deletions(-) diff --git a/test/openssl/test_pair.rb b/test/openssl/test_pair.rb index 10942191d..02525c8c3 100644 --- a/test/openssl/test_pair.rb +++ b/test/openssl/test_pair.rb @@ -97,28 +97,36 @@ module OpenSSL::TestPairM def test_getc ssl_pair {|s1, s2| s1 << "a" + s1.close assert_equal(?a, s2.getc) + assert_nil(s2.getc) } end def test_getbyte ssl_pair {|s1, s2| s1 << "a" + s1.close assert_equal(97, s2.getbyte) + assert_nil(s2.getbyte) } end - def test_readbyte + def test_readchar ssl_pair {|s1, s2| s1 << "b" - assert_equal(98, s2.readbyte) + s1.close + assert_equal("b", s2.readchar) + assert_raise(EOFError) { s2.readchar } } end - def test_readbyte_eof + def test_readbyte ssl_pair {|s1, s2| - s2.close - assert_raise(EOFError) { s1.readbyte } + s1 << "b" + s1.close + assert_equal(98, s2.readbyte) + assert_raise(EOFError) { s2.readbyte } } end @@ -216,6 +224,25 @@ def test_multibyte_read_write } end + def test_sysread_and_syswrite + ssl_pair {|s1, s2| + str = "x" * 100 + "\n" + s1.syswrite(str) + newstr = s2.sysread(str.bytesize) + assert_equal(str, newstr) + + buf = String.new + s1.syswrite(str) + assert_same(buf, s2.sysread(str.size, buf)) + assert_equal(str, buf) + + obj = Object.new + obj.define_singleton_method(:to_str) { str } + s1.syswrite(obj) + assert_equal(str, s2.sysread(str.bytesize)) + } + end + def test_read_nonblock ssl_pair {|s1, s2| err = nil @@ -393,116 +420,28 @@ def test_write_multiple_arguments } end - def test_partial_tls_record_read_nonblock + def test_copy_stream ssl_pair { |s1, s2| - # the beginning of a TLS record - s1.io.write("\x17") - # should raise a IO::WaitReadable since a full TLS record is not available - # for reading - assert_raise(IO::WaitReadable) { s2.read_nonblock(1) } + IO.pipe do |r, w| + str = "hello world\n" + w.write(str) + IO.copy_stream(r, s1, str.bytesize) + IO.copy_stream(s2, w, str.bytesize) + assert_equal(str, r.read(str.bytesize)) + end } end - def tcp_pair - host = "127.0.0.1" - serv = TCPServer.new(host, 0) - port = serv.connect_address.ip_port - sock1 = TCPSocket.new(host, port) - sock2 = serv.accept - serv.close - [sock1, sock2] - ensure - serv.close if serv && !serv.closed? - end - - def test_connect_accept_nonblock_no_exception - ctx2 = OpenSSL::SSL::SSLContext.new - ctx2.cert = @svr_cert - ctx2.key = @svr_key - - sock1, sock2 = tcp_pair - - s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2) - accepted = s2.accept_nonblock(exception: false) - assert_equal :wait_readable, accepted - - ctx1 = OpenSSL::SSL::SSLContext.new - s1 = OpenSSL::SSL::SSLSocket.new(sock1, ctx1) - th = Thread.new do - rets = [] - begin - rv = s1.connect_nonblock(exception: false) - rets << rv - case rv - when :wait_writable - IO.select(nil, [s1], nil, 5) - when :wait_readable - IO.select([s1], nil, nil, 5) - end - end until rv == s1 - rets - end - - until th.join(0.01) - accepted = s2.accept_nonblock(exception: false) - assert_include([s2, :wait_readable, :wait_writable ], accepted) - end - - rets = th.value - assert_instance_of Array, rets - rets.each do |rv| - assert_include([s1, :wait_readable, :wait_writable ], rv) - end - ensure - th.join if th - s1.close if s1 - s2.close if s2 - sock1.close if sock1 - sock2.close if sock2 - accepted.close if accepted.respond_to?(:close) - end - - def test_connect_accept_nonblock - ctx = OpenSSL::SSL::SSLContext.new - ctx.cert = @svr_cert - ctx.key = @svr_key - - sock1, sock2 = tcp_pair - - th = Thread.new { - s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx) - 5.times { - begin - break s2.accept_nonblock - rescue IO::WaitReadable - IO.select([s2], nil, nil, 1) - rescue IO::WaitWritable - IO.select(nil, [s2], nil, 1) - end - sleep 0.2 - } - } - - s1 = OpenSSL::SSL::SSLSocket.new(sock1) - 5.times { - begin - break s1.connect_nonblock - rescue IO::WaitReadable - IO.select([s1], nil, nil, 1) - rescue IO::WaitWritable - IO.select(nil, [s1], nil, 1) - end - sleep 0.2 + def test_close_write + ssl_pair { |s1, s2| + message = "abc"*1024 + s1.write(message) + s1.close_write + assert_equal(message, s2.read) + s2.write(message) + s2.close_write + assert_equal(message, s1.read) } - - s2 = th.value - - s1.print "a\ndef" - assert_equal("a\n", s2.gets) - ensure - sock1&.close - sock2&.close - th&.join end end diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb index 914b80623..a4f0d25e8 100644 --- a/test/openssl/test_ssl.rb +++ b/test/openssl/test_ssl.rb @@ -108,6 +108,96 @@ def test_ssl_with_server_cert } end + def test_connect_accept_nonblock_no_exception + ctx2 = OpenSSL::SSL::SSLContext.new + ctx2.cert = @svr_cert + ctx2.key = @svr_key + + sock1, sock2 = socketpair + + s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2) + accepted = s2.accept_nonblock(exception: false) + assert_equal :wait_readable, accepted + + ctx1 = OpenSSL::SSL::SSLContext.new + s1 = OpenSSL::SSL::SSLSocket.new(sock1, ctx1) + th = Thread.new do + rets = [] + begin + rv = s1.connect_nonblock(exception: false) + rets << rv + case rv + when :wait_writable + IO.select(nil, [s1], nil, 5) + when :wait_readable + IO.select([s1], nil, nil, 5) + end + end until rv == s1 + rets + end + + until th.join(0.01) + accepted = s2.accept_nonblock(exception: false) + assert_include([s2, :wait_readable, :wait_writable ], accepted) + end + + rets = th.value + assert_instance_of Array, rets + rets.each do |rv| + assert_include([s1, :wait_readable, :wait_writable ], rv) + end + ensure + th.join if th + s1.close if s1 + s2.close if s2 + sock1.close if sock1 + sock2.close if sock2 + accepted.close if accepted.respond_to?(:close) + end + + def test_connect_accept_nonblock + ctx = OpenSSL::SSL::SSLContext.new + ctx.cert = @svr_cert + ctx.key = @svr_key + + sock1, sock2 = socketpair + + th = Thread.new { + s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx) + 5.times { + begin + break s2.accept_nonblock + rescue IO::WaitReadable + IO.select([s2], nil, nil, 1) + rescue IO::WaitWritable + IO.select(nil, [s2], nil, 1) + end + sleep 0.2 + } + } + + s1 = OpenSSL::SSL::SSLSocket.new(sock1) + 5.times { + begin + break s1.connect_nonblock + rescue IO::WaitReadable + IO.select([s1], nil, nil, 1) + rescue IO::WaitWritable + IO.select(nil, [s1], nil, 1) + end + sleep 0.2 + } + + s2 = th.value + + s1.print "a\ndef" + assert_equal("a\n", s2.gets) + ensure + sock1&.close + sock2&.close + th&.join + end + def test_socket_open start_server { |port| begin @@ -158,30 +248,6 @@ def test_socket_open_with_local_address_port_context } end - def test_socket_close_write - server_proc = proc do |ctx, ssl| - message = ssl.read - ssl.write(message) - ssl.close_write - ensure - ssl.close - end - - start_server(server_proc: server_proc) do |port| - ctx = OpenSSL::SSL::SSLContext.new - ssl = OpenSSL::SSL::SSLSocket.open("127.0.0.1", port, context: ctx) - ssl.sync_close = true - ssl.connect - - message = "abc"*1024 - ssl.write message - ssl.close_write - assert_equal message, ssl.read - ensure - ssl&.close - end - end - def test_add_certificate ctx_proc = -> ctx { # Unset values set by start_server @@ -270,27 +336,6 @@ def test_extra_chain_cert_auto_chain end end - def test_sysread_and_syswrite - start_server { |port| - server_connect(port) { |ssl| - str = +("x" * 100 + "\n") - ssl.syswrite(str) - newstr = ssl.sysread(str.bytesize) - assert_equal(str, newstr) - - buf = String.new - 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 - def test_read_with_timeout omit "does not support timeout" unless IO.method_defined?(:timeout) @@ -314,30 +359,29 @@ def test_read_with_timeout end end - def test_getbyte - start_server { |port| - server_connect(port) { |ssl| - str = +("x" * 100 + "\n") - ssl.syswrite(str) - newstr = str.bytesize.times.map { |i| - ssl.getbyte - }.pack("C*") - assert_equal(str, newstr) - } - } - end + def test_partial_tls_record_read_nonblock + written = Thread::Queue.new + read = Thread::Queue.new + server_proc = -> (ctx, ssl) { + str = ssl.gets + ssl.puts(str) - def test_readbyte - start_server { |port| - server_connect(port) { |ssl| - str = +("x" * 100 + "\n") - ssl.syswrite(str) - newstr = str.bytesize.times.map { |i| - ssl.readbyte - }.pack("C*") - assert_equal(str, newstr) - } + # the beginning of a TLS record + ssl.io.write("\x17") + written << :written + read.pop } + start_server(server_proc: server_proc) do |port| + server_connect(port) do |ssl| + ssl.puts("abc") + assert_equal("abc\n", ssl.gets) + written.pop + # should raise a IO::WaitReadable since a full TLS record is not available + # for reading + assert_raise(IO::WaitReadable) { ssl.send(:sysread_nonblock, 1) } + read << :done + end + end end def test_sync_close @@ -383,20 +427,6 @@ def test_sync_close_initialize_opt end end - def test_copy_stream - start_server do |port| - server_connect(port) do |ssl| - IO.pipe do |r, w| - str = "hello world\n" - w.write(str) - IO.copy_stream(r, ssl, str.bytesize) - IO.copy_stream(ssl, w, str.bytesize) - assert_equal str, r.read(str.bytesize) - end - end - end - end - def test_verify_mode_default ctx = OpenSSL::SSL::SSLContext.new assert_equal OpenSSL::SSL::VERIFY_NONE, ctx.verify_mode From 7063d04b43f68e8e937b4fc44beb533202577c46 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Tue, 30 Dec 2025 17:43:50 +0900 Subject: [PATCH 2/5] ssl: simplify test_pair.rb OpenSSL::SSL::SSLSocket only depends on T_FILE and a small number of methods defined on IO, so the difference between TCPSocket and Socket is not significant for these tests. Test only one of them to reduce the test run time by half. Add a simple client using Socket to test_ssl.rb to keep basic coverage. Also simplify ut_eof.rb to test only one direction, since the direction does not matter after the handshake. --- test/openssl/test_pair.rb | 119 ++++++-------------------------------- test/openssl/test_ssl.rb | 13 +++++ test/openssl/ut_eof.rb | 11 ++++ 3 files changed, 43 insertions(+), 100 deletions(-) diff --git a/test/openssl/test_pair.rb b/test/openssl/test_pair.rb index 02525c8c3..8cb1060e4 100644 --- a/test/openssl/test_pair.rb +++ b/test/openssl/test_pair.rb @@ -2,35 +2,34 @@ require_relative 'utils' require_relative 'ut_eof' -if defined?(OpenSSL::SSL) +return unless defined?(OpenSSL::SSL) -module OpenSSL::SSLPairM - def setup +module OpenSSL::SSLPair + def ssl_pair svr_dn = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=localhost") ee_exts = [ ["keyUsage", "keyEncipherment,digitalSignature", true], ] - @svr_key = OpenSSL::TestUtils::Fixtures.pkey("rsa-1") - @svr_cert = issue_cert(svr_dn, @svr_key, 1, ee_exts, nil, nil) - end + svr_key = OpenSSL::TestUtils::Fixtures.pkey("rsa-1") + svr_cert = issue_cert(svr_dn, svr_key, 1, ee_exts, nil, nil) - def ssl_pair host = "127.0.0.1" - tcps = create_tcp_server(host, 0) - port = tcps.connect_address.ip_port + svr = TCPServer.new(host, 0) + svr.setsockopt(:TCP, :NODELAY, 1) + port = svr.connect_address.ip_port + tcps = nil th = Thread.new { + tcps = svr.accept sctx = OpenSSL::SSL::SSLContext.new - sctx.cert = @svr_cert - sctx.key = @svr_key - sctx.options |= OpenSSL::SSL::OP_NO_COMPRESSION - ssls = OpenSSL::SSL::SSLServer.new(tcps, sctx) - ns = ssls.accept - ssls.close - ns + sctx.add_certificate(svr_cert, svr_key) + ssl = OpenSSL::SSL::SSLSocket.new(tcps, sctx) + ssl.accept + ssl } - tcpc = create_tcp_client(host, port) + tcpc = TCPSocket.new(host, port) + tcpc.setsockopt(:TCP, :NODELAY, 1) c = OpenSSL::SSL::SSLSocket.new(tcpc) c.connect s = th.value @@ -39,57 +38,7 @@ def ssl_pair ensure tcpc&.close tcps&.close - s&.close - end -end - -module OpenSSL::SSLPair - include OpenSSL::SSLPairM - - def create_tcp_server(host, port) - TCPServer.new(host, port) - end - - def create_tcp_client(host, port) - TCPSocket.new(host, port) - end -end - -module OpenSSL::SSLPairLowlevelSocket - include OpenSSL::SSLPairM - - def create_tcp_server(host, port) - Addrinfo.tcp(host, port).listen - end - - def create_tcp_client(host, port) - Addrinfo.tcp(host, port).connect - end -end - -module OpenSSL::TestEOF1M - def open_file(content) - ssl_pair { |s1, s2| - begin - th = Thread.new { s2 << content; s2.close } - yield s1 - ensure - th&.join - end - } - end -end - -module OpenSSL::TestEOF2M - def open_file(content) - ssl_pair { |s1, s2| - begin - th = Thread.new { s1 << content; s1.close } - yield s2 - ensure - th&.join - end - } + svr&.close end end @@ -445,38 +394,8 @@ def test_close_write end end -class OpenSSL::TestEOF1 < OpenSSL::TestCase - include OpenSSL::TestEOF - include OpenSSL::SSLPair - include OpenSSL::TestEOF1M -end - -class OpenSSL::TestEOF1LowlevelSocket < OpenSSL::TestCase - include OpenSSL::TestEOF - include OpenSSL::SSLPairLowlevelSocket - include OpenSSL::TestEOF1M -end - -class OpenSSL::TestEOF2 < OpenSSL::TestCase - include OpenSSL::TestEOF - include OpenSSL::SSLPair - include OpenSSL::TestEOF2M -end - -class OpenSSL::TestEOF2LowlevelSocket < OpenSSL::TestCase - include OpenSSL::TestEOF - include OpenSSL::SSLPairLowlevelSocket - include OpenSSL::TestEOF2M -end - -class OpenSSL::TestPair < OpenSSL::TestCase +class OpenSSL::TestSSLPair < OpenSSL::TestCase include OpenSSL::SSLPair include OpenSSL::TestPairM -end - -class OpenSSL::TestPairLowlevelSocket < OpenSSL::TestCase - include OpenSSL::SSLPairLowlevelSocket - include OpenSSL::TestPairM -end - + include OpenSSL::TestEOF end diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb index a4f0d25e8..f350ab9df 100644 --- a/test/openssl/test_ssl.rb +++ b/test/openssl/test_ssl.rb @@ -198,6 +198,19 @@ def test_connect_accept_nonblock th&.join end + def test_low_level_socket + start_server do |port| + sock = Socket.tcp("127.0.0.1", port) + ssl = OpenSSL::SSL::SSLSocket.new(sock) + ssl.connect + ssl.puts("abc") + assert_equal("abc\n", ssl.gets) + ensure + ssl&.close + sock&.close + end + end + def test_socket_open start_server { |port| begin diff --git a/test/openssl/ut_eof.rb b/test/openssl/ut_eof.rb index 06aa632a6..807bbd015 100644 --- a/test/openssl/ut_eof.rb +++ b/test/openssl/ut_eof.rb @@ -4,6 +4,17 @@ if defined?(OpenSSL) module OpenSSL::TestEOF + def open_file(content) + ssl_pair { |s1, s2| + begin + th = Thread.new { s2 << content; s2.close } + yield s1 + ensure + th&.join + end + } + end + def test_getbyte_eof open_file("") {|f| assert_nil f.getbyte } end From b67520ff55a99de432a01ba147375761a51d2541 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Fri, 28 Nov 2025 03:34:14 +0900 Subject: [PATCH 3/5] ssl: refactor test helper start_server Break it into multiple pieces and simplify: - Let callers pass a complete SSLContext object instead of a callback proc ctx_proc to mutate it. - Add a variant start_server_proc for tests that need finer control, and remove two callbacks server_proc and accept_proc. - Remove rescue for IOError, Errno::EBADF, Errno::EINVAL, and Errno::ENOTSOCK which as far as I can tell should not be possible. --- test/openssl/test_ssl.rb | 563 ++++++++++++++++--------------- test/openssl/test_ssl_session.rb | 113 ++++--- test/openssl/utils.rb | 134 ++++---- 3 files changed, 405 insertions(+), 405 deletions(-) diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb index f350ab9df..8dbd48145 100644 --- a/test/openssl/test_ssl.rb +++ b/test/openssl/test_ssl.rb @@ -75,24 +75,23 @@ def test_ctx_options_config end def test_ssl_with_server_cert - ctx_proc = -> ctx { - ctx.cert = @svr_cert - ctx.key = @svr_key - ctx.extra_chain_cert = [@ca_cert] - } - server_proc = -> (ctx, ssl) { + sctx = OpenSSL::SSL::SSLContext.new + sctx.cert = @svr_cert + sctx.key = @svr_key + sctx.extra_chain_cert = [@ca_cert] + + server_proc = proc do |sock| + ssl = OpenSSL::SSL::SSLSocket.new(sock, sctx).accept assert_equal @svr_cert.to_der, ssl.cert.to_der assert_equal nil, ssl.peer_cert - - readwrite_loop(ctx, ssl) - } - start_server(ctx_proc: ctx_proc, server_proc: server_proc) { |port| + readwrite_loop(ssl) + end + start_server_proc(server_proc) { |port| begin sock = TCPSocket.new("127.0.0.1", port) ctx = OpenSSL::SSL::SSLContext.new ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) ssl.connect - assert_equal sock, ssl.io assert_equal nil, ssl.cert assert_equal @svr_cert.to_der, ssl.peer_cert.to_der @@ -262,12 +261,10 @@ def test_socket_open_with_local_address_port_context end def test_add_certificate - 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 - } - start_server(ctx_proc: ctx_proc) do |port| + sctx = OpenSSL::SSL::SSLContext.new + sctx.add_certificate(@svr_cert, @svr_key, [@ca_cert]) # RSA + + start_server(sctx) do |port| server_connect(port) { |ssl| assert_equal @svr_cert.subject, ssl.peer_cert.subject assert_equal [@svr_cert.subject, @ca_cert.subject], @@ -294,13 +291,11 @@ def test_add_certificate_multiple_certs ecdsa_dn = OpenSSL::X509::Name.parse_rfc2253("CN=localhost2") ecdsa_cert = issue_cert(ecdsa_dn, ecdsa_key, 456, exts, ca2_cert, ca2_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, [ca2_cert]) - } - start_server(ctx_proc: ctx_proc) do |port| + sctx = OpenSSL::SSL::SSLContext.new + sctx.add_certificate(@svr_cert, @svr_key, [@ca_cert]) # RSA + sctx.add_certificate(ecdsa_cert, ecdsa_key, [ca2_cert]) + + start_server(sctx) do |port| ctx = OpenSSL::SSL::SSLContext.new ctx.max_version = :TLS1_2 # TODO: We need this to force certificate type ctx.ciphers = "aECDSA" @@ -332,14 +327,12 @@ def test_extra_chain_cert_auto_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) - } + sctx = OpenSSL::SSL::SSLContext.new + sctx.add_certificate(@svr_cert, @svr_key) # no extra certs + sctx.cert_store = OpenSSL::X509::Store.new.tap { |store| + store.add_cert(@ca_cert) } - start_server(ctx_proc: ctx_proc) { |port| + start_server(sctx) { |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 @@ -375,7 +368,8 @@ def test_read_with_timeout def test_partial_tls_record_read_nonblock written = Thread::Queue.new read = Thread::Queue.new - server_proc = -> (ctx, ssl) { + server_proc = proc do |sock| + ssl = OpenSSL::SSL::SSLSocket.new(sock, make_server_context).accept str = ssl.gets ssl.puts(str) @@ -383,8 +377,8 @@ def test_partial_tls_record_read_nonblock ssl.io.write("\x17") written << :written read.pop - } - start_server(server_proc: server_proc) do |port| + end + start_server_proc(server_proc) do |port| server_connect(port) do |ssl| ssl.puts("abc") assert_equal("abc\n", ssl.gets) @@ -479,19 +473,24 @@ def test_verify_mode_server_cert def test_verify_mode_client_cert_required # Optional, client certificate not supplied - vflag = OpenSSL::SSL::VERIFY_PEER - accept_proc = -> ssl { + sctx = make_server_context + sctx.verify_mode = OpenSSL::SSL::VERIFY_PEER + server_proc = proc do |sock| + ssl = OpenSSL::SSL::SSLSocket.new(sock, sctx).accept assert_equal nil, ssl.peer_cert - } - start_server(verify_mode: vflag, accept_proc: accept_proc) { |port| + readwrite_loop(ssl) + end + start_server_proc(server_proc) { |port| assert_nothing_raised { server_connect(port) { |ssl| ssl.puts("abc"); ssl.gets } } } # Required, client certificate not supplied - vflag = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT - start_server(verify_mode: vflag, ignore_listener_error: true) { |port| + sctx = make_server_context + sctx.verify_mode = + OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT + start_server(sctx, ignore_listener_error: true) { |port| assert_handshake_error { server_connect(port) { |ssl| ssl.puts("abc"); ssl.gets } } @@ -499,16 +498,18 @@ def test_verify_mode_client_cert_required end def test_client_auth_success - vflag = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT - 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| + store = OpenSSL::X509::Store.new + store.add_cert(@ca_cert) + store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT + + sctx = make_server_context + sctx.verify_mode = + OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT + sctx.cert_store = store + # LibreSSL doesn't support client_cert_cb in TLS 1.3 + sctx.max_version = OpenSSL::SSL::TLS1_2_VERSION if libressl? + + start_server(sctx) { |port| ctx = OpenSSL::SSL::SSLContext.new ctx.key = @cli_key ctx.cert = @cli_cert @@ -534,8 +535,11 @@ def test_client_auth_success end def test_client_cert_cb_ignore_error - vflag = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT - start_server(verify_mode: vflag, ignore_listener_error: true) do |port| + sctx = make_server_context + sctx.verify_mode = + OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT + + start_server(sctx, ignore_listener_error: true) do |port| ctx = OpenSSL::SSL::SSLContext.new ctx.client_cert_cb = -> ssl { raise "exception in client_cert_cb must be suppressed" @@ -552,16 +556,16 @@ def test_client_cert_cb_ignore_error def test_client_ca 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 + sctx = make_server_context + sctx.verify_mode = + OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT + store = OpenSSL::X509::Store.new + store.add_cert(@ca_cert) + store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT + sctx.cert_store = store + sctx.client_ca = [@ca_cert] - vflag = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT - start_server(verify_mode: vflag, ctx_proc: ctx_proc) { |port| + start_server(sctx) { |port| ctx = OpenSSL::SSL::SSLContext.new client_ca_from_server = nil ctx.client_cert_cb = Proc.new do |sslconn| @@ -723,13 +727,14 @@ def test_finished_messages client_finished = nil client_peer_finished = nil - start_server(accept_proc: proc { |server| - server_finished = server.finished_message - server_peer_finished = server.peer_finished_message - }) { |port| - ctx = OpenSSL::SSL::SSLContext.new - ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE - server_connect(port, ctx) { |ssl| + server_proc = proc do |sock| + ssl = OpenSSL::SSL::SSLSocket.new(sock, make_server_context).accept + server_finished = ssl.finished_message + server_peer_finished = ssl.peer_finished_message + readwrite_loop(ssl) + end + start_server_proc(server_proc) { |port| + server_connect(port) { |ssl| ssl.puts "abc"; ssl.gets client_finished = ssl.finished_message @@ -760,14 +765,13 @@ def test_post_connect_check_with_anon_ciphers omit_on_fips omit "AWS-LC does not support DHE ciphersuites" if aws_lc? - ctx_proc = -> ctx { - ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION - ctx.ciphers = "aNULL" - ctx.tmp_dh = Fixtures.pkey("dh-1") - ctx.security_level = 0 - } + sctx = OpenSSL::SSL::SSLContext.new + sctx.max_version = OpenSSL::SSL::TLS1_2_VERSION + sctx.ciphers = "aNULL" + sctx.tmp_dh = Fixtures.pkey("dh-1") + sctx.security_level = 0 - start_server(ctx_proc: ctx_proc) { |port| + start_server(sctx) { |port| ctx = OpenSSL::SSL::SSLContext.new ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION ctx.ciphers = "aNULL" @@ -1093,19 +1097,18 @@ def test_tlsext_hostname fooctx.cert = @cli_cert fooctx.key = @cli_key - ctx_proc = proc { |ctx| - ctx.servername_cb = proc { |ssl, servername| - case servername - when "foo.example.com" - fooctx - when "bar.example.com" - nil - else - raise "unreachable" - end - } + sctx = make_server_context + sctx.servername_cb = proc { |ssl, servername| + case servername + when "foo.example.com" + fooctx + when "bar.example.com" + nil + else + raise "unreachable" + end } - start_server(ctx_proc: ctx_proc) do |port| + start_server(sctx) do |port| sock = TCPSocket.new("127.0.0.1", port) begin ssl = OpenSSL::SSL::SSLSocket.new(sock) @@ -1203,17 +1206,16 @@ def test_accept_errors_include_peeraddr end def test_verify_hostname_on_connect - ctx_proc = proc { |ctx| - exts = [ - ["keyUsage", "keyEncipherment,digitalSignature", true], - ["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 - } + exts = [ + ["keyUsage", "keyEncipherment,digitalSignature", true], + ["subjectAltName", "DNS:a.example.com,DNS:*.b.example.com," \ + "DNS:c*.example.com,DNS:d.*.example.com"], + ] + cert = issue_cert(@svr, @svr_key, 4, exts, @ca_cert, @ca_key) + sctx = OpenSSL::SSL::SSLContext.new + sctx.add_certificate(cert, @svr_key) - start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| + start_server(sctx, ignore_listener_error: true) do |port| ctx = OpenSSL::SSL::SSLContext.new assert_equal false, ctx.verify_hostname ctx.verify_hostname = true @@ -1249,16 +1251,15 @@ def test_verify_hostname_on_connect end def test_verify_hostname_failure_error_code - ctx_proc = proc { |ctx| - exts = [ - ["keyUsage", "keyEncipherment,digitalSignature", true], - ["subjectAltName", "DNS:a.example.com"], - ] - ctx.cert = issue_cert(@svr, @svr_key, 4, exts, @ca_cert, @ca_key) - ctx.key = @svr_key - } + exts = [ + ["keyUsage", "keyEncipherment,digitalSignature", true], + ["subjectAltName", "DNS:a.example.com"], + ] + cert = issue_cert(@svr, @svr_key, 4, exts, @ca_cert, @ca_key) + sctx = OpenSSL::SSL::SSLContext.new + sctx.add_certificate(cert, @svr_key) - start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| + start_server(sctx, ignore_listener_error: true) do |port| verify_callback_ok = verify_callback_err = nil ctx = OpenSSL::SSL::SSLContext.new @@ -1294,12 +1295,13 @@ def test_connect_certificate_verify_failed_exception_message } } - ctx_proc = proc { |ctx| - now = Time.now - ctx.cert = issue_cert(@svr, @svr_key, 30, [], @ca_cert, @ca_key, - not_before: now - 7200, not_after: now - 3600) - } - start_server(ignore_listener_error: true, ctx_proc: ctx_proc) { |port| + now = Time.now + cert = issue_cert(@svr, @svr_key, 30, [], @ca_cert, @ca_key, + not_before: now - 7200, not_after: now - 3600) + sctx = OpenSSL::SSL::SSLContext.new + sctx.add_certificate(cert, @svr_key) + + start_server(sctx, ignore_listener_error: true) { |port| store = OpenSSL::X509::Store.new store.add_cert(@ca_cert) ctx = OpenSSL::SSL::SSLContext.new @@ -1318,16 +1320,16 @@ def check_supported_protocol_versions OpenSSL::SSL::TLS1_2_VERSION, OpenSSL::SSL::TLS1_3_VERSION, ] - supported = [] - 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| + + sctx = make_server_context + # 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 + sctx.security_level = 0 + # Explicitly reset them to avoid influenced by OPENSSL_CONF + sctx.min_version = sctx.max_version = nil + + start_server(sctx, ignore_listener_error: true) do |port| possible_versions.each do |ver| ctx = OpenSSL::SSL::SSLContext.new ctx.security_level = 0 @@ -1349,20 +1351,19 @@ def check_supported_protocol_versions def test_set_params_min_version supported = check_supported_protocol_versions - store = OpenSSL::X509::Store.new - store.add_cert(@ca_cert) + return unless supported.include?(OpenSSL::SSL::SSL3_VERSION) - if supported.include?(OpenSSL::SSL::SSL3_VERSION) - # SSLContext#set_params properly disables SSL 3.0 by default - ctx_proc = proc { |ctx| - ctx.min_version = ctx.max_version = OpenSSL::SSL::SSL3_VERSION - } - 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_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx) } - } - end + # SSLContext#set_params properly disables SSL 3.0 by default + sctx = make_server_context + sctx.min_version = sctx.max_version = OpenSSL::SSL::SSL3_VERSION + + start_server(sctx, ignore_listener_error: true) { |port| + store = OpenSSL::X509::Store.new + store.add_cert(@ca_cert) + ctx = OpenSSL::SSL::SSLContext.new + ctx.set_params(cert_store: store, verify_hostname: false) + assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx) } + } end def test_minmax_version @@ -1381,11 +1382,11 @@ def test_minmax_version # Server enables a single version supported.each do |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| + sctx = make_server_context + sctx.security_level = 0 + sctx.min_version = sctx.max_version = ver + + start_server(sctx, ignore_listener_error: true) { |port| supported.each do |cver| # Client enables a single version ctx1 = OpenSSL::SSL::SSLContext.new @@ -1434,11 +1435,11 @@ def test_minmax_version # Server sets min_version (earliest is disabled) sver = supported[1] - ctx_proc = proc { |ctx| - ctx.security_level = 0 - ctx.min_version = sver - } - start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port| + sctx = make_server_context + sctx.security_level = 0 + sctx.min_version = sver + + start_server(sctx, ignore_listener_error: true) { |port| supported.each do |cver| # Client sets min_version ctx1 = OpenSSL::SSL::SSLContext.new @@ -1468,12 +1469,12 @@ def test_minmax_version # Server sets max_version (latest is disabled) sver = supported[-2] - 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| + sctx = make_server_context + sctx.security_level = 0 + sctx.min_version = 0 + sctx.max_version = sver + + start_server(sctx, ignore_listener_error: true) { |port| supported.each do |cver| # Client sets min_version ctx1 = OpenSSL::SSL::SSLContext.new @@ -1563,10 +1564,9 @@ def test_respect_system_default_min 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| + sctx = make_server_context + sctx.min_version = sctx.max_version = OpenSSL::SSL::TLS1_2_VERSION + start_server(sctx, 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 @@ -1579,10 +1579,9 @@ def test_respect_system_default_min 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| + sctx = make_server_context + sctx.min_version = sctx.max_version = OpenSSL::SSL::TLS1_3_VERSION + start_server(sctx, 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 @@ -1609,12 +1608,11 @@ def test_options_disable_versions end # Server disables TLS 1.2 and earlier - ctx_proc = proc { |ctx| - ctx.options |= OpenSSL::SSL::OP_NO_SSLv2 | OpenSSL::SSL::OP_NO_SSLv3 | - OpenSSL::SSL::OP_NO_TLSv1 | OpenSSL::SSL::OP_NO_TLSv1_1 | - OpenSSL::SSL::OP_NO_TLSv1_2 - } - start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port| + sctx = make_server_context + sctx.options |= OpenSSL::SSL::OP_NO_SSLv2 | OpenSSL::SSL::OP_NO_SSLv3 | + OpenSSL::SSL::OP_NO_TLSv1 | OpenSSL::SSL::OP_NO_TLSv1_1 | + OpenSSL::SSL::OP_NO_TLSv1_2 + start_server(sctx, ignore_listener_error: true) { |port| # Client only supports TLS 1.2 ctx1 = OpenSSL::SSL::SSLContext.new ctx1.min_version = ctx1.max_version = OpenSSL::SSL::TLS1_2_VERSION @@ -1627,10 +1625,9 @@ def test_options_disable_versions } # Server only supports TLS 1.2 - 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) { |port| + sctx = make_server_context + sctx.min_version = sctx.max_version = OpenSSL::SSL::TLS1_2_VERSION + start_server(sctx, ignore_listener_error: true) { |port| # Client doesn't support TLS 1.2 ctx1 = OpenSSL::SSL::SSLContext.new ctx1.options |= OpenSSL::SSL::OP_NO_TLSv1_2 @@ -1656,9 +1653,13 @@ def test_ssl_methods_constant def test_renegotiation_cb num_handshakes = 0 - renegotiation_cb = Proc.new { |ssl| num_handshakes += 1 } - ctx_proc = Proc.new { |ctx| ctx.renegotiation_cb = renegotiation_cb } - start_server(ctx_proc: ctx_proc) { |port| + sctx = make_server_context + sctx.renegotiation_cb = -> ssl { + num_handshakes += 1 + assert_kind_of(OpenSSL::SSL::SSLSocket, ssl) + } + + start_server(sctx) { |port| server_connect(port) { |ssl| assert_equal(1, num_handshakes) ssl.puts "abc"; assert_equal "abc\n", ssl.gets @@ -1668,13 +1669,10 @@ def test_renegotiation_cb def test_alpn_protocol_selection_ary advertised = ["http/1.1", "spdy/2"] - ctx_proc = Proc.new { |ctx| - ctx.alpn_select_cb = -> (protocols) { - protocols.first - } - ctx.alpn_protocols = advertised - } - start_server(ctx_proc: ctx_proc) { |port| + sctx = make_server_context + sctx.alpn_select_cb = -> protocols { protocols.first } + + start_server(sctx) { |port| ctx = OpenSSL::SSL::SSLContext.new ctx.alpn_protocols = advertised server_connect(port, ctx) { |ssl| @@ -1715,8 +1713,10 @@ def test_npn_protocol_selection_ary return unless OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb) advertised = ["http/1.1", "spdy/2"] - ctx_proc = proc { |ctx| ctx.npn_protocols = advertised } - start_server(ctx_proc: ctx_proc) { |port| + sctx = make_server_context + sctx.npn_protocols = advertised + + start_server(sctx) { |port| selector = lambda { |which| ctx = OpenSSL::SSL::SSLContext.new ctx.max_version = :TLS1_2 @@ -1738,8 +1738,10 @@ def advertised.each yield "http/1.1" yield "spdy/2" end - ctx_proc = Proc.new { |ctx| ctx.npn_protocols = advertised } - start_server(ctx_proc: ctx_proc) { |port| + sctx = make_server_context + sctx.npn_protocols = advertised + + start_server(sctx) { |port| selector = lambda { |selected, which| ctx = OpenSSL::SSL::SSLContext.new ctx.max_version = :TLS1_2 @@ -1756,8 +1758,10 @@ def advertised.each def test_npn_protocol_selection_cancel return unless OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb) - ctx_proc = Proc.new { |ctx| ctx.npn_protocols = ["http/1.1"] } - start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port| + sctx = make_server_context + sctx.npn_protocols = ["http/1.1"] + + start_server(sctx, ignore_listener_error: true) { |port| ctx = OpenSSL::SSL::SSLContext.new ctx.max_version = :TLS1_2 ctx.npn_select_cb = -> (protocols) { raise RuntimeError.new } @@ -1778,8 +1782,10 @@ def test_npn_advertised_protocol_too_long 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(ctx_proc: ctx_proc, ignore_listener_error: true) { |port| + sctx = make_server_context + sctx.npn_protocols = ["http/1.1"] + + start_server(sctx, ignore_listener_error: true) { |port| ctx = OpenSSL::SSL::SSLContext.new ctx.max_version = :TLS1_2 ctx.npn_select_cb = -> (protocols) { "a" * 256 } @@ -1787,13 +1793,14 @@ def test_npn_selected_protocol_too_long } end - def readwrite_loop_safe(ctx, ssl) - readwrite_loop(ctx, ssl) - rescue OpenSSL::SSL::SSLError - end - def test_close_after_socket_close - start_server(server_proc: method(:readwrite_loop_safe)) { |port| + # The client closes the TCP socket without SSLSocket#stop + sctx = make_server_context + if defined?(OpenSSL::SSL::OP_IGNORE_UNEXPECTED_EOF) + sctx.options |= OpenSSL::SSL::OP_IGNORE_UNEXPECTED_EOF + end + + start_server(sctx) { |port| sock = TCPSocket.new("127.0.0.1", port) ssl = OpenSSL::SSL::SSLSocket.new(sock) ssl.connect @@ -1819,11 +1826,10 @@ def test_get_ephemeral_key omit_on_fips # kRSA - ctx_proc1 = proc { |ctx| - ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION - ctx.ciphers = "kRSA" - } - start_server(ctx_proc: ctx_proc1, ignore_listener_error: true) do |port| + sctx = make_server_context + sctx.max_version = OpenSSL::SSL::TLS1_2_VERSION + sctx.ciphers = "kRSA" + start_server(sctx, ignore_listener_error: true) do |port| ctx = OpenSSL::SSL::SSLContext.new ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION ctx.ciphers = "kRSA" @@ -1852,10 +1858,9 @@ def test_get_ephemeral_key end # ECDHE - ctx_proc3 = proc { |ctx| - ctx.groups = "P-256" - } - start_server(ctx_proc: ctx_proc3) do |port| + sctx = make_server_context + sctx.groups = "P-256" + start_server(sctx) do |port| server_connect(port) { |ssl| assert_instance_of OpenSSL::PKey::EC, ssl.tmp_key ssl.puts "abc"; assert_equal "abc\n", ssl.gets @@ -1879,12 +1884,11 @@ def test_fallback_scsv server_connect(port, ctx) 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| + sctx = make_server_context + sctx.security_level = 0 + sctx.min_version = 0 + sctx.max_version = OpenSSL::SSL::TLS1_1_VERSION + start_server(sctx) do |port| ctx = OpenSSL::SSL::SSLContext.new ctx.enable_fallback_scsv ctx.security_level = 0 @@ -1940,15 +1944,16 @@ def test_tmp_dh_callback dh = Fixtures.pkey("dh-1") called = false - ctx_proc = -> ctx { - ctx.max_version = :TLS1_2 - ctx.ciphers = "DH:!NULL" - ctx.tmp_dh_callback = ->(*args) { - called = true - dh - } + + sctx = make_server_context + sctx.max_version = :TLS1_2 + sctx.ciphers = "DH:!NULL" + sctx.tmp_dh_callback = ->(*args) { + called = true + dh } - start_server(ctx_proc: ctx_proc) do |port| + + start_server(sctx) do |port| ctx = OpenSSL::SSL::SSLContext.new ctx.groups = "P-256" # Exclude RFC 7919 groups server_connect(port, ctx) { |ssl| @@ -2048,13 +2053,11 @@ def test_sigalgs 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| + sctx = OpenSSL::SSL::SSLContext.new + sctx.add_certificate(@svr_cert, @svr_key, [@ca_cert]) # RSA + sctx.add_certificate(ecdsa_cert, ecdsa_key, [@ca_cert]) # ECDSA + + start_server(sctx) do |port| ctx1 = OpenSSL::SSL::SSLContext.new ctx1.sigalgs = "rsa_pss_rsae_sha256" server_connect(port, ctx1) { |ssl| @@ -2091,15 +2094,16 @@ def test_client_sigalgs 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| + store = OpenSSL::X509::Store.new + store.add_cert(@ca_cert) + store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT + sctx = make_server_context + sctx.cert_store = store + sctx.verify_mode = + OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT + sctx.client_sigalgs = "ECDSA+SHA256" + + start_server(sctx, ignore_listener_error: true) do |port| ctx1 = OpenSSL::SSL::SSLContext.new ctx1.add_certificate(@cli_cert, @cli_key) # RSA assert_handshake_error { @@ -2121,13 +2125,14 @@ def test_get_sigalg # SSL_get0_peer_signature_name() not supported return unless openssl?(3, 5, 0) - server_proc = -> (ctx, ssl) { + server_proc = proc do |sock| + ssl = OpenSSL::SSL::SSLSocket.new(sock, make_server_context).accept 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| + readwrite_loop(ssl) + end + start_server_proc(server_proc) do |port| cli_ctx = OpenSSL::SSL::SSLContext.new server_connect(port, cli_ctx) do |ssl| assert_nil(ssl.sigalg) @@ -2149,20 +2154,18 @@ def test_pqc_sigalg 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) + sctx = OpenSSL::SSL::SSLContext.new + sctx.sigalgs = "rsa_pss_rsae_sha256:mldsa65" + sctx.add_certificate(mldsa_cert, mldsa) + sctx.add_certificate(rsa_cert, rsa) - readwrite_loop(ctx, ssl) - } - start_server(ctx_proc: ctx_proc, server_proc: server_proc) do |port| + server_proc = proc do |sock| + ssl = OpenSSL::SSL::SSLSocket.new(sock, sctx).accept + assert_equal('mldsa65', ssl.sigalg) + readwrite_loop(ssl) + end + start_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 @@ -2174,12 +2177,12 @@ def test_pqc_sigalg } end - server_proc = -> (ctx, ssl) { + server_proc = proc do |sock| + ssl = OpenSSL::SSL::SSLSocket.new(sock, sctx).accept assert_equal('rsa_pss_rsae_sha256', ssl.sigalg) - - readwrite_loop(ctx, ssl) - } - start_server(ctx_proc: ctx_proc, server_proc: server_proc) do |port| + readwrite_loop(ssl) + end + start_server_proc(server_proc) do |port| ctx = OpenSSL::SSL::SSLContext.new ctx.sigalgs = 'rsa_pss_rsae_sha256' server_connect(port, ctx) { |ssl| @@ -2192,12 +2195,12 @@ def test_pqc_sigalg 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| + sctx = make_server_context + sctx.max_version = :TLS1_2 + sctx.ciphers = "DH:!NULL" # use DH + sctx.tmp_dh_callback = nil + + start_server(sctx) do |port| assert_nothing_raised { server_connect(port) { } } end end @@ -2208,12 +2211,12 @@ def test_tmp_dh omit "AWS-LC does not support DHE ciphersuites" if aws_lc? dh = Fixtures.pkey("dh-1") - ctx_proc = -> ctx { - ctx.max_version = :TLS1_2 - ctx.ciphers = "DH:!NULL" # use DH - ctx.tmp_dh = dh - } - start_server(ctx_proc: ctx_proc) do |port| + sctx = make_server_context + sctx.max_version = :TLS1_2 + sctx.ciphers = "DH:!NULL" # use DH + sctx.tmp_dh = dh + + start_server(sctx) do |port| ctx = OpenSSL::SSL::SSLContext.new ctx.groups = "P-256" # Exclude RFC 7919 groups server_connect(port, ctx) { |ssl| @@ -2223,13 +2226,13 @@ def test_tmp_dh end 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.groups = "P-384:P-521" - } - start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| + # Enable both ECDHE (~ TLS 1.2) cipher suites and TLS 1.3 + sctx = make_server_context + sctx.max_version = OpenSSL::SSL::TLS1_2_VERSION + sctx.ciphers = "kEECDH" + sctx.groups = "P-384:P-521" + + start_server(sctx, 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.groups = "P-256:P-384" @@ -2267,11 +2270,11 @@ def test_set_groups_tls12 end def test_set_groups_tls13 - ctx_proc = -> ctx { - # Assume TLS 1.3 is enabled and chosen by default - ctx.groups = "P-384:P-521" - } - start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| + # Assume TLS 1.3 is enabled and chosen by default + sctx = make_server_context + sctx.groups = "P-384:P-521" + + start_server(sctx) do |port| ctx = OpenSSL::SSL::SSLContext.new ctx.groups = "P-256:P-384" # disable P-521 @@ -2294,10 +2297,10 @@ def test_pqc_group 'SecP256r1MLKEM768', 'SecP384r1MLKEM1024' ].each do |group| - ctx_proc = -> ctx { - ctx.groups = group - } - start_server(ctx_proc: ctx_proc) do |port| + sctx = make_server_context + sctx.groups = group + + start_server(sctx) do |port| ctx = OpenSSL::SSL::SSLContext.new ctx.groups = group server_connect(port, ctx) { |ssl| diff --git a/test/openssl/test_ssl_session.rb b/test/openssl/test_ssl_session.rb index 37874ca27..ab6379385 100644 --- a/test/openssl/test_ssl_session.rb +++ b/test/openssl/test_ssl_session.rb @@ -5,10 +5,10 @@ class OpenSSL::TestSSLSession < OpenSSL::SSLTestCase def test_session - ctx_proc = proc { |ctx| - ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION - } - start_server(ctx_proc: ctx_proc) do |port| + sctx = make_server_context + sctx.max_version = OpenSSL::SSL::TLS1_2_VERSION + + start_server(sctx) do |port| server_connect_with_session(port, nil, nil) { |ssl| session = ssl.session assert(session == OpenSSL::SSL::Session.new(session.to_pem)) @@ -120,14 +120,15 @@ def test_resumption } } - ctx_proc = proc { |ctx| - ctx.options &= ~OpenSSL::SSL::OP_NO_TICKET - # Disable server-side session cache which is enabled by default - ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_OFF - # Session tickets must be retrieved via ctx.session_new_cb in TLS 1.3 in AWS-LC. - ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION if libressl? || aws_lc? - } - start_server(ctx_proc: ctx_proc) do |port| + sctx = make_server_context + sctx.options &= ~OpenSSL::SSL::OP_NO_TICKET + # Disable server-side session cache which is enabled by default + sctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_OFF + # Session tickets must be retrieved via ctx.session_new_cb in TLS 1.3 in + # AWS-LC. + sctx.max_version = OpenSSL::SSL::TLS1_2_VERSION if libressl? || aws_lc? + + start_server(sctx) do |port| sess1 = server_connect_with_session(port, nil, nil) { |ssl| ssl.puts("abc"); assert_equal "abc\n", ssl.gets assert_equal false, ssl.session_reused? @@ -147,14 +148,16 @@ def test_resumption end def test_server_session_cache - ctx_proc = Proc.new do |ctx| - ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION - ctx.options |= OpenSSL::SSL::OP_NO_TICKET - end + sctx = make_server_context + sctx.max_version = OpenSSL::SSL::TLS1_2_VERSION + sctx.options |= OpenSSL::SSL::OP_NO_TICKET connections = nil saved_session = nil - server_proc = Proc.new do |ctx, ssl| + + server_proc = Proc.new do |sock| + ssl = OpenSSL::SSL::SSLSocket.new(sock, sctx).accept + ctx = ssl.context stats = ctx.session_cache_stats case connections @@ -194,10 +197,9 @@ def test_server_session_cache assert_equal true, ctx.session_add(saved_session.dup) end - readwrite_loop(ctx, ssl) + readwrite_loop(ssl) end - - start_server(ctx_proc: ctx_proc, server_proc: server_proc) do |port| + start_server_proc(server_proc) do |port| first_session = nil 10.times do |i| connections = i @@ -285,11 +287,12 @@ def test_ctx_client_session_cb_tls13 def test_ctx_client_session_cb_tls13_exception omit "LibreSSL does not call session_new_cb in TLS 1.3" if libressl? - server_proc = lambda do |ctx, ssl| - readwrite_loop(ctx, ssl) - rescue SystemCallError, OpenSSL::SSL::SSLError + sctx = make_server_context + if defined?(OpenSSL::SSL::OP_IGNORE_UNEXPECTED_EOF) + sctx.options |= OpenSSL::SSL::OP_IGNORE_UNEXPECTED_EOF end - start_server(server_proc: server_proc) do |port| + + start_server(sctx) do |port| ctx = OpenSSL::SSL::SSLContext.new ctx.min_version = :TLS1_3 ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT @@ -308,42 +311,42 @@ def test_ctx_client_session_cb_tls13_exception def test_ctx_server_session_cb connections = nil called = {} + cctx = OpenSSL::SSL::SSLContext.new cctx.max_version = OpenSSL::SSL::TLS1_2_VERSION - sctx = nil - ctx_proc = Proc.new { |ctx| - sctx = ctx - ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION - ctx.options |= OpenSSL::SSL::OP_NO_TICKET - - # get_cb is called whenever a client proposed to resume a session but - # the session could not be found in the internal session cache. - last_server_session = nil - ctx.session_get_cb = lambda { |ary| - _sess, data = ary - called[:get] = data - - if connections == 2 - last_server_session.dup - else - nil - end - } - ctx.session_new_cb = lambda { |ary| - _sock, sess = ary - called[:new] = sess - last_server_session = sess - } - - if TEST_SESSION_REMOVE_CB - ctx.session_remove_cb = lambda { |ary| - _ctx, sess = ary - called[:remove] = sess - } + sctx = make_server_context + sctx.max_version = OpenSSL::SSL::TLS1_2_VERSION + sctx.options |= OpenSSL::SSL::OP_NO_TICKET + + # get_cb is called whenever a client proposed to resume a session but + # the session could not be found in the internal session cache. + last_server_session = nil + sctx.session_get_cb = lambda { |ary| + _sess, data = ary + called[:get] = data + + if connections == 2 + last_server_session.dup + else + nil end } - start_server(ctx_proc: ctx_proc) do |port| + + sctx.session_new_cb = lambda { |ary| + _sock, sess = ary + called[:new] = sess + last_server_session = sess + } + + if TEST_SESSION_REMOVE_CB + sctx.session_remove_cb = lambda { |ary| + _ctx, sess = ary + called[:remove] = sess + } + end + + start_server(sctx) do |port| connections = 0 sess0 = server_connect_with_session(port, cctx, nil) { |ssl| ssl.puts("abc"); assert_equal "abc\n", ssl.gets diff --git a/test/openssl/utils.rb b/test/openssl/utils.rb index 7e6fe8b16..fc02bcd51 100644 --- a/test/openssl/utils.rb +++ b/test/openssl/utils.rb @@ -190,93 +190,87 @@ def setup @server = nil end - def readwrite_loop(ctx, ssl) + def readwrite_loop(ssl) while line = ssl.gets ssl.write(line) end + ssl.close end - def start_server(verify_mode: OpenSSL::SSL::VERIFY_NONE, - ctx_proc: nil, server_proc: method(:readwrite_loop), - accept_proc: proc{}, - ignore_listener_error: false, &block) - IO.pipe {|stop_pipe_r, stop_pipe_w| - ctx = OpenSSL::SSL::SSLContext.new - ctx.cert = @svr_cert - ctx.key = @svr_key - ctx.verify_mode = verify_mode - ctx_proc.call(ctx) if ctx_proc - - Socket.do_not_reverse_lookup = true + def make_server_context + sctx = OpenSSL::SSL::SSLContext.new + sctx.add_certificate(@svr_cert, @svr_key) + sctx + end + + def start_server_proc(server_proc, &block) + IO.pipe do |stop_pipe_r, stop_pipe_w| tcps = TCPServer.new("127.0.0.1", 0) + tcps.setsockopt(:TCP, :NODELAY, 1) port = tcps.connect_address.ip_port - ssls = OpenSSL::SSL::SSLServer.new(tcps, ctx) - threads = [] - begin - server_thread = Thread.new do - Thread.current.report_on_exception = false - - begin - loop do - begin - readable, = IO.select([ssls, stop_pipe_r]) - break if readable.include? stop_pipe_r - ssl = ssls.accept - accept_proc.call(ssl) - rescue OpenSSL::SSL::SSLError, IOError, Errno::EBADF, Errno::EINVAL, - Errno::ECONNABORTED, Errno::ENOTSOCK, Errno::ECONNRESET - retry if ignore_listener_error - raise - end - - th = Thread.new do - Thread.current.report_on_exception = false - - begin - server_proc.call(ctx, ssl) - ensure - ssl.close - end - true - end - threads << th - end - ensure - tcps.close - end - end + server_thread = Thread.new do + Thread.current.report_on_exception = false + + loop do + readable, = IO.select([tcps, stop_pipe_r]) + break if readable.include? stop_pipe_r + sock = tcps.accept - client_thread = Thread.new do - Thread.current.report_on_exception = false + th = Thread.new do + Thread.current.report_on_exception = false - begin - block.call(port) + server_proc.call(sock) ensure - # Stop accepting new connection - stop_pipe_w.close - server_thread.join + sock.close end + threads << th end - threads.unshift client_thread ensure - # Terminate existing connections. If a thread did 'pend', re-raise it. - pend = nil - threads.each { |th| - begin - timeout = EnvUtil.apply_timeout_scale(30) - th.join(timeout) or - th.raise(RuntimeError, "[start_server] thread did not exit in #{timeout} secs") - rescue Test::Unit::PendedError - pend = $! - rescue Exception - end - } - raise pend if pend - assert_join_threads(threads) + tcps.close + end + + client_thread = Thread.new do + Thread.current.report_on_exception = false + + block.call(port) + ensure + # Stop accepting new connection + stop_pipe_w.close + server_thread.join + end + threads.unshift client_thread + ensure + # Terminate existing connections. If a thread did 'pend', re-raise it. + pend = nil + threads.each { |th| + begin + timeout = EnvUtil.apply_timeout_scale(30) + th.join(timeout) or + th.raise(RuntimeError, "[start_server] thread did not exit in #{timeout} secs") + rescue Test::Unit::PendedError + pend = $! + rescue Exception + end + } + raise pend if pend + assert_join_threads(threads) + end + end + + def start_server(ctx = make_server_context, ignore_listener_error: false, &block) + server_proc = -> sock { + ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) + begin + ssl.accept + rescue OpenSSL::SSL::SSLError, Errno::ECONNABORTED, Errno::ECONNRESET + next if ignore_listener_error + raise end + readwrite_loop(ssl) } + start_server_proc(server_proc, &block) end end From fc60c97ee334081917cf388275fc460204b302f3 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Tue, 2 Dec 2025 17:33:47 +0900 Subject: [PATCH 4/5] ssl: delay closing TCP sockets in start_server Some tests expect server-side SSLSocket#accept to fail for various reasons. On some systems, closing the underlying socket immediately with IO#close causes the TCP connection to be terminated with RST. Do not close it immediately so that the client can reliably receive the TLS alert. This allows writing more meaningful assertions. Also add a dedicated test case for the rb_sys_fail() path in SSLSocket#connect. --- test/openssl/test_ssl.rb | 38 ++++++++++++++++++++++++++------------ test/openssl/utils.rb | 6 +++--- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb index 8dbd48145..5db46bcc9 100644 --- a/test/openssl/test_ssl.rb +++ b/test/openssl/test_ssl.rb @@ -491,7 +491,8 @@ def test_verify_mode_client_cert_required sctx.verify_mode = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT start_server(sctx, ignore_listener_error: true) { |port| - assert_handshake_error { + # TLS 1.3 alert: certificate_required(116) + assert_raise_with_message(OpenSSL::SSL::SSLError, /alert number 116/) { server_connect(port) { |ssl| ssl.puts("abc"); ssl.gets } } } @@ -547,7 +548,7 @@ def test_client_cert_cb_ignore_error # 1. Exception in client_cert_cb is suppressed # 2. No client certificate will be sent to the server # 3. SSL_VERIFY_FAIL_IF_NO_PEER_CERT causes the handshake to fail - assert_handshake_error { + assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx) { |ssl| ssl.puts("abc"); ssl.gets } } end @@ -1286,6 +1287,27 @@ def test_verify_hostname_failure_error_code end end + def test_connect_systemcallerror + # SSL_connect() should fail with SSL_ERROR_SYSCALL and errno should be + # kept intact from the underlying recv(2)/send(2). + pend "AWS-LC does not preserve errno on SSL_ERROR_SYSCALL" if aws_lc? + + server_proc = proc do |sock| + sock.setsockopt(:SOCKET, :LINGER, [1, 0].pack("ii")) + sock.read(1) + sock.close + end + start_server_proc(server_proc) do |port| + sock = TCPSocket.new("127.0.0.1", port) + ssl = OpenSSL::SSL::SSLSocket.new(sock) + assert_raise(Errno::ECONNRESET, Errno::EPIPE) { + ssl.connect + } + ensure + sock&.close + end + end + def test_connect_certificate_verify_failed_exception_message start_server(ignore_listener_error: true) { |port| ctx = OpenSSL::SSL::SSLContext.new @@ -1338,7 +1360,7 @@ def check_supported_protocol_versions ssl.puts "abc"; assert_equal "abc\n", ssl.gets } supported << ver - rescue OpenSSL::SSL::SSLError, Errno::ECONNRESET + rescue OpenSSL::SSL::SSLError end end @@ -2106,7 +2128,7 @@ def test_client_sigalgs start_server(sctx, ignore_listener_error: true) do |port| ctx1 = OpenSSL::SSL::SSLContext.new ctx1.add_certificate(@cli_cert, @cli_key) # RSA - assert_handshake_error { + assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx1) { |ssl| ssl.puts("abc"); ssl.gets } @@ -2458,14 +2480,6 @@ def server_connect(port, ctx = nil) sock.close end end - - def assert_handshake_error - # different OpenSSL versions react differently when facing a SSL/TLS version - # that has been marked as forbidden, therefore any of these may be raised - assert_raise(OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::EPIPE) { - yield - } - end end end diff --git a/test/openssl/utils.rb b/test/openssl/utils.rb index fc02bcd51..7dd3a719d 100644 --- a/test/openssl/utils.rb +++ b/test/openssl/utils.rb @@ -210,20 +210,19 @@ def start_server_proc(server_proc, &block) port = tcps.connect_address.ip_port threads = [] + sockets = [] server_thread = Thread.new do Thread.current.report_on_exception = false loop do readable, = IO.select([tcps, stop_pipe_r]) break if readable.include? stop_pipe_r - sock = tcps.accept + sockets << sock = tcps.accept th = Thread.new do Thread.current.report_on_exception = false server_proc.call(sock) - ensure - sock.close end threads << th end @@ -254,6 +253,7 @@ def start_server_proc(server_proc, &block) rescue Exception end } + sockets.each(&:close) raise pend if pend assert_join_threads(threads) end From c30c576522f36578f701d8d1c99c616dcdb1e3fa Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Fri, 28 Nov 2025 03:34:37 +0900 Subject: [PATCH 5/5] ssl: use start_server_proc in more test cases Update more tests to use start_server_proc instead of UNIXSocket.pair. It runs threads with timeout and prevents "rake test" from hanging indefinitely. It also produces better error messages when a test fails. --- test/openssl/test_ssl.rb | 331 ++++++++++++++++----------------------- 1 file changed, 135 insertions(+), 196 deletions(-) diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb index 5db46bcc9..a252a0e64 100644 --- a/test/openssl/test_ssl.rb +++ b/test/openssl/test_ssl.rb @@ -108,62 +108,52 @@ def test_ssl_with_server_cert end def test_connect_accept_nonblock_no_exception - ctx2 = OpenSSL::SSL::SSLContext.new - ctx2.cert = @svr_cert - ctx2.key = @svr_key - - sock1, sock2 = socketpair - - s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2) - accepted = s2.accept_nonblock(exception: false) - assert_equal :wait_readable, accepted - - ctx1 = OpenSSL::SSL::SSLContext.new - s1 = OpenSSL::SSL::SSLSocket.new(sock1, ctx1) - th = Thread.new do - rets = [] - begin - rv = s1.connect_nonblock(exception: false) - rets << rv + server_proc = proc do |sock| + s2 = OpenSSL::SSL::SSLSocket.new(sock, make_server_context) + accepted = s2.accept_nonblock(exception: false) + assert_equal(:wait_readable, accepted) + loop do + rv = s2.accept_nonblock(exception: false) case rv - when :wait_writable - IO.select(nil, [s1], nil, 5) when :wait_readable - IO.select([s1], nil, nil, 5) + IO.select([s2], nil, nil, 1) + when :wait_writable + IO.select(nil, [s2], nil, 1) + else + assert_same(s2, rv) + break end - end until rv == s1 - rets - end - - until th.join(0.01) - accepted = s2.accept_nonblock(exception: false) - assert_include([s2, :wait_readable, :wait_writable ], accepted) + end + assert_equal("abc\n", s2.gets) + s2.puts("abc") + s2.close end - - rets = th.value - assert_instance_of Array, rets - rets.each do |rv| - assert_include([s1, :wait_readable, :wait_writable ], rv) + start_server_proc(server_proc) do |port| + sock = TCPSocket.new("127.0.0.1", port) + s1 = OpenSSL::SSL::SSLSocket.new(sock) + loop do + rv = s1.connect_nonblock(exception: false) + case rv + when :wait_readable + IO.select([s1], nil, nil, 1) + when :wait_writable + IO.select(nil, [s1], nil, 1) + else + assert_same(s1, rv) + break + end + end + s1.puts("abc") + assert_equal("abc\n", s1.gets) + ensure + sock&.close end - ensure - th.join if th - s1.close if s1 - s2.close if s2 - sock1.close if sock1 - sock2.close if sock2 - accepted.close if accepted.respond_to?(:close) end def test_connect_accept_nonblock - ctx = OpenSSL::SSL::SSLContext.new - ctx.cert = @svr_cert - ctx.key = @svr_key - - sock1, sock2 = socketpair - - th = Thread.new { - s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx) - 5.times { + server_proc = proc do |sock| + s2 = OpenSSL::SSL::SSLSocket.new(sock, make_server_context) + rv = 5.times { begin break s2.accept_nonblock rescue IO::WaitReadable @@ -171,30 +161,29 @@ def test_connect_accept_nonblock rescue IO::WaitWritable IO.select(nil, [s2], nil, 1) end - sleep 0.2 } - } - - s1 = OpenSSL::SSL::SSLSocket.new(sock1) - 5.times { - begin - break s1.connect_nonblock - rescue IO::WaitReadable - IO.select([s1], nil, nil, 1) - rescue IO::WaitWritable - IO.select(nil, [s1], nil, 1) - end - sleep 0.2 - } - - s2 = th.value - - s1.print "a\ndef" - assert_equal("a\n", s2.gets) - ensure - sock1&.close - sock2&.close - th&.join + assert_same(s2, rv) + assert_equal("a\n", s2.gets) + s2.puts("b") + end + start_server_proc(server_proc) do |port| + sock = TCPSocket.new("127.0.0.1", port) + s1 = OpenSSL::SSL::SSLSocket.new(sock) + rv = 5.times { + begin + break s1.connect_nonblock + rescue IO::WaitReadable + IO.select([s1], nil, nil, 1) + rescue IO::WaitWritable + IO.select(nil, [s1], nil, 1) + end + } + assert_same(s1, rv) + s1.print "a\ndef" + assert_equal("b\n", s1.gets) + ensure + sock&.close + end end def test_low_level_socket @@ -1039,14 +1028,6 @@ def create_null_byte_SAN_certificate(critical = false) cert end - def socketpair - if defined? UNIXSocket - UNIXSocket.pair - else - Socket.pair(Socket::AF_INET, Socket::SOCK_STREAM, 0) - end - end - def test_keylog_cb omit "Keylog callback is not supported" if libressl? @@ -1140,70 +1121,41 @@ def test_tlsext_hostname end def test_servername_cb_exception - sock1, sock2 = socketpair - - t = Thread.new { - s1 = OpenSSL::SSL::SSLSocket.new(sock1) - s1.hostname = "localhost" + server_proc = proc do |sock| + sctx = make_server_context + sctx.servername_cb = lambda { |args| raise RuntimeError, "foo" } + ssl = OpenSSL::SSL::SSLSocket.new(sock, sctx) + assert_raise_with_message(RuntimeError, "foo") { ssl.accept } + end + start_server_proc(server_proc) do |port| + sock = TCPSocket.new("127.0.0.1", port) + ssl = OpenSSL::SSL::SSLSocket.new(sock) + ssl.hostname = "example.org" assert_raise_with_message(OpenSSL::SSL::SSLError, /unrecognized.name/i) { - s1.connect + ssl.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 + ensure + sock&.close + end end def test_servername_cb_raises_an_exception_on_unknown_objects - sock1, sock2 = socketpair - - t = Thread.new { - s1 = OpenSSL::SSL::SSLSocket.new(sock1) - s1.hostname = "localhost" - assert_raise(OpenSSL::SSL::SSLError) { s1.connect } - } - - 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 - sock2.close - t.kill.join - end - - def test_accept_errors_include_peeraddr - context = OpenSSL::SSL::SSLContext.new - context.cert = @svr_cert - context.key = @svr_key - - server = TCPServer.new("127.0.0.1", 0) - port = server.connect_address.ip_port - - ssl_server = OpenSSL::SSL::SSLServer.new(server, context) - - t = Thread.new do - assert_raise_with_message(OpenSSL::SSL::SSLError, /peeraddr=127\.0\.0\.1/) do - ssl_server.accept - end + server_proc = proc do |sock| + sctx = make_server_context + sctx.servername_cb = lambda { |args| Object.new } + ssl = OpenSSL::SSL::SSLSocket.new(sock, sctx) + assert_raise(ArgumentError) { ssl.accept } + end + start_server_proc(server_proc) do |port| + sock = TCPSocket.new("127.0.0.1", port) + ssl = OpenSSL::SSL::SSLSocket.new(sock) + ssl.hostname = "example.org" + assert_raise_with_message(OpenSSL::SSL::SSLError, /unrecognized.name/i) { + ssl.connect + } + ensure + sock&.close end - - sock = TCPSocket.new("127.0.0.1", port) - sock << "\x00" * 1024 - - assert t.join - ensure - sock&.close - server.close end def test_verify_hostname_on_connect @@ -1334,6 +1286,16 @@ def test_connect_certificate_verify_failed_exception_message } end + def test_connect_exception_message_include_peeraddr + start_server(ignore_listener_error: true) do |port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER + assert_raise_with_message(OpenSSL::SSL::SSLError, /peeraddr=127\.0\.0\.1/) do + server_connect(port, ctx) { } + end + end + end + def check_supported_protocol_versions possible_versions = [ OpenSSL::SSL::SSL3_VERSION, @@ -1705,30 +1667,20 @@ def test_alpn_protocol_selection_ary end def test_alpn_protocol_selection_cancel - sock1, sock2 = socketpair - - ctx1 = OpenSSL::SSL::SSLContext.new - ctx1.cert = @svr_cert - ctx1.key = @svr_key - ctx1.alpn_select_cb = -> (protocols) { nil } - ssl1 = OpenSSL::SSL::SSLSocket.new(sock1, ctx1) - - ctx2 = OpenSSL::SSL::SSLContext.new - ctx2.alpn_protocols = ["http/1.1"] - ssl2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2) - - t = Thread.new { - ssl2.connect_nonblock(exception: false) - } - assert_raise_with_message(TypeError, /nil/) { ssl1.accept } - t.join - ensure - sock1&.close - sock2&.close - ssl1&.close - ssl2&.close - t&.kill - t&.join + server_proc = proc do |sock| + sctx = make_server_context + sctx.alpn_select_cb = -> (protocols) { nil } + ssl = OpenSSL::SSL::SSLSocket.new(sock, sctx) + assert_raise_with_message(TypeError, /nil/) { ssl.accept } + end + start_server_proc(server_proc) do |port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.alpn_protocols = ["http/1.1"] + # no_application_protocol alert + assert_raise_with_message(OpenSSL::SSL::SSLError, /alert number 120/) { + server_connect(port, ctx) { } + } + end end def test_npn_protocol_selection_ary @@ -1925,8 +1877,7 @@ def test_fallback_scsv # Here is not OK # TLS1.2 is supported, fallback to TLS1.1 (downgrade attack) and signaling the fallback # Server support better, so refuse the connection - sock1, sock2 = socketpair - begin + server_proc = proc do |sock| # This test is for the downgrade protection mechanism of TLS1.2. # This is why ctx1 bounds max_version == TLS1.2. # Otherwise, this test fails when using openssl 1.1.1 (or later) that supports TLS1.3. @@ -1935,27 +1886,21 @@ def test_fallback_scsv ctx1.security_level = 0 ctx1.min_version = 0 ctx1.max_version = OpenSSL::SSL::TLS1_2_VERSION - s1 = OpenSSL::SSL::SSLSocket.new(sock1, ctx1) - + s1 = OpenSSL::SSL::SSLSocket.new(sock, ctx1) + # AWS-LC has slightly different error messages in all-caps. + assert_raise_with_message(OpenSSL::SSL::SSLError, /inappropriate.fallback/i) { + s1.accept + } + end + start_server_proc(server_proc) do |port| 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|INAPPROPRIATE_FALLBACK/) { - s2.connect - } + assert_raise_with_message(OpenSSL::SSL::SSLError, /inappropriate.fallback/i) { + server_connect(port, ctx2) { } } - assert_raise_with_message(OpenSSL::SSL::SSLError, /inappropriate fallback|INAPPROPRIATE_FALLBACK/) { - s1.accept - } - t.join - ensure - sock1.close - sock2.close end end @@ -2371,15 +2316,12 @@ def test_security_level def test_dup ctx = OpenSSL::SSL::SSLContext.new - sock1, sock2 = socketpair - ssl = OpenSSL::SSL::SSLSocket.new(sock1, ctx) + Socket.open(:INET, :STREAM) { |sock| + ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) - assert_raise(NoMethodError) { ctx.dup } - assert_raise(NoMethodError) { ssl.dup } - ensure - ssl.close if ssl - sock1.close - sock2.close + assert_raise(NoMethodError) { ctx.dup } + assert_raise(NoMethodError) { ssl.dup } + } end def test_freeze_calls_setup @@ -2395,17 +2337,14 @@ def test_freeze_calls_setup end def test_fileno - ctx = OpenSSL::SSL::SSLContext.new - sock1, sock2 = socketpair - - socket = OpenSSL::SSL::SSLSocket.new(sock1) - server = OpenSSL::SSL::SSLServer.new(sock2, ctx) + Socket.open(:INET, :STREAM) { |sock| + ctx = OpenSSL::SSL::SSLContext.new + ssl = OpenSSL::SSL::SSLSocket.new(sock) + server = OpenSSL::SSL::SSLServer.new(sock, ctx) - assert_equal socket.fileno, socket.to_io.fileno - assert_equal server.fileno, server.to_io.fileno - ensure - sock1.close - sock2.close + assert_equal(sock.fileno, ssl.fileno) + assert_equal(sock.fileno, server.fileno) + } end def test_export_keying_material