lib/ably/realtime/connection/websocket_transport.rb in ably-1.1.4.rc vs lib/ably/realtime/connection/websocket_transport.rb in ably-1.1.4

- old
+ new

@@ -1,5 +1,7 @@ +require 'openssl' + module Ably::Realtime class Connection # EventMachine WebSocket transport # @api private class WebsocketTransport < EventMachine::Connection @@ -14,14 +16,17 @@ :disconnecting, :disconnected ) include Ably::Modules::StateEmitter + attr_reader :host + def initialize(connection, url) @connection = connection @state = STATE.Initialized @url = url + @host = URI.parse(url).hostname setup_event_handlers end # Disconnect the socket transport connection and write all pending text. @@ -47,11 +52,11 @@ # Remote TCP connection attempt completes successfully # Required {http://www.rubydoc.info/github/eventmachine/eventmachine/EventMachine/Connection EventMachine::Connection} interface def connection_completed change_state STATE.Connected - start_tls if client.use_tls? + start_tls(tls_opts) if client.use_tls? driver.start end # Called by the event loop whenever data has been received by the network connection. # Simply pass onto the WebSocket driver to process and determine content boundaries. @@ -75,10 +80,55 @@ # {http://www.rubydoc.info/gems/websocket-driver/0.3.5/WebSocket/Driver WebSocket::Driver} interface def write(data) send_data(data) end + # TLS verification support, original implementation by Mislav Marohnić: + # + # https://github.com/lostisland/faraday/commit/63cf47c95b573539f047c729bd9ad67560bc83ff + def ssl_verify_peer(cert_string) + cert = nil + begin + cert = OpenSSL::X509::Certificate.new(cert_string) + rescue OpenSSL::X509::CertificateError => e + disconnect_with_reason "Websocket host '#{host}' returned an invalid TLS certificate: #{e.message}" + return false + end + + @last_seen_cert = cert + + if certificate_store.verify(@last_seen_cert) + begin + certificate_store.add_cert(@last_seen_cert) + rescue OpenSSL::X509::StoreError => e + unless e.message == 'cert already in hash table' + disconnect_with_reason "Websocket host '#{host}' returned an invalid TLS certificate: #{e.message}" + return false + end + end + true + else + disconnect_with_reason "Websocket host '#{host}' returned an invalid TLS certificate" + false + end + end + + def ssl_handshake_completed + unless OpenSSL::SSL.verify_certificate_identity(@last_seen_cert, host) + disconnect_with_reason "Websocket host '#{host}' returned an invalid TLS certificate" + false + else + true + end + end + + def certificate_store + @certificate_store ||= OpenSSL::X509::Store.new.tap do |store| + store.set_default_paths + end + end + # True if socket connection is ready to be released # i.e. it is not currently connecting or connected def ready_for_release? !connecting? && !connected? end @@ -104,10 +154,16 @@ def connection @connection end + def disconnect_with_reason(reason) + client.logger.error { "WebsocketTransport: Disconnecting due to error: #{reason}" } + @reason_closed = reason + disconnect + end + def reason_closed @reason_closed end # Send object down the WebSocket driver connection as a serialized string/byte array based on protocol @@ -211,9 +267,19 @@ coerce_into: lambda do |event| raise KeyError, "Expected :protocol_message, :#{event} is disallowed" unless event == :protocol_message :protocol_message end ) + end + + # TLS options to pass to EventMachine::Connection#start_tls + # + # See https://www.rubydoc.info/github/eventmachine/eventmachine/EventMachine/Connection#start_tls-instance_method + def tls_opts + { + sni_hostname: host, + verify_peer: true, + } end end end end