class Mongo::Socket::SSL
Wrapper for TLS sockets.
@since 2.0.0
Attributes
@return [ SSLContext ] context The TLS context.
@return [ String ] host The host to connect to.
@return [ String ] host_name
The original host name.
@return [ Integer ] port The port to connect to.
Public Class Methods
Initializes a new TLS socket.
@example Create the TLS socket.
SSL.new('::1', 27017, 30)
@param [ String ] host The hostname or IP address. @param [ Integer ] port The port number. @param [ Float ] timeout The socket timeout value. @param [ Integer ] family The socket family. @param [ Hash ] options The options.
@option options [ Float ] :connect_timeout Connect timeout. @option options [ Address
] :connection_address Address
of the
connection that created this socket.
@option options [ Integer ] :connection_generation Generation of the
connection (for non-monitoring connections) that created this socket.
@option options [ true | false ] :monitor Whether this socket was
created by a monitoring connection.
@option options [ String ] :ssl_ca_cert The file containing concatenated
certificate authority certificates used to validate certs passed from the other end of the connection. Intermediate certificates should NOT be specified in files referenced by this option. One of :ssl_ca_cert, :ssl_ca_cert_string or :ssl_ca_cert_object (in order of priority) is required when using :ssl_verify.
@option options [ Array<OpenSSL::X509::Certificate> ] :ssl_ca_cert_object
An array of OpenSSL::X509::Certificate objects representing the certificate authority certificates used to validate certs passed from the other end of the connection. Intermediate certificates should NOT be specified in files referenced by this option. One of :ssl_ca_cert, :ssl_ca_cert_string or :ssl_ca_cert_object (in order of priority) is required when using :ssl_verify.
@option options [ String ] :ssl_ca_cert_string A string containing
certificate authority certificate used to validate certs passed from the other end of the connection. This option allows passing only one CA certificate to the driver. Intermediate certificates should NOT be specified in files referenced by this option. One of :ssl_ca_cert, :ssl_ca_cert_string or :ssl_ca_cert_object (in order of priority) is required when using :ssl_verify.
@option options [ String ] :ssl_cert The certificate file used to identify
the connection against MongoDB. A certificate chain may be passed by specifying the client certificate first followed by any intermediate certificates up to the CA certificate. The file may also contain the certificate's private key, which will be ignored. This option, if present, takes precedence over the values of :ssl_cert_string and :ssl_cert_object
@option options [ OpenSSL::X509::Certificate ] :ssl_cert_object The OpenSSL::X509::Certificate
used to identify the connection against MongoDB. Only one certificate may be passed through this option.
@option options [ String ] :ssl_cert_string A string containing the PEM-encoded
certificate used to identify the connection against MongoDB. A certificate chain may be passed by specifying the client certificate first followed by any intermediate certificates up to the CA certificate. The string may also contain the certificate's private key, which will be ignored, This option, if present, takes precedence over the value of :ssl_cert_object
@option options [ String ] :ssl_key The private keyfile used to identify the
connection against MongoDB. Note that even if the key is stored in the same file as the certificate, both need to be explicitly specified. This option, if present, takes precedence over the values of :ssl_key_string and :ssl_key_object
@option options [ OpenSSL::PKey ] :ssl_key_object The private key used to identify the
connection against MongoDB
@option options [ String ] :ssl_key_pass_phrase A passphrase for the private key. @option options [ String ] :ssl_key_string A string containing the PEM-encoded private key
used to identify the connection against MongoDB. This parameter, if present, takes precedence over the value of option :ssl_key_object
@option options [ true, false ] :ssl_verify Whether to perform peer certificate validation and
hostname verification. Note that the decision of whether to validate certificates will be overridden if :ssl_verify_certificate is set, and the decision of whether to validate hostnames will be overridden if :ssl_verify_hostname is set.
@option options [ true, false ] :ssl_verify_certificate Whether to perform peer certificate
validation. This setting overrides :ssl_verify with respect to whether certificate validation is performed.
@option options [ true, false ] :ssl_verify_hostname Whether to perform peer hostname
validation. This setting overrides :ssl_verify with respect to whether hostname validation is performed.
@since 2.0.0 @api private
Mongo::Socket::new
# File lib/mongo/socket/ssl.rb, line 103 def initialize(host, port, host_name, timeout, family, options = {}) super(timeout, options) @host, @port, @host_name = host, port, host_name @context = create_context(options) @family = family @tcp_socket = ::Socket.new(family, SOCK_STREAM, 0) begin @tcp_socket.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1) set_socket_options(@tcp_socket) run_tls_context_hooks connect! rescue @tcp_socket.close raise end end
Public Instance Methods
Read a single byte from the socket.
@example Read a single byte.
socket.readbyte
@return [ Object ] The read byte.
@since 2.0.0
# File lib/mongo/socket/ssl.rb, line 176 def readbyte map_exceptions do byte = socket.read(1).bytes.to_a[0] byte.nil? ? raise(EOFError) : byte end end
Private Instance Methods
Establishes a socket connection.
@example Connect the socket.
sock.connect!
@note This method mutates the object by setting the socket
internally.
@return [ SSL
] The connected socket instance.
@since 2.0.0
# File lib/mongo/socket/ssl.rb, line 144 def connect! Timeout.timeout(options[:connect_timeout], Error::SocketTimeoutError, "The socket took over #{options[:connect_timeout]} seconds to connect") do map_exceptions do @tcp_socket.connect(::Socket.pack_sockaddr_in(port, host)) end @socket = OpenSSL::SSL::SSLSocket.new(@tcp_socket, context) begin @socket.hostname = @host_name @socket.sync_close = true map_exceptions do @socket.connect end verify_certificate!(@socket) verify_ocsp_endpoint!(@socket) rescue @socket.close @socket = nil raise end self end end
# File lib/mongo/socket/ssl.rb, line 217 def create_context(options) OpenSSL::SSL::SSLContext.new.tap do |context| if OpenSSL::SSL.const_defined?(:OP_NO_RENEGOTIATION) context.options = context.options | OpenSSL::SSL::OP_NO_RENEGOTIATION end if context.respond_to?(:renegotiation_cb=) # Disable renegotiation for older Ruby versions per the sample code at # https://rubydocs.org/d/ruby-2-6-0/classes/OpenSSL/SSL/SSLContext.html # In JRuby we must allow one call as this callback is invoked for # the initial connection also, not just for renegotiations - # https://github.com/jruby/jruby-openssl/issues/180 if BSON::Environment.jruby? allowed_calls = 1 else allowed_calls = 0 end context.renegotiation_cb = lambda do |ssl| if allowed_calls <= 0 raise RuntimeError, 'Client renegotiation disabled' end allowed_calls -= 1 end end set_cert(context, options) set_key(context, options) if verify_certificate? context.verify_mode = OpenSSL::SSL::VERIFY_PEER set_cert_verification(context, options) else context.verify_mode = OpenSSL::SSL::VERIFY_NONE end if context.respond_to?(:verify_hostname=) # We manually check the hostname after the connection is established if necessary, so # we disable it here in order to give consistent errors across Ruby versions which # don't support hostname verification at the time of the handshake. context.verify_hostname = OpenSSL::SSL::VERIFY_NONE end end end
# File lib/mongo/socket/ssl.rb, line 384 def human_address "#{host}:#{port} (#{host_name}:#{port}, TLS)" end
# File lib/mongo/socket/ssl.rb, line 320 def load_private_key(text, passphrase) args = if passphrase [text, passphrase] else [text] end # On JRuby, PKey.read does not grok cert+key bundles. # https://github.com/jruby/jruby-openssl/issues/176 if BSON::Environment.jruby? [OpenSSL::PKey::RSA, OpenSSL::PKey::DSA].each do |cls| begin return cls.send(:new, *args) rescue OpenSSL::PKey::PKeyError # ignore end end # Neither RSA nor DSA worked, fall through to trying PKey end OpenSSL::PKey.send(:read, *args) end
# File lib/mongo/socket/ssl.rb, line 378 def read_buffer_size # Buffer size for TLS reads. # Capped at 16k due to https://linux.die.net/man/3/ssl_read 16384 end
# File lib/mongo/socket/ssl.rb, line 388 def run_tls_context_hooks Mongo.tls_context_hooks.each do |hook| hook.call(@context) end end
# File lib/mongo/socket/ssl.rb, line 261 def set_cert(context, options) # Since we clear cert_text during processing, we need to examine # ssl_cert_object here to avoid considering it if we have also # processed the text. if options[:ssl_cert] cert_text = File.read(options[:ssl_cert]) cert_object = nil elsif cert_text = options[:ssl_cert_string] cert_object = nil else cert_object = options[:ssl_cert_object] end # The client certificate may be a single certificate or a bundle # (client certificate followed by intermediate certificates). # The text may also include private keys for the certificates. # OpenSSL supports passing the entire bundle as a certificate chain # to the context via SSL_CTX_use_certificate_chain_file, but the # Ruby openssl extension does not currently expose this functionality # per https://github.com/ruby/openssl/issues/254. # Therefore, extract the individual certificates from the certificate # text, and if there is more than one certificate provided, use # extra_chain_cert option to add the intermediate ones. This # implementation is modeled after # https://github.com/venuenext/ruby-kafka/commit/9495f5daf254b43bc88062acad9359c5f32cb8b5. # Note that the parsing here is not identical to what OpenSSL employs - # for instance, if there is no newline between two certificates # this code will extract them both but OpenSSL fails in this situation. if cert_text certs = cert_text.scan(/-----BEGIN CERTIFICATE-----(?:.|\n)+?-----END CERTIFICATE-----/) if certs.length > 1 context.cert = OpenSSL::X509::Certificate.new(certs.shift) context.extra_chain_cert = certs.map do |cert| OpenSSL::X509::Certificate.new(cert) end # All certificates are already added to the context, skip adding # them again below. cert_text = nil end end if cert_text context.cert = OpenSSL::X509::Certificate.new(cert_text) elsif cert_object context.cert = cert_object end end
# File lib/mongo/socket/ssl.rb, line 341 def set_cert_verification(context, options) context.verify_mode = OpenSSL::SSL::VERIFY_PEER cert_store = OpenSSL::X509::Store.new if options[:ssl_ca_cert] cert_store.add_file(options[:ssl_ca_cert]) elsif options[:ssl_ca_cert_string] cert_store.add_cert(OpenSSL::X509::Certificate.new(options[:ssl_ca_cert_string])) elsif options[:ssl_ca_cert_object] raise TypeError("Option :ssl_ca_cert_object should be an array of OpenSSL::X509:Certificate objects") unless options[:ssl_ca_cert_object].is_a? Array options[:ssl_ca_cert_object].each {|cert| cert_store.add_cert(cert)} else cert_store.set_default_paths end context.cert_store = cert_store end
# File lib/mongo/socket/ssl.rb, line 309 def set_key(context, options) passphrase = options[:ssl_key_pass_phrase] if options[:ssl_key] context.key = load_private_key(File.read(options[:ssl_key]), passphrase) elsif options[:ssl_key_string] context.key = load_private_key(options[:ssl_key_string], passphrase) elsif options[:ssl_key_object] context.key = options[:ssl_key_object] end end
# File lib/mongo/socket/ssl.rb, line 357 def verify_certificate!(socket) if verify_hostname? unless OpenSSL::SSL.verify_certificate_identity(socket.peer_cert, host_name) raise Error::SocketError, 'TLS handshake failed due to a hostname mismatch.' end end end
# File lib/mongo/socket/ssl.rb, line 185 def verify_certificate? # If ssl_verify_certificate is not present, disable only if # ssl_verify is explicitly set to false. if options[:ssl_verify_certificate].nil? options[:ssl_verify] != false # If ssl_verify_certificate is present, enable or disable based on its value. else !!options[:ssl_verify_certificate] end end
# File lib/mongo/socket/ssl.rb, line 196 def verify_hostname? # If ssl_verify_hostname is not present, disable only if ssl_verify is # explicitly set to false. if options[:ssl_verify_hostname].nil? options[:ssl_verify] != false # If ssl_verify_hostname is present, enable or disable based on its value. else !!options[:ssl_verify_hostname] end end
# File lib/mongo/socket/ssl.rb, line 365 def verify_ocsp_endpoint!(socket) unless verify_ocsp_endpoint? return end cert = socket.peer_cert ca_cert = socket.peer_cert_chain.last verifier = OcspVerifier.new(@host_name, cert, ca_cert, context.cert_store, **Utils.shallow_symbolize_keys(options)) verifier.verify_with_cache end
# File lib/mongo/socket/ssl.rb, line 207 def verify_ocsp_endpoint? if !options[:ssl_verify_ocsp_endpoint].nil? options[:ssl_verify_ocsp_endpoint] != false elsif !options[:ssl_verify_certificate].nil? options[:ssl_verify_certificate] != false else options[:ssl_verify] != false end end