| #!/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 |
| |