test/test_integration_ssl_session.rb
# frozen_string_literal: true
require_relative 'helper'
require_relative 'helpers/integration'
# These tests are used to verify that Puma works with SSL sockets. Only
# integration tests isolate the server from the test environment, so there
# should be a few SSL tests.
#
# For instance, since other tests make use of 'client' SSLSockets created by
# net/http, OpenSSL is loaded in the CI process. By shelling out with IO.popen,
# the server process isn't affected by whatever is loaded in the CI process.
class TestIntegrationSSLSession < TestIntegration
parallelize_me! if Puma::IS_MRI
require "openssl" unless defined?(::OpenSSL::SSL)
OSSL = ::OpenSSL::SSL
CLIENT_HAS_TLS1_3 = OSSL.const_defined? :TLS1_3_VERSION
GET = "GET / HTTP/1.1\r\nConnection: close\r\n\r\n"
RESP = "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 5\r\n\r\nhttps"
CERT_PATH = File.expand_path "../examples/puma/client-certs", __dir__
def teardown
return if skipped?
# stop server
sock = TCPSocket.new HOST, control_tcp_port
@ios_to_close << sock
sock.syswrite "GET /stop?token=#{TOKEN} HTTP/1.1\r\n\r\n"
sock.read
assert_match 'Goodbye!', @server.read
@server.close unless @server&.closed?
@server = nil
super
end
def bind_port
@bind_port ||= UniquePort.call
end
def control_tcp_port
@control_tcp_port ||= UniquePort.call
end
def set_reuse(reuse)
<<~RUBY
key = '#{File.expand_path '../examples/puma/client-certs/server.key', __dir__}'
cert = '#{File.expand_path '../examples/puma/client-certs/server.crt', __dir__}'
ca = '#{File.expand_path '../examples/puma/client-certs/ca.crt', __dir__}'
ssl_bind '#{HOST}', '#{bind_port}', {
cert: cert,
key: key,
ca: ca,
verify_mode: 'none',
reuse: #{reuse}
}
activate_control_app 'tcp://#{HOST}:#{control_tcp_port}', { auth_token: '#{TOKEN}' }
app do |env|
[200, {}, [env['rack.url_scheme']]]
end
RUBY
end
def with_server(config)
config_file = Tempfile.new %w(config .rb)
config_file.write config
config_file.close
config_file.path
# start server
cmd = "#{BASE} bin/puma -C #{config_file.path}"
@server = IO.popen cmd, 'r'
wait_for_server_to_boot log: false
@pid = @server.pid
yield
end
def run_session(reuse, tls = nil)
config = set_reuse reuse
with_server(config) { ssl_client tls_vers: tls }
end
def test_dflt
reused = run_session true
assert reused, 'session was not reused'
end
def test_dflt_tls1_2
reused = run_session true, :TLS1_2
assert reused, 'session was not reused'
end
def test_dflt_tls1_3
skip 'TLSv1.3 unavailable' unless Puma::MiniSSL::HAS_TLS1_3 && CLIENT_HAS_TLS1_3
reused = run_session true, :TLS1_3
assert reused, 'session was not reused'
end
def test_1000_tls1_2
reused = run_session '{size: 1_000}', :TLS1_2
assert reused, 'session was not reused'
end
def test_1000_10_tls1_2
reused = run_session '{size: 1000, timeout: 10}', :TLS1_2
assert reused, 'session was not reused'
end
def test__10_tls1_2
reused = run_session '{timeout: 10}', :TLS1_2
assert reused, 'session was not reused'
end
def test_off_tls1_2
ssl_vers = Puma::MiniSSL::OPENSSL_LIBRARY_VERSION
old_ssl = ssl_vers.include?(' 1.0.') || ssl_vers.match?(/ 1\.1\.1[ a-e]/)
skip 'Requires 1.1.1f or later' if old_ssl
reused = run_session 'nil', :TLS1_2
assert reused, 'session was not reused'
end
# TLSv1.3 reuse is always on
def test_off_tls1_3
skip 'TLSv1.3 unavailable' unless Puma::MiniSSL::HAS_TLS1_3 && CLIENT_HAS_TLS1_3
reused = run_session 'nil'
assert reused, 'TLSv1.3 session was not reused'
end
def client_skt(tls_vers = nil, session_pems = [])
ctx = OSSL::SSLContext.new
ctx.verify_mode = OSSL::VERIFY_NONE
ctx.session_cache_mode = OSSL::SSLContext::SESSION_CACHE_CLIENT
if tls_vers
if ctx.respond_to? :max_version=
ctx.max_version = tls_vers
ctx.min_version = tls_vers
else
ctx.ssl_version = tls_vers.to_s.sub('TLS', 'TLSv').to_sym
end
end
ctx.session_new_cb = ->(ary) { session_pems << ary.last.to_pem }
skt = OSSL::SSLSocket.new TCPSocket.new(HOST, bind_port), ctx
skt.sync_close = true
skt
end
def ssl_client(tls_vers: nil)
session_pems = []
skt = client_skt tls_vers, session_pems
skt.connect
skt.syswrite GET
skt.to_io.wait_readable 2
assert_equal RESP, skt.sysread(1_024)
skt.sysclose
skt = client_skt tls_vers, session_pems
skt.session = OSSL::Session.new(session_pems[0])
skt.connect
skt.syswrite GET
skt.to_io.wait_readable 2
assert_equal RESP, skt.sysread(1_024)
skt.session_reused?
ensure
skt&.sysclose unless skt&.closed?
end
end if Puma::HAS_SSL && Puma::IS_MRI