abstract class OpenSSL::SSL::Socket < IO class Client < Socket def initialize(io, context : Context::Client = Context::Client.new, sync_close : Bool = false, hostname : String? = nil) super(io, context, sync_close) begin if hostname # Macro from OpenSSL: SSL_ctrl(s,SSL_CTRL_SET_TLSEXT_HOSTNAME,TLSEXT_NAMETYPE_host_name,(char *)name) LibSSL.ssl_ctrl( @ssl, LibSSL::SSLCtrl::SET_TLSEXT_HOSTNAME, LibSSL::TLSExt::NAMETYPE_host_name, hostname.to_unsafe.as(Pointer(Void)) ) param = LibSSL.ssl_get0_param(@ssl) if ::Socket::IPAddress.valid?(hostname) unless LibCrypto.x509_verify_param_set1_ip_asc(param, hostname) == 1 raise OpenSSL::Error.new("X509_VERIFY_PARAM_set1_ip_asc") end else unless LibCrypto.x509_verify_param_set1_host(param, hostname, 0) == 1 raise OpenSSL::Error.new("X509_VERIFY_PARAM_set1_host") end end end ret = LibSSL.ssl_connect(@ssl) unless ret == 1 raise OpenSSL::SSL::Error.new(@ssl, ret, "SSL_connect") end rescue ex LibSSL.ssl_free(@ssl) # GC never calls finalize, avoid mem leak raise ex end end def self.open(io, context : Context::Client = Context::Client.new, sync_close : Bool = false, hostname : String? = nil, &) socket = new(io, context, sync_close, hostname) begin yield socket ensure socket.close end end # Returns the `OpenSSL::X509::Certificate` the peer presented. def peer_certificate : OpenSSL::X509::Certificate super.not_nil! end end class Server < Socket def initialize(io, context : Context::Server = Context::Server.new, sync_close : Bool = false, accept : Bool = true) super(io, context, sync_close) if accept begin self.accept rescue ex LibSSL.ssl_free(@ssl) # GC never calls finalize, avoid mem leak raise ex end end end def accept : Nil ret = LibSSL.ssl_accept(@ssl) unless ret == 1 bio.io.close if @sync_close raise OpenSSL::SSL::Error.new(@ssl, ret, "SSL_accept") end end def self.open(io, context : Context::Server = Context::Server.new, sync_close : Bool = false, &) socket = new(io, context, sync_close) begin yield socket ensure socket.close end end end include IO::Buffered # If `#sync_close?` is `true`, closing this socket will # close the underlying IO. property? sync_close : Bool getter? closed : Bool {% if compare_versions(Crystal::VERSION, "1.12.0") >= 0 %} @bio = uninitialized ReferenceStorage(BIO) {% else %} @bio = uninitialized BIO {% end %} protected def initialize(io, context : Context, @sync_close : Bool = false) @closed = false @ssl = LibSSL.ssl_new(context) unless @ssl raise OpenSSL::Error.new("SSL_new") end bio = {% if compare_versions(Crystal::VERSION, "1.12.0") >= 0 %} @bio = uninitialized ReferenceStorage(BIO) BIO.unsafe_construct(pointerof(@bio), io) {% else %} @bio = BIO.new(io) {% end %} LibSSL.ssl_set_bio(@ssl, bio, bio) end def finalize LibSSL.ssl_free(@ssl) end private def bio {% if compare_versions(Crystal::VERSION, "1.12.0") >= 0 %} @bio.to_reference {% else %} @bio {% end %} end def unbuffered_read(slice : Bytes) : Int32 check_open count = slice.size return 0 if count == 0 LibSSL.ssl_read(@ssl, slice.to_unsafe, count).tap do |bytes| if bytes <= 0 && !LibSSL.ssl_get_error(@ssl, bytes).zero_return? ex = OpenSSL::SSL::Error.new(@ssl, bytes, "SSL_read") if ex.underlying_eof? # underlying BIO terminated gracefully, without terminating SSL aspect gracefully first # some misbehaving servers "do this" so treat as EOF even though it's a protocol error return 0 end raise ex end end end def unbuffered_write(slice : Bytes) : Nil check_open return if slice.empty? count = slice.size bytes = LibSSL.ssl_write(@ssl, slice.to_unsafe, count) unless bytes > 0 raise OpenSSL::SSL::Error.new(@ssl, bytes, "SSL_write") end end def unbuffered_flush : Nil bio.io.flush end # Returns the negotiated ALPN protocol (eg: `"h2"`) of `nil` if no protocol was # negotiated. def alpn_protocol LibSSL.ssl_get0_alpn_selected(@ssl, out protocol, out len) String.new(protocol, len) unless protocol.null? end def unbuffered_close : Nil return if @closed @closed = true begin loop do ret = LibSSL.ssl_shutdown(@ssl) break if ret == 1 # done bidirectional break if ret == 0 && sync_close? # done unidirectional, "this first successful call to SSL_shutdown() is sufficient" raise OpenSSL::SSL::Error.new(@ssl, ret, "SSL_shutdown") if ret < 0 rescue e : OpenSSL::SSL::Error case e.error when .want_read?, .want_write? # Ignore, shutdown did not complete yet when .syscall? # OpenSSL claimed an underlying syscall failed, but that didn't set any error state, # assume we're done break else raise e end # ret == 0, retry, shutdown is not complete yet end rescue IO::Error ensure bio.io.close if @sync_close end end def unbuffered_rewind : Nil raise IO::Error.new("Can't rewind OpenSSL::SSL::Socket::Client") end # Returns the hostname provided through Server Name Indication (SNI) def hostname : String? if host_name = LibSSL.ssl_get_servername(@ssl, LibSSL::TLSExt::NAMETYPE_host_name) String.new(host_name) end end # Returns the current cipher used by this socket. def cipher : String String.new(LibSSL.ssl_cipher_get_name(LibSSL.ssl_get_current_cipher(@ssl))) end # Returns the name of the TLS protocol version used by this socket. def tls_version : String String.new(LibSSL.ssl_get_version(@ssl)) end def local_address io = bio.io io.responds_to?(:local_address) ? io.local_address : nil end def remote_address io = bio.io io.responds_to?(:remote_address) ? io.remote_address : nil end def read_timeout io = bio.io if io.responds_to? :read_timeout io.read_timeout else raise NotImplementedError.new("#{io.class}#read_timeout") end end def read_timeout=(value) io = bio.io if io.responds_to? :read_timeout= io.read_timeout = value else raise NotImplementedError.new("#{io.class}#read_timeout=") end end def write_timeout io = bio.io if io.responds_to? :write_timeout io.write_timeout else raise NotImplementedError.new("#{io.class}#write_timeout") end end def write_timeout=(value) io = bio.io if io.responds_to? :write_timeout= io.write_timeout = value else raise NotImplementedError.new("#{io.class}#write_timeout=") end end # Returns the `OpenSSL::X509::Certificate` the peer presented, if a # connection was established. # # NOTE: Due to the protocol definition, a TLS/SSL server will always send a # certificate, if present. A client will only send a certificate when # explicitly requested to do so by the server (see `SSL_CTX_set_verify(3)`). If # an anonymous cipher is used, no certificates are sent. That a certificate # is returned does not indicate information about the verification state. def peer_certificate : OpenSSL::X509::Certificate? raw_cert = LibSSL.ssl_get_peer_certificate(@ssl) if raw_cert begin OpenSSL::X509::Certificate.new(raw_cert) ensure LibCrypto.x509_free(raw_cert) end end end end