Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pure Ruby EM SSL (working start_tls) #712

Merged
merged 1 commit into from
Apr 25, 2016

Conversation

portertech
Copy link
Contributor

@portertech portertech commented Apr 16, 2016

This pull request adds SSL/TLS support to the pure Ruby EM implementation.

@amdprophet & I went into the belly of the beast that is em/pure_ruby. The pure Ruby implementation leaves much to be desired, but it's a start!

$ rake test_em_pure_ruby                                         
EM Library Type: pure_ruby
Loaded suite /home/portertech/.gem/ruby/2.3.0/gems/rake-11.1.2/lib/rake/rake_test_loader
Started
.........
======================================================================================================================================
omitted. [test_no_dhparam(TestSslDhParam)]
/home/portertech/projects/sensu/eventmachine/tests/test_ssl_dhparam.rb:49:in `test_no_dhparam'
======================================================================================================================================
.............
======================================================================================================================================
omitted. [test_accept_server(TestSslVerify)]
/home/portertech/projects/sensu/eventmachine/tests/test_ssl_verify.rb:101:in `test_accept_server'
======================================================================================================================================
======================================================================================================================================
omitted. [test_deny_server(TestSslVerify)]
/home/portertech/projects/sensu/eventmachine/tests/test_ssl_verify.rb:116:in `test_deny_server'
======================================================================================================================================
.

Finished in 1.25792021 seconds.
--------------------------------------------------------------------------------------------------------------------------------------
26 tests, 57 assertions, 0 failures, 0 errors, 0 pendings, 3 omissions, 0 notifications
100% passed
--------------------------------------------------------------------------------------------------------------------------------------
20.67 tests/s, 45.31 assertions/s

ctx.cert = OpenSSL::X509::Certificate.new(tls_parms[:cert_chain]) if tls_parms[:cert_chain]
ctx.key = OpenSSL::PKey::RSA.new(tls_parms[:priv_key]) if tls_parms[:priv_key]
verify_mode = OpenSSL::SSL::VERIFY_NONE
if tls_parms[:verify_peer] || tls_parms[:verify_peer] == 1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this redundant because 1 is truthy?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll happily remove the extra conditional, wasn't sure if all consuming libraries were following the rules and using a boolean.

@portertech
Copy link
Contributor Author

Looks like we do not want to set context ssl_version, but instead use context options or ciphers to limit (like the C++ reactor).

EM_PROTO_TLSv1_2 => OpenSSL::SSL::OP_NO_TLSv1_2
}.inject(0) do |options, (bits, option)|
options |= option if (bits & protocols_bitmask) == 0
options
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this better matches the C++ by looping over the entire array. Note that ssl_version might also be a non-array (but I think I coerce it to an array in lib/em/connection.rb, so by the time it gets to the reactor it should always be an array? gotta double check).

This is something I implemented differently in EM 1.2 vs. how Ruby does it, is allowing an array of possible ssl_versions. I want to propose this upstream; the way Ruby does it now makes it fairly hard to write "Allow the following SSL/TLS protocol versions: TLS 1.0, 1.1, or 1.2"

@portertech
Copy link
Contributor Author

@sodabrew I think I have taken this as far as I can for the time being. These changes allow Sensu to run on platforms that cannot successfully compile the c++ reactor 👍

@portertech
Copy link
Contributor Author

@amdprophet it's happening!

193

@portertech
Copy link
Contributor Author

I made sure to comment on the few SSL test omission lines.

@amdprophet
Copy link

giphy

@sodabrew
Copy link
Member

I'll give this another look over and run the test suite myself in a few hours. Assuming I don't find anything else at issue, are you still working on this, or ready for merge?

@portertech
Copy link
Contributor Author

@sodabrew ready for merge 👍

@sodabrew
Copy link
Member

The stock OS X Ruby 2.0 may have an older OpenSSL under the hood?

  NameError: uninitialized constant OpenSSL::SSL::SSLErrorWaitReadable

and I found myself replacing this nice Hash inject with a less elegant pile of ifs, because I don't have the TLSv1_1, TLSv1_2 macros available:

-      ssl_options = {
-        EM_PROTO_SSLv2 => OpenSSL::SSL::OP_NO_SSLv2,
-        EM_PROTO_SSLv3 => OpenSSL::SSL::OP_NO_SSLv3,
-        EM_PROTO_TLSv1 => OpenSSL::SSL::OP_NO_TLSv1,
-        EM_PROTO_TLSv1_1 => OpenSSL::SSL::OP_NO_TLSv1_1,
-        EM_PROTO_TLSv1_2 => OpenSSL::SSL::OP_NO_TLSv1_2
-      }.inject(OpenSSL::SSL::OP_ALL) do |options, (bits, option)|
-        options |= option if (bits & protocols_bitmask) == 0
-        options
-      end
+      options = OpenSSL::SSL::OP_ALL
+      # The basic SSL / TLS versions are widely defined across OpenSSL versions
+      options |= OpenSSL::SSL::OP_NO_SSLv2 if defined?(OpenSSL::SSL::OP_NO_SSLv2) && EM_PROTO_SSLv2 & protocols_bitmask == 0
+      options |= OpenSSL::SSL::OP_NO_SSLv3 if defined?(OpenSSL::SSL::OP_NO_SSLv3) && EM_PROTO_SSLv3 & protocols_bitmask == 0
+      options |= OpenSSL::SSL::OP_NO_TLSv1 if defined?(OpenSSL::SSL::OP_NO_TLSv1) && EM_PROTO_TLSv1 & protocols_bitmask == 0
+      # But these TLS 1.x versions are not defined until later OpenSSL versions
+      options |= OpenSSL::SSL::OP_NO_TLSv1_1 if defined?(OpenSSL::SSL::OP_NO_TLSv1_1) && EM_PROTO_TLSv1_1 & protocols_bitmask == 0
+      options |= OpenSSL::SSL::OP_NO_TLSv1_2 if defined?(OpenSSL::SSL::OP_NO_TLSv1_2) && EM_PROTO_TLSv1_2 & protocols_bitmask == 0

@portertech
Copy link
Contributor Author

@sodabrew would you like me to apply your patch to this pr?

@sodabrew
Copy link
Member

Yes please do. I hadn't looked at the context for SSLErrorWaitReadable to see if that can be wrapped in an if defined? block, but I think that is important to avoid breaking older OpenSSL versions.

@portertech
Copy link
Contributor Author

@sodabrew I'll make the necessary changes 👍 Thanks for catching these.

# SSLErrorWaitReadable and SSLErrorWaitWritable OpenSSL classes.
# Set them if not defined.
unless defined?(OpenSSL::SSL::SSLErrorWaitReadable)
OpenSSL::SSL::SSLErrorWaitReadable = OpenSSL::SSL::SSLError
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sodabrew WDYT?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is OK? I wonder if it will work-alike.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that this will rescue instances of OpenSSL::SSL::SSLError in the blocks for the Read/Write errors. I think the way to roll is to flip the situation around, so below you'd rescue a new class EventMachine::Error::SSLErrorWaitWritable, and then define it as:

if defined?(OpenSSL::SSL::SSLErrorWaitWritable)
  EventMachine::Error::SSLErrorWaitWritable = OpenSSL::SSL::SSLErrorWaitWritable
else
  class EventMachine::Error::SSLErrorWaitWritable < EventMachine::Error; end
end

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a better approach, I'll apply a similar patch 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Figuring out what openssl threw before wait writable etc.

@portertech
Copy link
Contributor Author

@sodabrew ready for your review & testing once again 👍

@portertech
Copy link
Contributor Author

925

@portertech
Copy link
Contributor Author

@sodabrew please let me know if there are any other changes that I need to make. We would love to see this make it into upstream 👍

@sodabrew
Copy link
Member

Would you mind squashing to fewer commits? I think just a few commits should suffice for the final form of this PR.

[pure_ruby_tls] initial implementation

[pure_ruby_tls] closer to working start_tls

[pure_ruby_tls] working start_tls implementation

[pure_ruby_tls] fixed connection completed

[pure_ruby_tls] debugging handshake errors when certs are used

[pure_ruby_tls] fully operational battle station

[pure_ruby_tls] implemented fail_if_no_peer_cert, cipher_list, and protocols_bitmask

[pure_ruby_tls] support testing pure ruby reactor

[pure_ruby_tls] ssl/tls tcp support support

[pure_ruby_tls] working tls server

[pure_ruby_tls] delete dup tests

[pure_ruby_tls] fixed tcp client ready?() for aix and solaris

[pure_ruby_tls] only use Socket::TCP_INFO on linux

[pure_ruby_tls] working ssl_handshake_completed

[pure_ruby_tls] default cert/key, error classes, working unbind, fix blocking eventable_write, and tests!

[pure_ruby_tls] changed default cert subject to include EM, removed verify_peer == 1

[pure_ruby_tls] working ssl verify with peer cert

[pure_ruby_tls] fixed ssl options, OP_ALL

[pure_ruby_tls] working get peer cert and cipher

[pure_ruby_tls] fixed ssl_verify_peer connection close, working sni_hostname

[pure_ruby_tls] working dhparam

[pure_ruby_tls] working ecdh curve on ruby 2.3

[pure_ruby_tls] omit verify peer tests that do not pass due to default server chain

[pure_ruby_tls] support older versions of ruby and openssl

[pure_ruby_tls] fixed ssl wait readable/writable error handling on older rubies

[pure_ruby_tls] older rubies missing default dh keys

[pure_ruby_tls] removed duplicate default cert creator
@portertech portertech force-pushed the feature/pure_ruby_tls branch from 891fbe7 to adafc7b Compare April 25, 2016 19:01
@portertech
Copy link
Contributor Author

@sodabrew commits clean up 👍

@sodabrew
Copy link
Member

Thank you!

@sodabrew sodabrew merged commit c17c518 into eventmachine:master Apr 25, 2016
@sodabrew sodabrew added this to the v1.2.1 milestone Apr 25, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants