Request for feedback: Updating SSL to align with stdlib #957
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
First: this branch isn't ready to be merged. It's still a bit sloppy and incomplete. I'm looking for feedback on the basic approach, API design, etc. If the basic approach for the API is okay (whether or not the actual implementation is good), I'd like to split it into a series of much smaller simpler PRs (re-implementing as necessary). I'm hopeful that someone can tell me there's a simpler, easier way to accomplish what I wrote in this draft PR. ;)
Issues
EventMachine is full of old SSL code, which isn't following best practices. It is much easier to write insecure code than to write secure code: there are no secure defaults. Debugging errors can be a pain. EventMachine's interface with OpenSSL feels very different from stdlib's, which can lead to confusion (and bugs and security holes).
Additionally, the internal API for setting the context:
EventMachine.set_tls_params
is a big mess, and it's only going to get messier if we keep using the same approach.In particular, I wanted to add a default
ca_store
, the ability to configureca_path
andca_file
, and averify_hostname
option which uses theopenssl
gem's algorithm. I want to delegate all certificate and identity verification to the openssl library, and have the ability to easily diagnose specific errors from ruby.Proposal:
My proposal is to move as close as possible to stdlib's API and implementation , while maintaining a reasonable backward compatibility with the original EM API.
API changes
The API for internal methods, such as
EM.set_tls_params
,EM.start_tls
, and even e.g.EM.get_cipher_*
do not to be protected from change. Applications and other gems should not rely on those. However it might be worth doing a quick survey of reverse dependencies on rubygems.org to see if anyone is using them.Where the default
EM.start_tls
with no parameters defaults are insecure, we should break compatibility. The new defaults should mimic stdlib'sOpenSSL::SSL::SSLContext::DEFAULT_PARAMS
as much as it is feasible to do so. And we should run a RFC6125 hostname verification procedure by default just like stdlib (seeOpenSSL::SSL::SSLSocket#post_connection_check
andOpenSSL::SSL.verify_certificate_identity
).Instead of passing a dozen or more parameters from
EM::Connection#start_tls
toEM.set_tls_parms
tot_set_tls_parms
toConnectionDescriptor::SetTlsParms
to a bunch of instance variables onConnectionDescriptor
to instance variables onSslContext_t
andSslBox_t
to the actual underlyingSSL_CTX
andSSL
objects, we should create a ruby class that wraps all of theSSL_CTX
params. This current PR unloads that ruby object into into a C struct which is delivered into aSslContext_t
object. But ideally we could make our ruby object just aTypedData_Wrap_Struct
around theSSL_CTX
and replaceSslContext_t
altogether. Both the OpenSSL project and theopenssl
gem encapsulatedSSL_CTX
into a data type. So should EM! We can entirely dispose ofEM.set_tls_parms
and simply changestart_tls
to take ourSSL_CTX
wrapper and whatever other options remain for theSSL
object, e.g. SNI hostname.Adding a ruby class that is analogous to
OpenSSL::SSL::SSLSocket
seems less necessary: the EventMachine approach has always been query methods and callback methods on theEM::Connection
module/class. However, EM's C++ code has already found it useful to split offSslBox_t
, and keeping a small separation allows us to change our API inside ourSSL
andSSL_CTX
wrappers, while maintaining backwards compatibility viaEM:::Connection
. E.g. this could be an opportunity to have a singlecipher
method instead of threeget_cipher_*
methods. Andpeer_cert
can returnOpenSSL::X509::Certificate
objects (from stdlib), instead of simple PEM strings. AndEM::SSL::X509StoreContext
can be sent to theEM::SSL::Context#verify_callback
. Etc.The direction I'm going is that the ruby
EM::Connection
class doesn't need to know about SSL at all. Instead of callingEM::Connection#start_tls(**params)
and using the two connection callback methods, the new recommended API could mimic stdlib:backwards compatibility
EM::Connection#start_tls
as just a simple implementation of the above. It can support both the old API and the new API. If a context is sent in, great! Use it. Otherwise, take the arguments Hash and use it to build a new context like above.😜
EM::SSL::BackwardsCompatible
mixin that could be used to bring back the existing behavior. It could be included in the next release but deprecated and available but not included inEM::Connection
in the next release. That could allow the basicEM::Connection
code to be a little bit simpler.Implementation changes
So... this PR is sloppy. Sorry. Consider it a first-draft proof of concept?
Moving the implementation as closely as possible to stdlib isn't a nice-to-have. It's critically important: stdlib has far more active maintainers and resources devoted to it. In my branch, I copied and pasted large chunks of code (C and Ruby) from the
openssl
gem. This was just a quick exploration. Ideally, we should be able to "inherit" bugfixes and improvements from stdlib. This could be accomplished in a few ways:openssl
updates.SSL_CTX
,SSL
, etc fromVALUE
-- Another alternative might be to hack create actual stdlib objects, hack the wrappedT_DATA
objects to extract the underlyingSSL_CTX
,SSL
, etc, and callSSL_dup
to have something that's been configured by ruby but is completely under EM control. I'm not 100% certain that's feasible forSSL
but I think it could be doable with `SSL_CTX. One very big concern of mine is that I don't know C linking and ABIs well enough to know if it's actually safe to do this.openssl
include some of its header files intoproject.h
, and link directly against wherever possible. Again, I'm not really sure how "safe" this is wrt openssl ABIs. But it might enable us to use the C functions defined in the gem, without ABI incompatibility issues.OpenSSL::SSL::SSLSocket
to the underlying IO, and then proxy reads and writes through thatSSLSocket
. Essentially, replaceSslBox_t *SslBox
withVALUE SSLSocket
, and replace everyif (SslBox) {...}
inConnectionDescripter
with a similarif (SslSocket) { ... }
. Although calling it from C/C++, we'd only use the ruby API. In this case, EventMachine wouldn't even need to include the openssl headers.I did some research, and there do appear to be occasional minor ABI incompatibilities between different versions of openssl?
https://abi-laboratory.pro/?view=timeline&l=openssl So I don't know if that can be made safe. I'm only a C/C++ tourist. ;)
But pushing as much of our SSL code into stdlib's
openssl
gem as possible seems like a wise approach. Perhaps there's a set of patches we could submit to theopenssl
gem which might let us use it more directly without sacrificing performance?