Class: EventMachine::AblyHttpRequest::HttpStubConnection

Inherits:
Connection
  • Object
show all
Includes:
Deferrable
Defined in:
lib/em-http/http_connection.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#parentObject

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_completedObject



33
34
35
# File 'lib/em-http/http_connection.rb', line 33

def connection_completed
  @parent.connection_completed
end

#create_certificate_storeObject



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

#hostObject



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.message)
  end
end

#ssl_handshake_completedObject



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

Returns:

  • (Boolean)


165
166
167
# File 'lib/em-http/http_connection.rb', line 165

def verify_peer?
  parent.connopts.tls[:verify_peer]
end