Class: HTTPX::SSL

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

Direct Known Subclasses

ProxySSL

Constant Summary collapse

TLS_OPTIONS =

rubocop:disable Style/MutableConstant

{ alpn_protocols: %w[h2 http/1.1].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, #close, #closed?, #inspect, #read, #socket, #to_io, #write

Methods included from Loggable

#log, #log_exception, #log_redact

Constructor Details

#initialize(_, _, options) ⇒ SSL

Returns a new instance of SSL.



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

def initialize(_, _, options)
  super

  ctx_options = TLS_OPTIONS.merge(options.ssl)
  @sni_hostname = ctx_options.delete(: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_options.empty?
    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

    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

    the value to set the attribute ssl_session to.



17
18
19
# File 'lib/httpx/io/ssl.rb', line 17

def ssl_session=(value)
  @ssl_session = value
end

Instance Method Details

#can_verify_peer?Boolean

Returns:

  • (Boolean)


72
73
74
# File 'lib/httpx/io/ssl.rb', line 72

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

#connectObject



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/httpx/io/ssl.rb', line 95

def connect
  return if @state == :negotiated

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

  unless @io.is_a?(OpenSSL::SSL::SSLSocket)
    if (hostname_is_ip = (@ip == @sni_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

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

    @io.hostname = @sni_hostname unless hostname_is_ip
    @io.session = @ssl_session unless ssl_session_expired?
    @io.sync_close = true
  end
  try_ssl_connect
end

#connected?Boolean

Returns:

  • (Boolean)


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

def connected?
  @state == :negotiated
end

#expired?Boolean

Returns:

  • (Boolean)


87
88
89
# File 'lib/httpx/io/ssl.rb', line 87

def expired?
  super || ssl_session_expired?
end

#protocolObject

in jruby, alpn_protocol may return “” github.com/jruby/jruby-openssl/issues/287



61
62
63
64
65
# File 'lib/httpx/io/ssl.rb', line 61

def protocol
  @io.alpn_protocol || super
rescue StandardError
  super
end

#session_new_cbObject

session_new_cb not implemented under JRuby



49
50
51
# File 'lib/httpx/io/ssl.rb', line 49

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

#ssl_session_expired?Boolean

Returns:

  • (Boolean)


91
92
93
# File 'lib/httpx/io/ssl.rb', line 91

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

#try_ssl_connectObject



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/httpx/io/ssl.rb', line 120

def try_ssl_connect
  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) ⇒ Object



76
77
78
79
80
81
# File 'lib/httpx/io/ssl.rb', line 76

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

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