require "spec" require "socket" require "../../spec_helper" require "../../socket/spec_helper" require "../../../support/ssl" describe OpenSSL::SSL::Socket do describe OpenSSL::SSL::Socket::Server do it "auto accept client by default" do TCPServer.open("127.0.0.1", 0) do |tcp_server| server_context, client_context = ssl_context_pair spawn do OpenSSL::SSL::Socket::Client.open(TCPSocket.new(tcp_server.local_address.address, tcp_server.local_address.port), client_context, hostname: "example.com") do |socket| socket.print "hello" end end socket = tcp_server.accept ssl_server = OpenSSL::SSL::Socket::Server.new(socket, server_context) ssl_server.gets.should eq("hello") ssl_server.close end end it "doesn't accept client when specified" do TCPServer.open("127.0.0.1", 0) do |tcp_server| server_context, client_context = ssl_context_pair spawn do OpenSSL::SSL::Socket::Client.open(TCPSocket.new(tcp_server.local_address.address, tcp_server.local_address.port), client_context, hostname: "example.com") do |socket| socket.print "hello" end end socket = tcp_server.accept ssl_server = OpenSSL::SSL::Socket::Server.new(socket, server_context, accept: false) ssl_server.accept ssl_server.gets.should eq("hello") ssl_server.close end end end end private alias Server = OpenSSL::SSL::Socket::Server private alias Client = OpenSSL::SSL::Socket::Client private def socket_test(server_tests, client_tests) tcp_server = TCPServer.new("127.0.0.1", 0) server_context, client_context = ssl_context_pair OpenSSL::SSL::Server.open(tcp_server, server_context) do |server| spawn do Client.open(TCPSocket.new(tcp_server.local_address.address, tcp_server.local_address.port), client_context, hostname: "example.com") do |socket| client_tests.call(socket) end end client = server.accept server_tests.call(client) client.close end end describe OpenSSL::SSL::Socket do it "returns the cipher that is currently in use" do socket_test( server_tests: ->(client : Server) { client.cipher.should_not be_empty }, client_tests: ->(client : Client) { } ) end it "returns the TLS version" do socket_test( server_tests: ->(client : Server) { client.tls_version.should contain "TLS" }, client_tests: ->(client : Client) { } ) end it "returns the peer certificate" do socket_test( server_tests: ->(client : Server) { client.peer_certificate.should be_nil }, client_tests: ->(client : Client) { client.peer_certificate.should_not be_nil } ) end it "returns selected alpn protocol" do tcp_server = TCPServer.new("127.0.0.1", 0) server_context, client_context = ssl_context_pair server_context.alpn_protocol = "h2" client_context.alpn_protocol = "h2" OpenSSL::SSL::Server.open(tcp_server, server_context) do |server| spawn do Client.open(TCPSocket.new(tcp_server.local_address.address, tcp_server.local_address.port), client_context, hostname: "example.com") do |socket| socket.alpn_protocol.should eq("h2") end end client = server.accept client.alpn_protocol.should eq("h2") client.close end end it "accepts clients that only write then close the connection" do tcp_server = TCPServer.new("127.0.0.1", 0) server_context, client_context = ssl_context_pair # in tls 1.3, if clients don't read anything and close the connection # the server still try and write to it a ticket, resulting in a "pipe failure" # this context method disables the tickets which allows the behavior: server_context.disable_session_resume_tickets OpenSSL::SSL::Server.open(tcp_server, server_context) do |server| spawn do # the :sync_close aspect, as implemented in crystal, effects a unidirectional socket close from the client OpenSSL::SSL::Socket::Client.open(TCPSocket.new(tcp_server.local_address.address, tcp_server.local_address.port), client_context, hostname: "example.com", sync_close: true) do |socket| # doesn't read anything, just write and close connection immediately socket.puts "hello" end end client = server.accept # shouldn't raise "Broken pipe (Errno)" client.close end end it "closes connection to server that doesn't properly terminate SSL session" do tcp_server = TCPServer.new("127.0.0.1", 0) server_context, client_context = ssl_context_pair server_context.disable_session_resume_tickets # avoid Broken pipe client_successfully_closed_socket = Channel(Nil).new spawn do OpenSSL::SSL::Server.open(tcp_server, server_context, sync_close: true) do |server| server_client = server.accept # require client to close the socket from its side, without the server closing it, IIS behave this way. client_successfully_closed_socket.receive server_client.close end end socket = TCPSocket.new(tcp_server.local_address.address, tcp_server.local_address.port) socket = OpenSSL::SSL::Socket::Client.new(socket, client_context, hostname: "example.com", sync_close: true) socket.close client_successfully_closed_socket.send(nil) end it "interprets graceful EOF of underlying socket as SSL termination" do tcp_server = TCPServer.new("127.0.0.1", 0) server_context, client_context = ssl_context_pair server_context.disable_session_resume_tickets # avoid Broken pipe server_finished_reading = Channel(String | Exception).new spawn do OpenSSL::SSL::Server.open(tcp_server, server_context, sync_close: true) do |server| server_socket = server.accept received = server_socket.gets_to_end # interprets underlying socket close as a graceful EOF server_finished_reading.send(received) end rescue exc server_finished_reading.send exc end socket = TCPSocket.new(tcp_server.local_address.address, tcp_server.local_address.port) socket_ssl = OpenSSL::SSL::Socket::Client.new(socket, client_context, hostname: "example.com", sync_close: true) socket_ssl.print "hello" socket_ssl.flush # needed today see #5375 socket.close # close underlying socket without gracefully shutting down SSL at all server_received = server_finished_reading.receive if server_received.is_a?(Exception) raise server_received end server_received.should eq("hello") end it "calls on_server_name callback with client hostname" do pending_interpreted!("Leads to invalid memory access") tcp_server = TCPServer.new("127.0.0.1", 0) server_context, client_context = ssl_context_pair sni_hostname = nil server_context.on_server_name do |hostname| sni_hostname = hostname nil # continue with default context end spawn do Client.open(TCPSocket.new(tcp_server.local_address.address, tcp_server.local_address.port), client_context, hostname: "test.example.com") do |socket| socket.print "hello" end end OpenSSL::SSL::Server.open(tcp_server, server_context) do |server| client = server.accept client.gets.should eq("hello") client.close end sni_hostname.should eq("test.example.com") end it "handles exception in on_server_name callback" do pending_interpreted!("Leads to invalid memory access") tcp_server = TCPServer.new("127.0.0.1", 0) server_context, client_context = ssl_context_pair server_context.on_server_name do |hostname| raise "test error" end client_error = Channel(Exception?).new spawn do begin Client.open(TCPSocket.new(tcp_server.local_address.address, tcp_server.local_address.port), client_context, hostname: "test.example.com") do |socket| socket.print "hello" end client_error.send(nil) rescue ex client_error.send(ex) end end OpenSSL::SSL::Server.open(tcp_server, server_context) do |server| expect_raises(OpenSSL::SSL::Error) do server.accept end end client_error.receive.should be_a(OpenSSL::SSL::Error) end end