Class: EventMachine::AblyHttpRequest::HttpStubConnection
- Inherits:
-
Connection
- Object
- Connection
- EventMachine::AblyHttpRequest::HttpStubConnection
- Includes:
- Deferrable
- Defined in:
- lib/em-http/http_connection.rb
Instance Attribute Summary collapse
-
#parent ⇒ Object
Returns the value of attribute parent.
Instance Method Summary collapse
- #connection_completed ⇒ Object
- #create_certificate_store ⇒ Object
- #host ⇒ Object
- #receive_data(data) ⇒ Object
- #ssl_handshake_completed ⇒ Object
-
#ssl_verify_peer(cert_string) ⇒ Object
TLS verification support, original implementation by Mislav Marohnić github.com/lostisland/faraday/blob/63cf47c95b573539f047c729bd9ad67560bc83ff/lib/faraday/adapter/em_http_ssl_patch.rb.
- #unbind(reason = nil) ⇒ Object
- #verify_peer? ⇒ Boolean
Instance Attribute Details
#parent ⇒ Object
Returns the value of attribute parent.
18 19 20 |
# File 'lib/em-http/http_connection.rb', line 18 def parent @parent end |
Instance Method Details
#connection_completed ⇒ Object
33 34 35 |
# File 'lib/em-http/http_connection.rb', line 33 def connection_completed @parent.connection_completed end |
#create_certificate_store ⇒ Object
173 174 175 176 177 178 179 |
# File 'lib/em-http/http_connection.rb', line 173 def create_certificate_store store = OpenSSL::X509::Store.new store.set_default_paths ca_file = parent.connopts.tls[:cert_chain_file] store.add_file(ca_file) if ca_file store end |
#host ⇒ Object
169 170 171 |
# File 'lib/em-http/http_connection.rb', line 169 def host parent.connopts.host end |
#receive_data(data) ⇒ Object
25 26 27 28 29 30 31 |
# File 'lib/em-http/http_connection.rb', line 25 def receive_data(data) begin @parent.receive_data data rescue EventMachine::Connectify::CONNECTError => e @parent.close(e.) end end |
#ssl_handshake_completed ⇒ Object
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/em-http/http_connection.rb', line 96 def ssl_handshake_completed # Warning message updated by Ably — the previous message suggested that # when verify_peer is false, the server certificate would be verified # but not checked against the hostname. This is not true — when # verify_peer is false, the server certificate is not verified at all. unless verify_peer? warn "[WARNING; ably-em-http-request] TLS server certificate validation is disabled (use 'tls: {verify_peer: true}'), see" + " CVE-2020-13482 and https://github.com/igrigorik/em-http-request/issues/339 for details" unless parent.connopts.tls.has_key?(:verify_peer) return true end # It’s not great to have to perform the server certificate verification # after the handshake has completed, because it means: # # - We have to be sure that we don’t send any data over the TLS # connection until we’ve verified the certificate. Created # https://github.com/ably/ably-ruby/issues/400 to understand whether # there’s anything we need to change to be sure of this. # # - If verification does fail, we have no way of failing the handshake # with a bad_certificate error. # # Unfortunately there doesn’t seem to be a better alternative within # the TLS-related APIs provided to us by EventMachine. (Admittedly I am # not familiar with EventMachine.) # # (Performing the verification post-handshake is not new to the Ably # implementation of certificate verification; the previous # implementation performed hostname verification after the handshake # was complete.) # I was quite worried by the description in the aforementioned issue # eventmachine/eventmachine#954 of how EventMachine "ignores all errors # from the chain construction" and hence I don’t know if there is some # weird scenario where, somehow, the calls to ssl_verify_peer terminate # with some intermediate certificate instead of with the certificate of # the server we’re communicating with. (It's quite possible that this # can’t occur and I’m just being paranoid, but I think a bit of # paranoia when it comes to security isn't a bad thing.) # # That's why, instead of the previous code which passed # certificate_store.verify the final certificate received by # ssl_verify_peer, I explicitly use the result of get_peer_cert, to be # sure that the certificate that we’re verifying is the one that the # server has demonstrated that they hold the private key for. server_certificate = OpenSSL::X509::Certificate.new(get_peer_cert) # A sense check to confirm my understanding of what’s in @peer_certificate_chain. # # (As mentioned above, unless something has gone very wrong, these two # certificates should be identical.) unless server_certificate.to_der == @peer_certificate_chain.last.to_der raise OpenSSL::SSL::SSLError.new(%(Peer certificate sense check failed for "#{host}")); end # Verify the server’s certificate against the default trust anchors, # aided by the intermediate certificates provided by the server. unless create_certificate_store.verify(server_certificate, @peer_certificate_chain[0...-1]) raise OpenSSL::SSL::SSLError.new(%(unable to verify the server certificate for "#{host}")) end # Verify that the peer’s certificate matches the hostname. unless OpenSSL::SSL.verify_certificate_identity(server_certificate, host) raise OpenSSL::SSL::SSLError.new(%(host "#{host}" does not match the server certificate)) else true end end |
#ssl_verify_peer(cert_string) ⇒ Object
TLS verification support, original implementation by Mislav Marohnić github.com/lostisland/faraday/blob/63cf47c95b573539f047c729bd9ad67560bc83ff/lib/faraday/adapter/em_http_ssl_patch.rb
Updated by Ably, here’s why:
We noticed that the existing verification mechanism is failing in the case where the certificate chain presented by the server contains a certificate that’s signed by an expired trust anchor. At the time of writing, this is the case with some Let’s Encrypt certificate chains, which contain a cross-sign by the expired DST Root X3 CA.
This isn’t meant to be an issue; the certificate chain presented by the server still contains a certificate that’s a trust anchor in most modern systems. So in the case where this trust anchor exists, OpenSSL would instead construct a certification path that goes straight to that anchor, bypassing the expired certificate.
Unfortunately, as described in github.com/eventmachine/eventmachine/issues/954#issue-1014842247, EventMachine misuses OpenSSL in a variety of ways. One of them is that it does not configure a list of trust anchors, meaning that OpenSSL is unable to construct the correct certification path in the manner described above.
This means that we end up in a degenerate situation where ssl_verify_peer just receives the certificates in the chain provided by the peer. In the scenario described above, one of these certificates is expired and hence the existing verification mechanism, which “verifies” each certificate provided to ssl_verify_peer, fails.
So, instead we remove the existing ad-hoc mechanism for verification (which did things I’m not sure it should have done, like putting non-trust-anchors into an OpenSSL::X509::Store) and instead employ OpenSSL (configured to use the system trust store, and hence able to construct the correct certification path) to do all the hard work of constructing the certification path and then verifying the peer certificate. (This is what, in my opinion, EventMachine ideally would be allowing OpenSSL to do in the first place. Instead, as far as I can tell, it pushes all of this responsibility onto its users, and then provides them with an insufficient API for meeting this responsibility.)
82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/em-http/http_connection.rb', line 82 def ssl_verify_peer(cert_string) # We use ssl_verify_peer simply as a mechanism for gathering the # certificate chain presented by the peer. In ssl_handshake_completed, # we’ll make use of this information in order to verify the peer. @peer_certificate_chain ||= [] begin cert = OpenSSL::X509::Certificate.new(cert_string) @peer_certificate_chain << cert true rescue OpenSSL::X509::CertificateError return false end end |
#unbind(reason = nil) ⇒ Object
37 38 39 |
# File 'lib/em-http/http_connection.rb', line 37 def unbind(reason=nil) @parent.unbind(reason) end |
#verify_peer? ⇒ Boolean
165 166 167 |
# File 'lib/em-http/http_connection.rb', line 165 def verify_peer? parent.connopts.tls[:verify_peer] end |