Class: HTTPX::SSL

Inherits:
TCP
  • Object
show all
Defined in:
lib/httpx/io/ssl.rb,
sig/io/ssl.rbs

Direct Known Subclasses

ProxySSL

Constant Summary collapse

TLS_OPTIONS =

Returns:

  • (Hash[Symbol, untyped])
tls_options.freeze

Constants included from Loggable

Loggable::COLORS, Loggable::USE_DEBUG_LOG

Instance Attribute Summary collapse

Attributes inherited from TCP

#addresses, #interests, #ip, #port, #state

Instance Method Summary collapse

Methods inherited from TCP

#add_addresses, #addresses?, #build_socket, #can_disconnect?, #close, #closed?, #do_transition, #inspect, #read, #socket, #to_io, #try_connect, #write

Methods included from Loggable

#log, #log_exception, log_identifiers, #log_redact, #log_redact_body, #log_redact_headers

Constructor Details

#initialize(_, _, options) ⇒ SSL

Returns a new instance of SSL.



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/httpx/io/ssl.rb', line 17

def initialize(_, _, options)
  super

  @ssl_session = @session_new_cb = nil

  ctx_options = TLS_OPTIONS
  ctx_options = ctx_options.merge(options.ssl) if options.ssl && !options.ssl.empty?
  @sni_hostname = (ctx_options.delete(:hostname) if ctx_options.key?(:hostname)) || @hostname

  if @keep_open && @io.is_a?(OpenSSL::SSL::SSLSocket)
    # externally initiated ssl socket
    @ctx = @io.context
    @state = :negotiated
  else
    @ctx = OpenSSL::SSL::SSLContext.new
    @ctx.set_params(ctx_options)
    unless @ctx.session_cache_mode.nil? # a dummy method on JRuby
      @ctx.session_cache_mode =
        OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT | OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE
    end
    init_session_new_cb

    yield(self) if block_given?
  end

  @verify_hostname = @ctx.verify_hostname
end

Instance Attribute Details

#ssl_session=(value) ⇒ Object (writeonly)

Sets the attribute ssl_session

Parameters:

  • value (OpenSSL::SSL::Session, nil)

    the value to set the attribute ssl_session to.



15
16
17
# File 'lib/httpx/io/ssl.rb', line 15

def ssl_session=(value)
  @ssl_session = value
end

Instance Method Details

#can_verify_peer?Boolean

Returns:

  • (Boolean)


84
85
86
# File 'lib/httpx/io/ssl.rb', line 84

def can_verify_peer?
  @ctx.verify_mode == OpenSSL::SSL::VERIFY_PEER
end

#connectObject



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
# File 'lib/httpx/io/ssl.rb', line 106

def connect
  return if @state == :negotiated

  unless @state == :connected
    super
    return unless @state == :connected
  end

  # @type ivar @io: OpenSSL::SSL::SSLSocket

  unless @io.is_a?(OpenSSL::SSL::SSLSocket)
    if (hostname_is_ip = (@ip == @sni_hostname)) && @ctx.verify_hostname
      # IPv6 address would be "[::1]", must turn to "0000:0000:0000:0000:0000:0000:0000:0001" for cert SAN check
      @sni_hostname = @ip.to_string
      # IP addresses in SNI is not valid per RFC 6066, section 3.
      @ctx.verify_hostname = false
    end

    ssl = OpenSSL::SSL::SSLSocket.new(@io, @ctx)

    ssl.hostname = @sni_hostname unless hostname_is_ip
    ssl.session = @ssl_session unless ssl_session_expired?
    ssl.sync_close = true

    @io = ssl
  end
  try_ssl_connect
end

#connected?Boolean

Returns:

  • (Boolean)


96
97
98
# File 'lib/httpx/io/ssl.rb', line 96

def connected?
  @state == :negotiated
end

#init_session_new_cbvoid

This method returns an undefined value.

sets the ssl context's new session callback, which points at @session_new_cb when available.



52
53
54
# File 'lib/httpx/io/ssl.rb', line 52

def init_session_new_cb
  @ctx.session_new_cb = proc { |_, sess| @session_new_cb&.call(sess) }
end

#protocolObject

in jruby, alpn_protocol may return "" https://github.com/jruby/jruby-openssl/issues/287



73
74
75
76
77
# File 'lib/httpx/io/ssl.rb', line 73

def protocol
  return super unless @io.is_a?(OpenSSL::SSL::SSLSocket)

  @io.alpn_protocol || super
end

#session_new_cb {|sess| ... } ⇒ void

This method returns an undefined value.

session_new_cb not implemented under JRuby

Yields:

Yield Parameters:

  • sess (OpenSSL::SSL::Session)

Yield Returns:

  • (void)


47
48
49
# File 'lib/httpx/io/ssl.rb', line 47

def session_new_cb(&pr)
  @session_new_cb = pr
end

#ssl_session_expired?Boolean

Returns:

  • (Boolean)


100
101
102
103
104
# File 'lib/httpx/io/ssl.rb', line 100

def ssl_session_expired?
  ssl_session = @ssl_session

  ssl_session.nil? || Process.clock_gettime(Process::CLOCK_REALTIME) >= (ssl_session.time.to_f + ssl_session.timeout)
end

#try_ssl_connectvoid

This method returns an undefined value.

:nocov:



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'sig/io/ssl.rbs', line 27

def try_ssl_connect
  # @type ivar @io: OpenSSL::SSL::SSLSocket
  ret = @io.connect_nonblock(exception: false)
  log(level: 3, color: :cyan) { "TLS CONNECT: #{ret}..." }
  case ret
  when :wait_readable
    @interests = :r
    return
  when :wait_writable
    @interests = :w
    return
  end
  @io.post_connection_check(@sni_hostname) if @ctx.verify_mode != OpenSSL::SSL::VERIFY_NONE && @verify_hostname
  transition(:negotiated)
  @interests = :w
end

#verify_hostname(host) ⇒ Boolean

Parameters:

  • host (String)

Returns:

  • (Boolean)


88
89
90
91
92
93
94
# File 'lib/httpx/io/ssl.rb', line 88

def verify_hostname(host)
  return false if @ctx.verify_mode == OpenSSL::SSL::VERIFY_NONE
  # @type ivar @io: OpenSSL::SSL::SSLSocket
  return false if !@io.respond_to?(:peer_cert) || (peer_cert = @io.peer_cert).nil?

  OpenSSL::SSL.verify_certificate_identity(peer_cert, host)
end