cotag / ruby-tls

Generic TLS for ruby
Other
6 stars 5 forks source link

Is this usable? #5

Open HoneyryderChuck opened 7 years ago

HoneyryderChuck commented 7 years ago

I've been looking for an alternative for 'openssl', that plays well with jruby. This seemed to be it, as it supports ALPN as well. However, I'm struggling to make it work. I'm testing against an openssl-enabled client, And it fails when the tls server starts and sends the first message:

> OpenSSL::SSL::SSLError: SSL_connect returned=1 errno=0 state=SSLv3 read server hello A: wrong version number

The thing I saw was that there is (currently) no way to set the version.

Do you have any working example with sockets/network protocols?

HoneyryderChuck commented 7 years ago

Humm, I've worked around another issue, and finally managed to establish proper connectivity.

My client is using net/http, which allows to set the SSL version, and by default this is :SSLv23. This can be set to :TLSv1, :TLSv1_1, and :TLSv1_2. My test was failing because the minimal version set in my test was :TLSv1_1. After removing this, everything worked.

However, this means that ruby-tls currently only works with :SSLv23. ALPN protocols should only work with TLSv1_2, right?

I've tried to patch the lib and attach other openssl functions like TLSv1_2_server_method, but I'm getting the following error:

FFI::NotFoundError: Function 'TLSv1_2_server_method' not found in [libssl.dylib]

Does this make sense? This lib should be FFI'ing into the same libssl object file that my ruby's openssl is, right? Which is, in this case, ruby 2.4, which enables TLS 1-up-to-1.2 .

stakach commented 7 years ago

It doesn't use the ruby libssl and uses the systems libssl - which is probably where the issue is being introduced - we needed features before they were available in Ruby and it is used for SSL connections in https://github.com/cotag/libuv

HoneyryderChuck commented 7 years ago

It doesn't use the ruby libssl and uses the systems libssl

Indeed. I just confirmed that in my MacOS Sierra, the libssl.dylib file doesn't contain the necessary symbols:

$ nm -g /usr/lib/libssl.dylib | grep server_method
00000000000080c0 T _DTLSv1_server_method
000000000000b450 T _SSLv23_server_method
000000000000eff0 T _SSLv2_server_method
000000000001ad10 T _SSLv3_server_method
0000000000030830 T _TLSv1_server_method

However, this is one of 5 possible ssl lib files (in my system, I have libssl.0.9.7.dylib, libssl.0.9.8.dylib, libssl.35.dylib, libssl.39.dylib, libssl.dylib, and two of them contain the necessary symbols for TLS 1.1 and 1.2 support.

I don't know what's the best way to approach this, as I'm afraid that in most cases, the default lib file will be an old version of ssl (in the mac OS, waaaay old), and this disables a lot of the benefits I was hoping to get, like TLS 1.2 / ALPN support, which is necessary for http2.

stakach commented 7 years ago

It'll pick the first library it discovers - this hasn't been an issue for us as we do most of our work in docker containers - I think I used homebrew to install a new version of openssl for development work.

Ruby's version of OpenSSL isn't distributed as a dynamic link library so we can't access the methods we need without using a 3rd party lib.

One option would be to add openssl as a subproject to this project and compile it at GEM install time then link to the local copy of openssl - which would keep versions tightly coupled. I've done something similar for the libuv gem we maintain

HoneyryderChuck commented 7 years ago

I've also installed a new version of openssl using brew, and link it. But any program will compile with the system openssl by default. I've compiled latest cURL's against this new version, but had to specify the path myself. Unfortunately I can't specify the path using a 3rd-party gem.

Your last solution seems the one where you can control what you expect. I've seen that only SSLv23_client_method and SSLv23_server_method, and maybe the reason is this. I mean, what version of TLS are your docker programs using? SSLv23, right? You don't have a specific workaround for newer versions?

HoneyryderChuck commented 7 years ago

I've forked and am running a local version of this lib, and added this:

ffi_lib ['ssl.39', 'ssl.35', 'ssl']

attached the missing symbols and reran my tests. And it all just works. A notable change is that I get a protocol on handshake completion ("h2") instead of nothing (which is what happens if I run my tests in SSLv23 mode, for lack of ALPN support).

I'll see if I can create a client with this lib to demonstrate what happens locally, and also to get tests passing in jruby (which was my main point to use this lib, as jruby-openssl is wildly buggy and behind). I think that it makes sense to not expect stock-openssl to be updated, but what's definitely missing is a way to relefct is version (i.e. something like a Ruby::TLS::SSL.version, which returns i.e. 1.0.0). This could help me dynamically load the missing symbols.

I'll see if I can get a patch.

HoneyryderChuck commented 7 years ago

btw, what are you using for verifying certs against a chain? I see that this should be verified in the #verify_cb callback.

stakach commented 7 years ago

Awesome! Eventmachine has some good documentation on the verify_cb method http://www.rubydoc.info/github/eventmachine/eventmachine/EventMachine/Connection#ssl_verify_peer-instance_method

HoneyryderChuck commented 7 years ago

Thx for the link, but I'm afraid that it doesn't help me, at least what concerns the actual job of verifying the cert. My question would be more "how to do this in plain Ruby?", as ruby's "openssl" uses an openssl specific API for that apparently (it even uses the system default ca dir if none is provided).

So the question would be: does it bring value to show how to do this in plain ruby, or could this gem provide the same solution as openssl, i.e. verify the server certificate against the local provided ca bundle?

stakach commented 7 years ago

Looks like we could just use the ruby OpenSSL code: https://github.com/ruby/openssl/blob/master/lib/openssl/ssl.rb#L261

OpenSSL::SSL.verify_certificate_identity(cert, hostname)

HoneyryderChuck commented 7 years ago

Isn't that to verify the hostname (SNI)? Not that I have anything against it being done.

I meant more this snippet: https://github.com/ruby/openssl/blob/e72d960db2623b21ee001b5a7b9d9e6ff55bdf94/ext/openssl/ossl_ssl.c#L877-L892

If passed a ca file bundle and VERIFY_PEER. From what I saw, there's nothing like this happening, am I right?

stakach commented 7 years ago

That is the same as what this library does when you set the verify_peer: true option https://github.com/cotag/ruby-tls/blob/master/spec/verify_spec.rb#L78

Then the code to perform a standard verification is https://github.com/ruby/openssl/blob/master/lib/openssl/ssl.rb#L261

The blob you've referenced above is where they are setting up the callbacks

HoneyryderChuck commented 7 years ago

I have a test with verify_peer: true and no passed CA path or dir, and my test isn't failing. Given that this is a generated CA pem file for my tests, and my test certificates are signed by it, my tests should be failing but they're not.

The only thing that this verify_peer: true is deciding, is whether to call the verify_cb callback, and leave the actual verification job for the user. But it is not using SSL functions to verify the server certificate against a selected cert store.

If I do the same using OpenSSL, I get a:

ctx.ca_file_path = 'path/to/ca_file'
ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
OpenSSL::SSL::SSLError: SSL_connect returned=1 errno=0 state=error: certificate verify failed

Shouldn't this gem be doing the same and handle verification?

stakach commented 7 years ago

Ahh yeah, you could be right. Based on this example https://wiki.openssl.org/index.php/Manual:SSL_CTX_set_verify(3) I still think the verification has to happen in that callback.. Maybe we just do it for the user?

Or I could be looking at the wrong thing again and totally misunderstanding what we should be verifying

HoneyryderChuck commented 7 years ago

Maybe we just do it for the user?

I'd say so.

I'm just struggling with finding where openssl is doing it though. I had doubts about a previous comment and tried to implement the verify callback with OpenSSL::SSL.verify_certificate_identity, and I just confirmed that it only does hostname verification, so I'm back to square one...