SSL Sockets

SSL provides a mechanism for exchanging data securely over sockets using public key cryptography.

SSL sockets don’t replace TCP sockets, but they allow you to ‘upgrade’ plain ol' insecure socket to secure SSL sockets. You can add a secure layer, if you will, on top of your TCP sockets.

Note that a socket can be upgraded to SSL, but a single socket can’t do both SSL and non-SSL communication. When using SSL, end-to-end communication with the receiver will all be done using SSL. Otherwise there’s no security.

For services that need to be available over SSL, as well as insecure over plain TCP, two ports (and two sockets) are required. HTTP is a common example of this: insecure HTTP traffic happens on port 80 by default, whereas HTTPS (HTTP over SSL) communication happens on port 443 by default.

So any TCP socket can be transformed into an SSL socket. In Ruby this is most often done using the openssl library included in the standard library. Here’s an example:

require 'socket'
require 'openssl'

def main
  # Build the TCP server.
  server = TCPServer.new(4481)

  # Build the SSL context.
  ctx = OpenSSL::SSL::SSLContext.new
  ctx.cert, ctx.key = create_self_signed_cert(
    1024, 
    [['CN', 'localhost']], 
    "Generated by Ruby/OpenSSL"
  )
  ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER

  # Build the SSL wrapper around the TCP server.
  ssl_server = OpenSSL::SSL::SSLServer.new(server, ctx)

  # Accept connections on the SSL socket.
  connection = ssl_server.accept

  # Treat it like any other connection.
  connection.write("Bah now")
  connection.close
end

# This code is unabashedly taken straight from webrick/ssl.
# It generates a self-signed SSL certificate suitable for use
# with a Context object.
def create_self_signed_cert(bits, cn, comment)
  rsa = OpenSSL::PKey::RSA.new(bits){|p, n|
    case p
    when 0; $stderr.putc "."  # BN_generate_prime
    when 1; $stderr.putc "+"  # BN_generate_prime
    when 2; $stderr.putc "*"  # searching good prime,
      # n = #of try,
      # but also data from BN_generate_prime
    when 3; $stderr.putc "\n" # found good prime, n==0 - p, n==1 - q,
      # but also data from BN_generate_prime
    else;   $stderr.putc "*"  # BN_generate_prime
    end
  }
  cert = OpenSSL::X509::Certificate.new
  cert.version = 2
  cert.serial = 1
  name = OpenSSL::X509::Name.new(cn)
  cert.subject = name
  cert.issuer = name
  cert.not_before = Time.now
  cert.not_after = Time.now + (365*24*60*60)
  cert.public_key = rsa.public_key

  ef = OpenSSL::X509::ExtensionFactory.new(nil,cert)
  ef.issuer_certificate = cert
  cert.extensions = [
    ef.create_extension("basicConstraints","CA:FALSE"),
    ef.create_extension("keyUsage", "keyEncipherment"),
    ef.create_extension("subjectKeyIdentifier", "hash"),
    ef.create_extension("extendedKeyUsage", "serverAuth"),
    ef.create_extension("nsComment", comment),
  ]
  aki = ef.create_extension("authorityKeyIdentifier",
                            "keyid:always,issuer:always")
  cert.add_extension(aki)
  cert.sign(rsa, OpenSSL::Digest::SHA1.new)

  return [ cert, rsa ]
end

main

That bit of code generates a self-signed SSL certificate and uses that to support the SSL connection. The certificate is the cornerstone of security for SSL. Without it you’re basically just using a fancy insecure connection.

Likewise, setting verify_mode = OpenSSL::SSL::VERIFY_PEER is essential to the security of your connection. Many Ruby libraries default that value to OpenSSL::SSL::VERIFY_NONE. This is a more lenient setting that will allow non-verified SSL certificates, forgoing much of the assumed security provided by the certificates. This issue has been discussed at length in the Ruby community.

So once you have that server running you can attempt to connect to it using a regular ol' TCP connection with netcat:

$ echo hello | nc localhost 4481

Upon doing so your server will crash with an OpenSSL::SSL::SSLError. This is a good thing!

The server refused to accept a connection from an insecure client and, so, raised an exception. Boot up the server again and we’ll connect to it using an SSL-secured Ruby client:

require 'socket'
require 'openssl'

# Create the TCP client socket.
socket = TCPSocket.new('0.0.0.0', 4481)

ssl_socket = OpenSSL::SSL::SSLSocket.new(socket)
ssl_socket.connect

ssl_socket.read

Now you should see both programs exit successfully. In this case there was successful SSL communication between server and client.

In a typical production setting you wouldn’t generate a self-signed certificate (that’s suitable only for development/testing). You’d usually buy a certificate from a trusted source . The SSL source would provide you with the cert and key that you need for secure communication and those would take the place of the self-signed certificate in our example.