2

After a long day I managed to get to the bottom of what I believe is a SSL/TLS cipher negotiation issue with a server that doesn't support the latest and greatest versions.

Stack:

  • Ubuntu 14.04 fully patched
  • OpenSSL 1.0.1f 6 Jan 2014
  • irb 0.9.6(09/06/30)
  • ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux] (using rbenv)

After 60 seconds the snippet below gives me an error:

require 'net/http'
require 'openssl'
uri = URI.parse('https://some_old_server/my/path')
http = Net::HTTP.new('some_old_server', 443)
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
http.use_ssl = true
response = http.request(Net::HTTP::Get.new(uri.request_uri))

Errno::ECONNRESET: Connection reset by peer - SSL_connect

If I add this to the code, it works:

(...)
http.ciphers = ['AES128-SHA']
(...)

 => #<Net::HTTPOK 200 OK readbody=true>

This isn't a ruby-specific issue but ideally there's a ruby solution. I can't lock the ciphers to 'AES128-SHA' because the same code handles a number of sites that may or may not support this cipher.

Has anyone ever come across this and found a generic solution?

EDIT: this seems to be caused by the "TLS hang bug" and was fixed in openssl 1.0.1g.

New question: is there a work-around that can be implemented on the ruby side?

More information.

A Gentoo server running OpenSSL 1.0.1j 15 Oct 2014 doesn't have this issue. I tried installing 1.0.1j on the Ubuntu 14.04 server, recompiling ruby (rbenv install 2.2.2) and the error was still present.

I've tried to monkey patch ext/openssl but that didn't work.

Using the whole cipher list from the link above doesn't work. However, using a small subset does work:

require 'net/http'
require 'openssl'
uri = URI.parse('https://some_old_server/my/path')
http = Net::HTTP.new('some_old_server', 443)
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
http.use_ssl = true
http.ciphers = %w{
          AES128-GCM-SHA256
          AES256-GCM-SHA384
          AES128-SHA256
          AES256-SHA256
          AES128-SHA
          AES256-SHA
          ECDHE-ECDSA-RC4-SHA
          ECDHE-RSA-RC4-SHA
          RC4-SHA
        }.join(":")
response = http.request(Net::HTTP::Get.new(uri.request_uri))

Openssl agrees with ruby (as it should). Running these, on the same system, replicates the issue as I see them in ruby:

openssl s_client -connect some_old_server:443
CONNECTED(00000003)
(...)
  write:errno=104
  ---
  no peer certificate available
  ---
  No client certificate CA names sent
  ---
  SSL handshake has read 0 bytes and written 295 bytes
  ---
  New, (NONE), Cipher is (NONE)
  Secure Renegotiation IS NOT supported
  Compression: NONE
  Expansion: NONE
  ---

Passing the cipher:

openssl s_client -cipher AES128-SHA -connect some_old_server:443
CONNECTED(00000003)
(...)
  ---
  No client certificate CA names sent
  ---
  SSL handshake has read 2721 bytes and written 425 bytes
  ---
  New, TLSv1/SSLv3, Cipher is AES128-SHA
  Server public key is 2048 bit
  Secure Renegotiation IS NOT supported
  Compression: NONE
  Expansion: NONE
  SSL-Session:
      Protocol  : TLSv1
      Cipher    : AES128-SHA
      Session-ID: removed
      Session-ID-ctx:
      Master-Key: removed
      Key-Arg   : None
      PSK identity: None
      PSK identity hint: None
      SRP username: None
      Start Time: 1454394952
      Timeout   : 300 (sec)
      Verify return code: 20 (unable to get local issuer certificate)
  ---

I read somewhere to use

http.ssl_options = OpenSSL::SSL::OP_ALL

but ssl_options isn't available in Net::HTTP on ruby 2.2.2.

Thiago Figueiro
  • 410
  • 5
  • 15
  • 1
    Maybe related: [How to set TLS context options in Ruby (like OpenSSL::SSL::SSL_OP_NO_SSLv2)](http://stackoverflow.com/q/22550213) – jww Feb 02 '16 at 16:57
  • Thanks, @jww - I tried that one and, while the code works, the connection still times-out. – Thiago Figueiro Feb 02 '16 at 20:24
  • A bit of tcpdump'ing tells me that the SSL hello on 1.0.1j has padding while 1.0.1f has not. Indeed the padding extension was added on 1.0.1g to "handle broken servers". I'll keep investigating this line. – Thiago Figueiro Feb 03 '16 at 00:17
  • This issue is in the OpenSSL FAQ: https://www.openssl.org/docs/faq.html#USER17 - other SO entries: https://stackoverflow.com/questions/11321403/openssl-trouble-with-ruby-1-9-3 – Thiago Figueiro Feb 03 '16 at 00:46
  • Another way of setting options: http://stackoverflow.com/a/30008472/27693 – Thiago Figueiro Feb 03 '16 at 02:07

1 Answers1

0

After spending more time on this than I'd care to admit, my solution was to upgrade from Ubuntu 14.04 to 15.10 which comes with OpenSSL 1.0.2d 9 Jul 2015.

While the TLS negotiation still hangs using the openssl CLI, in Ruby it does not:

require 'net/http'
require 'openssl'
require 'pp'

uri = URI.parse('https://broken_server/my/path')
http = Net::HTTP.new('broken_server', 443)

http.instance_eval {
  @ssl_context = OpenSSL::SSL::SSLContext.new
  @ssl_context.set_params({:options=>OpenSSL::SSL::OP_ALL})
}

http.verify_mode = OpenSSL::SSL::VERIFY_NONE
http.use_ssl = true
pp response = http.request(Net::HTTP::Get.new(uri.request_uri))

SSL context code above courtesy of @vinhboy.

The CLI equivalent of the above is turned-on with the -bugs option:

openssl s_client -bugs -connect broken_server:443
Community
  • 1
  • 1
Thiago Figueiro
  • 410
  • 5
  • 15