Search code examples
rubymacosopensslsniserver-name

OpenSSL::SSL::SSLContext SNI servername_cb Not Working


As referenced in OpenSSL::X509::Certificate Showing Certificate for Wrong Domain, I need to use TLSv1 or above and Server Name Indication extension.

Even with ssl_version and servername_cb set via the SSLContext, I'm still getting the wrong certificate for myproair.com.

  begin 
    timeout(1) do
      tcp_client = TCPSocket.new("#{instance["domain"]}", 443)
      ssl_context = OpenSSL::SSL::SSLContext.new()
      ssl_context.ssl_version = :TLSv1
      ssl_context.servername_cb = "https://#{instance["domain"]}"
      ssl_client = OpenSSL::SSL::SSLSocket.new(tcp_client, ssl_context)
      ssl_client.connect
      cert = OpenSSL::X509::Certificate.new(ssl_client.peer_cert)
      ssl_client.sysclose
      tcp_client.close
      #http://ruby-doc.org/stdlib-2.0/libdoc/openssl/rdoc/OpenSSL/X509/Certificate.html
      date = Date.parse((cert.not_after).to_s)
      row.push("#{date.strftime('%F')} #{cert.signature_algorithm} #{cert.subject.to_a.select{|name, _, _| name == 'CN' }.first[1]}".downcase.ljust(57))
    end
  rescue SocketError
    row.push("down".ljust(57))
  rescue Errno::ECONNREFUSED
    row.push("connection refused".ljust(57))
  rescue Errno::ECONNRESET
    row.push("connection reset".ljust(57))
  rescue Timeout::Error
    row.push("no 443 listener".ljust(57))
  rescue OpenSSL::SSL::SSLError
    row.push("bad certificate - ssl error".ljust(57))
  rescue Exception => ex
    row.push("error: #{ex.class} #{ex.message}".ljust(57))
  end

How do I set the server name in Ruby 2.0 on OS X?


$ ruby --version
ruby 2.0.0p481 (2014-05-08 revision 45883) [universal.x86_64-darwin14]

$ openssl version
OpenSSL 0.9.8zc 15 Oct 2014

Solution

  • Naturally you use the undocumented 'hostname' method for OpenSSL::SSLSocket!

    tcp_client = TCPSocket.new("#{instance["domain"]}", 443)
    ssl_context = OpenSSL::SSL::SSLContext.new()
    ssl_context.ssl_version = :TLSv1
    ssl_client = OpenSSL::SSL::SSLSocket.new(tcp_client, ssl_context)
    ssl_client.hostname = instance["domain"]
    ssl_client.connect
    cert = OpenSSL::X509::Certificate.new(ssl_client.peer_cert)
    ssl_client.sysclose
    tcp_client.close
    

    I discovered that here while writing code with a similar goal.