Mathish

the two halves of my tasty brain

OpenSSL in Ruby

The following code assumes that there is a subdirectory named certs containing known certificates in PEM format, and a subdir keys containing the client’s private RSA key. Further, there are lots of comments specific to my actual needs, namely exporting keys generated in Java using keytool for an Apache ActiveMQ message broker. Lastly, to use the ca_path method, the certs directory needs to be properly indexed using c_rehash (make sure the underlying version of openssl matches the version Ruby’s OpenSSL extension was built against, otherwise the hash algorithm may not be the same.)

The code that follow was written for my own benefit in understanding the mapping between the OpenSSL C API and API available in Ruby. The actual connection established is specific to my needs, but the OpenSSL setup should be pretty common. The type of the private key will differ depending upon the algorithm used during the generation of the certificate.

#!/usr/bin/env ruby

require 'socket'
require 'openssl'

SSL_HOST = 'localhost'
SSL_PORT = 61612
SSL_CERT_DIR = File.expand_path('certs', File.dirname(__FILE__))
SSL_BROKER_CERT = File.expand_path('broker.pem', SSL_CERT_DIR)
SSL_CLIENT_CERT = File.expand_path('client.pem', SSL_CERT_DIR)
SSL_CLIENT_KEY = File.expand_path('keys/client.key', File.dirname(__FILE__))

USE_BROKER_CERT_FILE = false
USE_CLIENT_CERT = false

# Things to note:
#
# I am using Apache ActiveMQ to test this SSL stuff.  It's java based, so there's
# a lot of `keytool` to `openssl` conversion going on, including an external Java
# program to dump the private key from the keytool keystore, because apparently
# keytool doesn't provide a way to do that.
#
# The commands used:
#
# 1. Active MQ client and broker cert generation:
# Create the broker cert/keypair
# > keytool -genkey -alias broker -keyalg RSA -keystore broker.ks
# Export the broker certificate (DER format)
# > keytool -export -alias broker -keystore broker.ks -file broker.der
# Create the client cert/keypair
# > keytool -genkey -alias client -keyalg RSA -keystore client.ks
# Add the broker cert to the client's trust-store (just to generate the trust stores,
# we don't use the client store on the client side, because we're using Ruby + OpenSSL)
# > keytool -import -alias broker -keystore client.ts -file broker.der
# Export client certificate
# > keytool -export -alias client -keystore client.ks -file client.der
# Import client cert into broker trust store
# > keytool -import -alias client -keystore broker.ts -file client.der
# This gets all the keys and whatnot set up for ActiveMQ.  Indeed, these steps
# can be found at: http://activemq.apache.org/how-do-i-use-ssl.html
#
# Next, we need to get these keys into OpenSSL acceptable forms
# (see: http://conshell.net/wiki/index.php/Keytool_to_OpenSSL_Conversion_tips)
# Convert the broker keytool DER cert into a PEM cert
# > openssl x509 -out broker.pem -outform pem -in broker.der -inform der
# Convert the client keytool DER cert into a PEM cert
# > openssl x509 -out client.pem -outform pem -in client.der -inform der
# As I am using ActiveMQ, there isn't a need to generate anything more on the
# broker side.  The client just needs the PEM form for SSL trust.
# However, when the broker requires ssl authentication (needClientAuth=true on the
# transport URI), we will need the client's private key from the keystore as well.
# Unfortunately, there is no keytool command (as far as I've seen so far) that will
# export this from a java keystore.  So, we make use of the DumpKey program copied
# from http://www.herongyang.com/crypto/Migrating_Keys_keytool_to_OpenSSL.html and
# found in examples/DumpKey.java to export the private key.
# Finally, we convert the private key output to a form usable by OpenSSL:
# > openssl enc -in client_bin.key -out client.key -a
# And wrap the output file with "-----BEGIN/END PRIVATE KEY-----" as outlined in
# http://www.herongyang.com/crypto/Migrating_Keys_keytool_to_OpenSSL_4.html
#
# Quite a bit of work... thanks Java!  Hopefully tests will require less work
# by using only OpenSSH within a stub broker.



tcp_sock = TCPSocket.new(SSL_HOST, SSL_PORT)
ctx = OpenSSL::SSL::SSLContext.new
ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT

if USE_BROKER_CERT_FILE
  # Specify the cert file directly
  ctx.ca_file = SSL_BROKER_CERT
  ctx.ca_path = nil
else
  # ... or the path to a series of cert files
  #
  # Theoretically, either method would work once c_rehash has been run on
  # a directory containing the certs.  However, with OpenSSL 1.0.0a, the
  # ca_path setting appears to not work at all, so I cannot test this
  # at the moment.
  #
  # Scratch that, OpenSSL didn't break anything, I did.
  # Ruby's OpenSSL extension was built against the OS X OpenSSL version (0.9.8l)
  # I was using c_rehash in such a way that it was calling on the 1.0.0a version
  # installed via port.  ca_path isn't broken, but the hashing mechanism between
  # 0.9.x and 1.0.x is different.
  ctx.ca_file = nil
  ctx.ca_path = SSL_CERT_DIR
end

if USE_CLIENT_CERT
  ctx.cert = OpenSSL::X509::Certificate.new(File.read(SSL_CLIENT_CERT))
  ctx.key = OpenSSL::PKey::RSA.new(File.read(SSL_CLIENT_KEY))
end

puts "CA File: #{ctx.ca_file}"
puts "CA Path: #{ctx.ca_path}"


ssl_sock = OpenSSL::SSL::SSLSocket.new(tcp_sock, ctx)

ssl_sock.sync_close = true

ssl_sock.connect
begin
  ssl_sock.post_connection_check('Apache ActiveMQ')
rescue => ex
  puts "!!!!!! WARNING !!!!!!!"
  puts
  puts "Post check failed"
  puts "#{ex.inspect}"
  puts
  puts "!!!!!! WARNING !!!!!!!"
end

puts
puts ssl_sock.peer_cert_chain.inspect
puts

# ssl_sock.puts 'GET / HTTP/1.1'
# ssl_sock.puts "Host: #{SSL_HOST}"
# ssl_sock.puts 'Connection: close'
# ssl_sock.puts
# 
# in_body = false
# while line = ssl_sock.gets
#   in_body ||= line.chomp.empty?
#   puts line if !in_body
# end

ssl_sock.puts "CONNECT\n\n\x00"
#sleep(2)
t = Thread.new(ssl_sock) do |s|
  read_it = ""
  while (c = s.getc rescue nil)
    if c.ord == 0
      puts "Read Frame: #{read_it}"
      read_it = ""
    else
      read_it << c
    end
  end
  #s.puts "DISCONNECT\n\n\x00"
  #puts "Finally: #{s.gets}"
end
ssl_sock.puts "SUBSCRIBE\nid:sub-001\ndestination:/queue/testing/ssl\nack:auto\n\n\000"

ssl_sock.puts "SEND\ndestination:/queue/testing/ssl\nreceipt:rcpt-001\ncontent-length:5\n\nhello\000"

sleep(2)
puts
puts "Disconnecting"
ssl_sock.puts "DISCONNECT\n\n\x00"
t.join

ssl_sock.close