|
#!/usr/bin/env ruby
|
|
|
|
# This code was written by Sam Stelfox (http://www.stelfox.net/) and is
|
|
# licensed under GPLv2
|
|
|
|
require 'openssl'
|
|
require 'socket'
|
|
|
|
# This bug has been tested on:
|
|
# ruby 2.2.0dev (2014-04-16 trunk 45603) [x86_64-linux]
|
|
# ruby 2.1.0p0 (2013-12-25 revision 44422) [x86_64-linux]
|
|
|
|
# Too connect to this server once it is running you can use the following
|
|
# command (only tested on Linux):
|
|
#
|
|
# openssl s_client -connect 127.0.0.1:4443
|
|
|
|
# This class is a work around for OpenSSL::SSL::SSLServer when a raw socket
|
|
# needs to be passed too it. The Ruby sample code I've been able to find for
|
|
# interacting with OpenSSL::SSL::SSLServer shows passing a TCPServer object
|
|
# which is not always ideal.
|
|
#
|
|
# TCPServer#accept only returns the socket file descriptor, while Socket#accept
|
|
# returns both the socket file descriptor and an Addrinfo object with
|
|
# information about the client connected.
|
|
class TweakedSSLServer < OpenSSL::SSL::SSLServer
|
|
# This method was pulled in it's entirety out of trunk with one line adjusted
|
|
# so it can handle both Socket and TCPServer.
|
|
def accept
|
|
sock, addrinfo = @svr.accept # This line was adjusted
|
|
begin
|
|
ssl = OpenSSL::SSL::SSLSocket.new(sock, @ctx)
|
|
ssl.sync_close = true
|
|
ssl.accept if @start_immediately
|
|
ssl
|
|
rescue SSLError => ex
|
|
sock.close
|
|
raise ex
|
|
end
|
|
end
|
|
end
|
|
|
|
# Helper method for generating a self-signed certificate so we're not dependent
|
|
# on outside files
|
|
def generate_cert(name, key)
|
|
cert = OpenSSL::X509::Certificate.new
|
|
cert.version = 2
|
|
cert.serial = 1
|
|
|
|
cert.not_before = (Time.now - 3600)
|
|
cert.not_after = (Time.now + 3600)
|
|
|
|
cert.subject = OpenSSL::X509::Name.parse("CN=#{name}")
|
|
cert.issuer = cert.subject
|
|
cert.public_key = key.public_key
|
|
|
|
cert.sign(key, OpenSSL::Digest::SHA384.new)
|
|
end
|
|
|
|
# Sample non-production key & certificate. These are not relevant to the issue,
|
|
# but are required to setup the SSL context.
|
|
SERVER_KEY = OpenSSL::PKey::RSA.new(1024)
|
|
SERVER_CERT = generate_cert('example', SERVER_KEY)
|
|
|
|
sock = Socket.new(Socket::AF_INET6, Socket::SOCK_STREAM, 0)
|
|
|
|
sock.bind(Socket.pack_sockaddr_in(4443, '::'))
|
|
|
|
ssl_context = OpenSSL::SSL::SSLContext.new
|
|
ssl_context.key = SERVER_KEY
|
|
ssl_context.cert = SERVER_CERT
|
|
|
|
# Switch the comments on these lines to see the issue with the SSLServer code.
|
|
# When the regular SSLServer is used, this will break with the following error
|
|
# after a client attempts to connect:
|
|
#
|
|
# wrong argument type Array (expected File)
|
|
# /home/dev_user/.rvm/rubies/ruby-head/lib/ruby/2.2.0/openssl/ssl.rb:230:in `initialize'
|
|
# /home/dev_user/.rvm/rubies/ruby-head/lib/ruby/2.2.0/openssl/ssl.rb:230:in `new'
|
|
# /home/dev_user/.rvm/rubies/ruby-head/lib/ruby/2.2.0/openssl/ssl.rb:230:in `accept'
|
|
# test_ssl_server.rb:84:in `block in <main>'
|
|
# test_ssl_server.rb:83:in `each'
|
|
# test_ssl_server.rb:83:in `<main>'
|
|
#
|
|
ssl_sock = OpenSSL::SSL::SSLServer.new(sock, ssl_context)
|
|
#ssl_sock = TweakedSSLServer.new(sock, ssl_context)
|
|
|
|
ssl_sock.listen(5)
|
|
|
|
# Properly close the socket on a Ctrl-C before closing.
|
|
trap(:INT) { ssl_sock.close; exit }
|
|
|
|
puts "Server ready"
|
|
|
|
# Standard simplified server loop
|
|
begin
|
|
ready_socket = IO.select([ssl_sock], nil, nil, 1)
|
|
if ready_socket
|
|
ready_socket[0].each do |s|
|
|
client = s.accept
|
|
client.write("Client successfully connected!\n")
|
|
client.close
|
|
end
|
|
end
|
|
rescue => e
|
|
puts e.message
|
|
puts e.backtrace
|
|
end while true
|
|
|