Today’s Ruby Tuesday takes a look at the OpenSSL::SSL::SSLContext#ssl_version.
At work today, I was pulled into a bit of a “fire”, where I was told that one of the sets of services our app at work depends on is going to be removing support for all but TLS 1.2.
Just doing a basic Net::HTTP.get
did not get us a result back, and we first had to figure out how to get TLS 1.2 as something that Ruby as the client would say it supports.
Before we can start testing any of this, we need to require openssl
and net/http
.
require 'openssl' # => true require 'net/http' # => true
It turns out when you don’t specify a version the default version is SSLv23, which can be found by looking at DEFAULT_PARAMS
on the SSLContext
.
OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:ssl_version] => "SSLv23"
Since TLSv1.2, is a “higher” version than SSLv23, we were getting errors back because we a connection using TLSv1.2 would never be negotiated.
To get the ability to support TLS generically, instead of just hardcoding a specific version, e.g. TLSv1
, or TLSv1_2
, the ssl_version
needs to be set to nil
to tell Ruby’s OpenSSL components to use TLS instead of SSL.
This opened up the question of if we set OpenSSL to be TLS instead of SSL, would the TLS Protocol negotiate down to SSL if we happen to have an endpoint we need to talk to that doesn’t currently support TLS, but only SSL.
Playing around with Net::HTTP.start
I was able to play with sending HTTPs requests using different settings for the ssl_version
.
As I was also testing against a local instance of nginx that would only support SSLv3 using a self-signed certificate, I had the veryfy_mode
to VERIFY_NONE
for testing. Note that I do NOT recommend this for real use cases.
The first helper method I created doesn’t specify a ssl_version
option, so it just uses the default ssl_version
setting.
def test_url_no_version(url) Net::HTTP.start(url.hostname, nil, use_ssl: url.scheme == "https", verify_mode: OpenSSL::SSL::VERIFY_NONE ) do |http| response = http.request(Net::HTTP::Get.new(url)) puts response.inspect response end end # => :test_url_no_version
The the next helper method I created sets the ssl_version
option to nil
to allow it to use TLS instead of SSL.
def test_url_ssl_version_is_nil(url) Net::HTTP.start(url.hostname, nil, use_ssl: url.scheme == "https", verify_mode: OpenSSL::SSL::VERIFY_NONE, ssl_version: nil ) do |http| response = http.request(Net::HTTP::Get.new(url)) puts response.inspect response end end # => :test_url
The the last helper method I created sets the ssl_version
option to :SSLv3
, which is the only version the webserver is setup to handle.
def test_url_ssl3(url) Net::HTTP.start(url.hostname, nil, use_ssl: url.scheme == "https", verify_mode: OpenSSL::SSL::VERIFY_NONE, ssl_version: :SSLv3 ) do |http| response = http.request(Net::HTTP::Get.new(url)) puts response.inspect response end end # => :test_url_ssl3
Now that we have these, we can test the results of asking the test webserver for a request and see what happens. We will also use Google as a baseline to compare against.
First we will test the verion where the ssl_version
is set to nil
. This would tell us if it would fall back to try a SSL variant.
test_url_ssl_version_is_nil(URI("https://www.google.com")) # #<Net::HTTPOK 200 OK readbody=true> # => #<Net::HTTPOK 200 OK readbody=true> test_url_ssl_version_is_nil(URI("https://localhost/index.html")) # OpenSSL::SSL::SSLError: SSL_connect returned=1 errno=0 state=SSLv2/v3 read server hello A: unsupported protocol # from /Users/proctor/.rvm/rubies/ruby-2.2.3/lib/ruby/2.2.0/net/http.rb:923:in `connect'
Google returns successfully, but the local test doesn’t so it doesn’t look to fall back to SSL from TLS.
Next we try with the default setting for ssl_version
.
test_url_no_version(URI("https://www.google.com")) # #<Net::HTTPOK 200 OK readbody=true> # => #<Net::HTTPOK 200 OK readbody=true> test_url_no_version(URI("https://localhost/index.html")) # OpenSSL::SSL::SSLError: SSL_connect returned=1 errno=0 state=SSLv2/v3 read server hello A: unsupported protocol # from /Users/proctor/.rvm/rubies/ruby-2.2.3/lib/ruby/2.2.0/net/http.rb:923:in `connect'
Google still returns successfully, but the local test case still doesn’t work.
Finally we will test with specifying SSL v3 specifically.
test_url_ssl3(URI("https://www.google.com")) # #<Net::HTTPOK 200 OK readbody=true> # => #<Net::HTTPOK 200 OK readbody=true> test_url_ssl3(URI("https://localhost/index.html")) # #<Net::HTTPOK 200 OK readbody=true> # => #<Net::HTTPOK 200 OK readbody=true>
And for this, Google and the local test both work. So we have shown that with the right ssl version specified, we do get a response back from our local test server, but the fallback from TLS to SSL doesn’t happen.
–Proctor