lib/httpx/io/ssl.rb in httpx-0.24.3 vs lib/httpx/io/ssl.rb in httpx-0.24.4
- old
+ new
@@ -8,26 +8,54 @@
class SSL < TCP
using RegexpExtensions unless Regexp.method_defined?(:match?)
TLS_OPTIONS = if OpenSSL::SSL::SSLContext.instance_methods.include?(:alpn_protocols)
- { alpn_protocols: %w[h2 http/1.1].freeze }.freeze
+ { alpn_protocols: %w[h2 http/1.1].freeze }
else
- {}.freeze
+ {}
end
+ # https://github.com/jruby/jruby-openssl/issues/284
+ TLS_OPTIONS[:verify_hostname] = true if RUBY_ENGINE == "jruby"
+ TLS_OPTIONS.freeze
+ attr_writer :ssl_session
+
def initialize(_, _, options)
super
- @ctx = OpenSSL::SSL::SSLContext.new
+
ctx_options = TLS_OPTIONS.merge(options.ssl)
@sni_hostname = ctx_options.delete(:hostname) || @hostname
- @ctx.set_params(ctx_options) unless ctx_options.empty?
- @state = :negotiated if @keep_open
+ 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
+
@hostname_is_ip = IPRegex.match?(@sni_hostname)
+ @verify_hostname = @ctx.verify_hostname
end
+ if OpenSSL::SSL::SSLContext.method_defined?(:session_new_cb=)
+ def session_new_cb(&pr)
+ @ctx.session_new_cb = proc { |_, sess| pr.call(sess) }
+ end
+ else
+ # session_new_cb not implemented under JRuby
+ def session_new_cb; end
+ end
+
def protocol
@io.alpn_protocol || super
rescue StandardError
super
end
@@ -41,39 +69,47 @@
return false if !@io.respond_to?(:peer_cert) || @io.peer_cert.nil?
OpenSSL::SSL.verify_certificate_identity(@io.peer_cert, host)
end
- def close
- super
- # allow reconnections
- # connect only works if initial @io is a socket
- @io = @io.io if @io.respond_to?(:io)
- end
-
def connected?
@state == :negotiated
end
+ def expired?
+ super || ssl_session_expired?
+ end
+
+ def ssl_session_expired?
+ @ssl_session.nil? || Process.clock_gettime(Process::CLOCK_REALTIME) >= (@ssl_session.time.to_f + @ssl_session.timeout)
+ end
+
def connect
super
return if @state == :negotiated ||
@state != :connected
unless @io.is_a?(OpenSSL::SSL::SSLSocket)
+ if @hostname_is_ip
+ # 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
if RUBY_VERSION < "2.3"
# :nocov:
def try_ssl_connect
@io.connect_nonblock
- @io.post_connection_check(@sni_hostname) if @ctx.verify_mode != OpenSSL::SSL::VERIFY_NONE && !@hostname_is_ip
+ @io.post_connection_check(@sni_hostname) if @ctx.verify_mode != OpenSSL::SSL::VERIFY_NONE && @verify_hostname
transition(:negotiated)
@interests = :w
rescue ::IO::WaitReadable
@interests = :r
rescue ::IO::WaitWritable
@@ -101,11 +137,11 @@
return
when :wait_writable
@interests = :w
return
end
- @io.post_connection_check(@sni_hostname) if @ctx.verify_mode != OpenSSL::SSL::VERIFY_NONE && !@hostname_is_ip
+ @io.post_connection_check(@sni_hostname) if @ctx.verify_mode != OpenSSL::SSL::VERIFY_NONE && @verify_hostname
transition(:negotiated)
@interests = :w
end
# :nocov:
@@ -128,9 +164,10 @@
def transition(nextstate)
case nextstate
when :negotiated
return unless @state == :connected
+
when :closed
return unless @state == :negotiated ||
@state == :connected
end
do_transition(nextstate)