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

Constructor Details

#initialize(_, _, options) ⇒ SSL

Returns a new instance of SSL.



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

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.



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

def ssl_session=(value)
  @ssl_session = value
end

Instance Method Details

#can_verify_peer?Boolean

Returns:

  • (Boolean)


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

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

#connectObject



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

def connect
  super
  return if @state == :negotiated ||
            @state != :connected

  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)


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

def connected?
  @state == :negotiated
end

#expired?Boolean

Returns:

  • (Boolean)


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

def expired?
  super || ssl_session_expired?
end

#protocolObject

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



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

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

#session_new_cbObject

session_new_cb not implemented under JRuby



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

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

#ssl_session_expired?Boolean

Returns:

  • (Boolean)


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

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



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/httpx/io/ssl.rb', line 116

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



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

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