Hostname does not match the server certificate - using old expired certificate - ruby

I am trying to connect via TCP socket to an old, unmaintained game server. When I try to do so using OpenSSL and Socketry, I get following error.
hostname "xxx" does not match the server certificate
This is how my code works.
ssl_context = OpenSSL::SSL::SSLContext.new
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
ssl_context.ciphers = "AES128-SHA"
ssl_context.cert = OpenSSL::X509::Certificate.new(File.open("certificate.crt"))
ssl_context.key = OpenSSL::PKey::RSA.new(File.open("certificate.key"))
socket = Socketry::SSL::Socket.new(ssl_context: ssl_context)
socket.connect(address, port)
I've had to force cipher to AES128-SHA, as otherwise I would get a different error.
SSL_connect returned=1 errno=0 state=error: dh key too small (OpenSSL::SSL::SSLError)
How can I bypass these errors and just force connection to said server? The certificate itself has expired 10 years ago.

Socketry doesn't include much documentation about this, so you need to turn to the source. Depending on exactly what you're trying to do, you might try one of the following:
Passing verify_hostname: false to Socket#connect.
Rescuing Socketry::SSL::HostnameError yourself.
Rescuing Socketry::SSL::CertificateVerifyError.
At least one of these ought to address your specific issue. If not, the source offers other places you might monkey-patch or refine for your use case.

Related

Faraday with net-http-persistent leaves connections open, resulting in "Errno::EMFILE: Too many open files"

We have a process (Rake task) that runs for a long period of time, making HTTPS requests to remote hosts using Faraday with the net_http_persistent adapter. After several hours to days of runtime, it stops making requests.
The process is also making some requests using Excon (to report exceptions to our exception-reporting service), and both HTTP clients are logging the same error, with slight difference in wording. The two errors are:
Faraday::SSLError: SSL_connect SYSCALL returned=5 errno=0 state=error: certificate verify failed
Excon::Error::Certificate: SSL_connect SYSCALL returned=5 errno=0 state=error: certificate verify failed (OpenSSL::SSL::SSLError) Unable to verify certificate. This may be an issue with the remote host or with Excon. Excon has certificates bundled, but these can be customized:
Our exception-reporting service is failing to receive any of these exceptions. So we only found these errors in the log.
In order to reproduce this issue, I ran a loop of Faraday requests until it failed. What I found is that while the exception raised was Faraday::SSLError, once I tried to perform any other action in the REPL that required the opening of a file, it raised Errno::EMFILE: Too many open files.
Finally I had my clue. The red herring was caused by the OpenSSL library catching the EMFILE system error, and instead raising a general SSL connection failure. Both Faraday and Excon (used by the exception-reporting tool) did this, making it impossible to see the real problem.
The underlying problem was that the process had reached the limit of open files. This was caused by the way that net-http-persistent keeps connections open until instructed to shut down combined with the fact that Faraday has no mechanism to instruct its adapter to shut down.
The solution was switching from net-http-persistent to Excon, which supports persistent connections as long as it's configured to do so:
Faraday.new(url: url) do |faraday|
faraday.adapter :excon, persistent: true
end

OpenSSL error from ruby script

SSL_connect returned=1 errno=0 state=SSLv2/v3 read server hello A: sslv3 alert handshake failure (OpenSSL::SSL::SSLError)
This error is a pretty common one to anyone using ruby on windows.
For me, the solution has always been this. It has worked great on any computer I have put it on and I've loved it.
Just this week it stopped working. Has anyone else had this happen?
I've tried getting the cert from curl again just in case the cert was no longer valid. I tried removing and adding the environmental I use mechanize and tried to reference it as suggested here.
I've also tried going to different urls, "https://google.com" and others, for example.
Nothing so far. Anyone have a better solution for this? It has happened on all windows computers that I have this cert referenced this way.
Your problem seems to be different to the one you linked to. There it failed to verify the certificate returned by the server. Here it erros earlier when it reads the server hello. Do you have any more debug information eg stacktrace or a pcap of the interaction?
A (highly) speculative explanation is that you sent an sslv2 compatible client hello and the server returned an ssl alert as it will only accept sslv3/tls format client hello

Disable SNI Extension for Ruby net/http - Using IP address with SSL/TLS

I have a Ruby polling script that runs on a set of servers in an IP range. I very strongly prefer to do this polling by IP address, not by hostname, because:
1) I have defined IP address ranges to poll, and hostnames are arbitrary/change a lot
2) Because they change a lot, most of the hostnames do not have a reverse DNS lookup, so I can't engineer a list of hostnames from IPs
Before our web servers had no problem with this polling, but on a new server that does not accept SSLv3 communication, this is the error I get when I run my poll:
/home/dashboard/.rvm/rubies/ruby-2.1.6/lib/ruby/2.1.0/net/http.rb:923:in `connect': SSL_connect returned=1 errno=0 state=unknown state: tlsv1 unrecognized name (OpenSSL::SSL::SSLError)
On the server side, this is the error:
nss_engine_init.c(1802): start function ownSSLSNISocketConfig for SNI
nss_engine_init.c(1827): Search [val = 172.16.99.18] failed, unrecognized name
When I run the poll with hostname, everything works fine.
Here is the crux of the HTTP Client code in Ruby:
def init_http(url)
uri = URI.parse(url)
http = Net::HTTP.new(uri.host, uri.port)
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
http.read_timeout = 10
http.use_ssl = true
#http.ssl_version = 'TLSv1'
return [http, uri]
end
As you can tell, I've been playing around with TLS and SSL version, because I figured that might be the issue. My next thought (that Google only has evidence of for Java) is, "How easy is it to just disable the SNI extension on my client?" The more general question is, "Can I keep using IP addresses with Ruby net/http while taking advantage of newer, more secure communication protocols?"
... tlsv1 unrecognized name (OpenSSL::SSL::SSLError)
This is in most cases not a problem you can solve by disabling SNI on the client side. SNI is required when you have multiple certificates on the same IP address and if you just connect by IP address and don't send the requested hostname (i.e. disabling SNI) the server will not know which certificate it should provide - which then results in the above error.
I very strongly prefer to do this polling by IP address, not by hostname, ...
If you have to deal with server which require SNI then you have to use SNI and you have to use SNI with the proper hostname, which is not necessarily the same name as you get from reverse lookup.
The easiest way to solve this is to add the patch that #steffenullrich mentioned for bug 10613.
All I did was look at the diff, and edit the file myself, but you can use the Linux patch tool if you're familiar with it.
For those who are unsure of where /net/http.rb is located, it is in the same location as the rest of your Ruby sources. For example, mine was here:
/home/myuser/.rvm/rubies/ruby-2.1.6/lib/net/http.rb
Once you patch the file, set the .disable_sni property of your HTTP object to true, and SNI will not be required, allowing the use of IP addresses in mixed-TLS communication.

SSL mode flags - verification of certificates: is it safe to use :none?

I am writing a soap request over SSL using Savon and HTTPi, a Ruby soap client and an interface for Ruby HTTP clients, respectively. Here's the code:
client = Savon::Client.new(original_class.constantize.wsdl_url)
client.http.auth.ssl.cert_key_file = "path_to_the_key"
client.http.auth.ssl.cert_key_password = 'secret'
client.http.auth.ssl.cert_file = "path_to_the_certification"
client.http.auth.ssl.verify_mode = :none
#response = client.request :ins0, action do
soap.body = encoded_body
end
That's the only way I get this to work. But, I know that there is three others verify modes, which are:
:peer (SSL_VERIFY_PEER)
:fail_if_no_peer_cert (SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
:client_once (SSL_VERIFY_CLIENT_ONCE)
If I change the verify mode to any other of the above, I get this error:
OpenSSL::SSL::SSLError: SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed
Then comes my questions (among others I have):
Am I doing wrong if I keep the verify mode to :none? Is there any lack of security?
What does the error really mean? That my code is wrong or that my certificate (which is self-assigned --- I am in development environment) is not good?
I read the OpenSSL documentation about verify modes:
http://www.openssl.org/docs/ssl/SSL_CTX_set_verify.html
About SSL_VERIFY_NONE, on Client Mode, says:
The result of the certificate verification process can be checked
after the TLS/SSL handshake using the SSL_get_verify_result(3)
function. The handshake will be continued regardless of the
verification result.
Should I be worried about it? Should I see verify mode :none as a dangerous thing?
I am asking that because since I can't make it work with the others verify modes, I would like to release the soap request over SSL feature the way it is working now. But I surely wouldn't do it if that could be dangerous.
It is NOT safe to set verify mode to :none (SSL_VERIFY_NONE). This opens up the code to being susceptible to man-in-the-middle attacks. With :none, there will be not server authentication. If an attacker intercepts the connection from my client, my client will not detect the difference and will give any sensitive data communicated through this socket to the attacker.
The mode flags :fail_if_not_peer_cert and :client_once are only for a server; meaning nothing to the client, it ignores them.
For client purpose, :peer (SSL_VERIFY_PEER) is the only one the matters. In order to use :peer, I need to have the root certificate used by the certificate in the client trustedstore.
A big thanks to Dave Thompson from OpenSSL User Support Mailing List. After I joined this list, I finally got help.

Add SSL to a connection

How do you secure a socket with SSL in Ruby when you need to communicate over plaintext first?
I can't use OpenSSL::SSL::SSLServer because it's the client's responsibility to request an SSL connection first
To put a long story short, I am attempting to implement RFC3207, where the client sends the keyword "STARTTLS", and then an SSL connection is created.
My question is "How do I create the SSL connection after the server has sent '220 OK'?"
I know I can use OpenSSL::SSL::SSLSocket on the client-side, but I have no idea what to do on the server-side
If you know how to do this in a language other than Ruby, just post the code and I'll translate it, I've been working on this for about 8 hours and I need everything I can get
I have asked in #ruby-lang, but with no avail, and I have tried wrapping Socket objects in SSLSockets on the server and client at the same time, but that isn't working either
In short, I'm very stuck, I need all the help I can get
I created this gist to illustrate how to set up a minimal TLS server. You may want to leave out lines 62-67, that was to illustrate a new feature on trunk.
But other than that, it's a fully working TLS server, you may build on it to add further functionality.
You may also want to change the server certificate's CN from "localhost" to a real domain if you want to use it seriously :)
You may notice that the largest part of the work is actually setting up the PKI aspects correctly. The core server part is this:
ctx = OpenSSL::SSL::SSLContext.new
ctx.cert = ... # the server certificate
ctx.key = ... # the key associated with this certificate
ctx.ssl_version = :SSLv23
tcps = TCPServer.new('127.0.0.1', 8443)
ssls = OpenSSL::SSL::SSLServer.new(tcps, ctx)
ssls.start_immediately = true
begin
loop do
ssl = ssls.accept
puts "Connected"
begin
while line = ssl.gets
puts "Client says: #{line}"
ssl.write(line) # simple echo, do something more useful here
end
ensure
ssl.close
end
end
ensure
tcps.close if tcps
end
You have to set the SSLServer's start_immediately field to false in order to start the SSL server in plain text mode. At any point (ie. when you receive the STARTTLS command from the client), you can call the SSLSocket's accept method to initiate SSL/TLS handshake. The client will of course have to agree to the protocol :)
Here is a sample server I wrote to test this:
#!/usr/bin/ruby
require 'socket';
require 'openssl';
certfile = 'mycert.pem';
port = 9002;
server = TCPServer.new( port );
# Establish an SSL context
sslContext = OpenSSL::SSL::SSLContext.new
sslContext.cert = OpenSSL::X509::Certificate.new( File.open( certfile ) );
sslContext.key = OpenSSL::PKey::RSA.new( File.open( certfile ) );
# Create SSL server
sslServer = OpenSSL::SSL::SSLServer.new( server, sslContext );
# Don't expect an immidate SSL handshake upon connection.
sslServer.start_immediately = false;
sslSocket = sslServer.accept;
sslSocket.puts( "Toast.." );
# Server loop
while line = sslSocket.gets
line.chomp!;
if "STARTTLS" == line
# Starting TLS
sslSocket.accept;
end
sslSocket.puts( "Got '#{line}'" );
end
sslSocket.close;
I'm sure the original poster knows how to test STARTTLS, but the rest of us might need this reminder. Actaually I'm normally using the utils from the GNUTLS package (gnutls-bin in debian/ubuntu) to test starttls, because it allows me to start the handshake whenever I want to:
$ gnutls-cli --starttls --port 9002 --insecure localhost
This connects in plain text TCP socket mode. Type some lines and get them echoed. This traffic is unencrypted. If you send STARTTLS, the sslSocket.accept is called, and the server waits for SSL handshake. Press ctrl-d (EOF) to start handshake from the gnutls client, and watch it establish an encrypted SSL connection. Subsequent lines will be echoed as well, but the traffic is now encrypted.
I have made some headway on this, saved for future use:
Yes, you should use OpenSSL::SSL::SSLSocket on both ends
On the server side, you must create an OpenSSL::SSL::SSLContext object, passing in a symbol with the protocol you wish to use and "_server" appended to the end, see OpenSSL::SSL::SSLContext::METHODS for what I mean, in short use ":TLSv1_server" for RFC3207 don't even need to do that, on the server side create the context with certs and then call #accept on the socket to wait for client transfer
Pass in SSL certificates to the ctx object
Edit as you please

Resources