Trying to create a simple Ruby server over SSL
Asked Answered
W

2

14

I am trying to create a simple SSL client and server in Ruby. But I'm getting a cryptic error message and the documentation is of no help.

Here is my server code:

#!/usr/bin/ruby

require "gserver"
require "openssl"

listeningPort = Integer(ARGV[0])

class Server < GServer
  def initialize(listeningPort)
    @sslContext = OpenSSL::SSL::SSLContext.new
    @sslContext.cert = OpenSSL::X509::Certificate.new(File.open("MyCert.pem"))
    super(listeningPort, "0.0.0.0")
  end
  def serve(io)
    begin
      ssl = OpenSSL::SSL::SSLSocket.new(io, @sslContext)
      ssl.sync_close = true
      ssl.connect
      while (lineIn = ssl.gets)
        lineIn = lineIn.chomp
        $stdout.puts "=> " + lineIn
        lineOut = "You said: " + lineIn
        $stdout.puts "<= " + lineOut
        ssl.puts lineOut
      end
    rescue
      $stderr.puts $!
    end
  end
end

server = Server.new(listeningPort)
server.start
server.join

The client code is similar:

#!/usr/bin/ruby

require "socket"
require "thread"
require "openssl"

host = ARGV[0]
port = Integer(ARGV[1])

socket = TCPSocket.new(host, port)
sslContext = OpenSSL::SSL::SSLContext.new
sslContext.cert = OpenSSL::X509::Certificate.new(File.open("MyCert.pem"))
ssl = OpenSSL::SSL::SSLSocket.new(socket, sslContext)
ssl.sync_close = true
ssl.connect
puts ssl.peer_cert   # this is nil

Thread.new {
  begin
    while lineIn = ssl.gets
      lineIn = lineIn.chomp
      $stdout.puts lineIn
    end
  rescue
    $stderr.puts "Error in input loop: " + $!
  end
}

while (lineOut = $stdin.gets)
  lineOut = lineOut.chomp
  ssl.puts lineOut
end

When I connect, I get this error on both the server and the client:

in `connect': SSL_connect returned=1 errno=0 state=SSLv2/v3 read server hello A: unknown protocol (OpenSSL::SSL::SSLError)

The problem could be that it doesn't trust the certificate (self-signed). I'm not sure how to tell the client to trust that certificate. Above, I have put the server's cert in the context, but that was just a shot in the dark. I'm not even sure my certificate is in an acceptable format (it is in base64 with the cert and the private key in the file). The documentation is very scant and there doesn't seem to be much on the web in this area either.

Any ideas?

Woodworm answered 3/5, 2011 at 16:47 Comment(1)
Ahh, this helps: https://mcmap.net/q/830770/-ruby-openssl-documentation-closedWoodworm
W
15

I figured it out, thanks to that link to some decent documentation.

For one thing, SSLSocket.connect() is only meant to be called on the client.

But the main problem is that I'm trying to take a GServer socket and upgrade it to SSL. Instead, I should use OpenSSL::SSL::SSLServer.

Also, I separated my certificate and private key into two files.

Here is the working server:

#!/usr/bin/ruby

require "socket"
require "openssl"
require "thread"

listeningPort = Integer(ARGV[0])

server = TCPServer.new(listeningPort)
sslContext = OpenSSL::SSL::SSLContext.new
sslContext.cert = OpenSSL::X509::Certificate.new(File.open("cert.pem"))
sslContext.key = OpenSSL::PKey::RSA.new(File.open("priv.pem"))
sslServer = OpenSSL::SSL::SSLServer.new(server, sslContext)

puts "Listening on port #{listeningPort}"

loop do
  connection = sslServer.accept
  Thread.new {
    begin
      while (lineIn = connection.gets)
        lineIn = lineIn.chomp
        $stdout.puts "=> " + lineIn
        lineOut = "You said: " + lineIn
        $stdout.puts "<= " + lineOut
        connection.puts lineOut
      end
    rescue
      $stderr.puts $!
    end
  }
end

And client:

#!/usr/bin/ruby

require "socket"
require "thread"
require "openssl"

host = ARGV[0]
port = Integer(ARGV[1])

socket = TCPSocket.new(host, port)
expectedCert = OpenSSL::X509::Certificate.new(File.open("cert.pem"))
ssl = OpenSSL::SSL::SSLSocket.new(socket)
ssl.sync_close = true
ssl.connect
if ssl.peer_cert.to_s != expectedCert.to_s
  stderrr.puts "Unexpected certificate"
  exit(1)
end

Thread.new {
  begin
    while lineIn = ssl.gets
      lineIn = lineIn.chomp
      $stdout.puts lineIn
    end
  rescue
    $stderr.puts "Error in input loop: " + $!
  end
}

while (lineOut = $stdin.gets)
  lineOut = lineOut.chomp
  ssl.puts lineOut
end
Woodworm answered 3/5, 2011 at 18:11 Comment(3)
Could you post the commands to generate cert.pem and priv.pem?Endor
openssl req -x509 -newkey rsa:4096 -keyout priv.pem -out cert.pem -days 365 -nodes # remove -nodes to add a pw to it. for generating themCharyl
the client implementation looks wrong to me. The solution just tests if server and client share have a pre-shared certificate. That's not how certification works. The client has a store and verify the certificate chain that the server presented. Check docs.ruby-lang.org/en/2.4.0/OpenSSL/X509/Store.html (main explanation). I don't if you will get only a valid ssl_socket . pay good attention to the ssl_context.verify_mode and dig for its meaning.Charyl
C
0

if you need to provide intermediate certificates, set sslContext.extra_chain_cert with them.

If use have letsencrypt certificates, use your fullchain.pem for sslContext.cert, privkey.pem for sslContext.key and ["chain.pem"] for sslContext.extra_chain_cert. Doing so, you can also test the connection to the server from your browser, with full chain verification.

Note: this complements the author's answer https://mcmap.net/q/818102/-trying-to-create-a-simple-ruby-server-over-ssl , but it was rejected by user @joe , because he understands that this is not a complement for the question. If you agree to him, try using this answer as solution to the question!

Charyl answered 4/5, 2017 at 10:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.