Zack Hobson

SSL and Ruby Thursday January 23, 2014

Update: Jeff Hodges has corrected a statement in this post about the most recent OpenSSL providing secure defaults in Ruby. The error was my fault, I misread part of the email thread in question. A follow-up post is in progress, but I’ve corrected this post in the meantime.


Recently Jeff Hodges publicly disclosed the contents of a thread on the ruby-security mailing list. In this thread, Mr. Hodges pointed out that the lack of secure defaults means that users with older versions of OpenSSL will be susceptible to certain kinds of attacks. This generated some discussion in Ruby circles about the security responsibilities of Ruby’s volunteer maintainers.

At the same time, it’s been pointed out by Kenn White that open-source Ruby software already has a pretty wide-spread issue with not validating the server SSL certificate against a root CA, allowing easy man-in-the-middle attacks. In this context, better defaults are not much help, as verification is explicitly by-passed.

Full disclosure: My Harvest command line had this bug before I switched to Faraday, which handles this behavior correctly by default.

It’s probably important to you that your software uses SSL in the most effective way possible, so let’s see how to avoid the above issues in your own Ruby programs and environment.

Using the latest OpenSSL version

Unfortunately, at the time of this writing, updating the version of OpenSSL used by Ruby will not necessarily provide the most secure defaults. Hopefully this will be fixed in Ruby itself.

To make sure your Ruby installation is using the latest OpenSSL, you need to know which version of OpenSSL your Ruby is using, and how to update it.

First, we’ll invoke ruby on the command line to see which version of OpenSSL it was built with, and whether it matches the latest version listed on the OpenSSL web site:

ruby -ropenssl -e 'puts OpenSSL::OPENSSL_VERSION'

Helpfully, this command reports both the version and release date of the OpenSSL library used by your Ruby installation. If it’s out of date, you’ll need to update OpenSSL and then rebuild Ruby.

The method for updating your OpenSSL installation and Ruby varies with your system. It’s pretty straightforward on a Mac using Homebrew and rbenv:

brew update
brew upgrade openssl
rbenv install -f `rbenv version`

On my machine, this process ensured that my Ruby installation utilized the latest OpenSSL, which is 1.0.1f at the time of this writing.

Verifying your SSL certificate

In SSL, trust is every bit as important as secrecy. With no way to verify identity, you have no way to verify that someone else isn’t just reading your data and passing it on. This is the reason we have root certs to begin with: They’re the root of the trust network that verifies the owner of the domain is who they say they are.

In order to verify the SSL peer with Ruby’s built-in OpenSSL and Net::HTTP, you need to specify the correct mode and cert storage location. Here is a simple example of a wrapper method that creates a Net::HTTP connection using peer verification:

require 'net/http'
require 'openssl'
def verified_peer_connect hostname
  Net::HTTP.new(hostname, 443).tap do |conn|
    conn.use_ssl = true
    conn.verify_mode = OpenSSL::SSL::VERIFY_PEER
    conn.cert_store = OpenSSL::X509::Store.new
    conn.cert_store.set_default_paths
  end
end

# Example: fetch the Google home page
conn = verified_peer_connect('www.google.com')
conn.get('/')

Of course, SSL will work without these settings, as those scores of open source examples can attest. The point is to more fully utilize the capabilities of SSL, including peer verification.

Ruby and Rails have been in the spotlight for security issues in the recent past, so it’s a good idea these days to be especially vigilant about security updates and best practices. Regardless of any disagreement surrounding disclosure or default ciphers, these issues can be avoided entirely with just a little bit of up-front effort.