lib/puma/minissl.rb in piesync-puma-3.12.6.1 vs lib/puma/minissl.rb in piesync-puma-5.4.0.1
- old
+ new
@@ -1,29 +1,61 @@
# frozen_string_literal: true
begin
require 'io/wait'
- rescue LoadError
+rescue LoadError
end
+# need for Puma::MiniSSL::OPENSSL constants used in `HAS_TLS1_3`
+require 'puma/puma_http11'
+
module Puma
module MiniSSL
+ # Define constant at runtime, as it's easy to determine at built time,
+ # but Puma could (it shouldn't) be loaded with an older OpenSSL version
+ # @version 5.0.0
+ HAS_TLS1_3 = !IS_JRUBY &&
+ (OPENSSL_VERSION[/ \d+\.\d+\.\d+/].split('.').map(&:to_i) <=> [1,1,1]) != -1 &&
+ (OPENSSL_LIBRARY_VERSION[/ \d+\.\d+\.\d+/].split('.').map(&:to_i) <=> [1,1,1]) !=-1
+
class Socket
def initialize(socket, engine)
@socket = socket
@engine = engine
@peercert = nil
end
+ # @!attribute [r] to_io
def to_io
@socket
end
def closed?
@socket.closed?
end
+ # Returns a two element array,
+ # first is protocol version (SSL_get_version),
+ # second is 'handshake' state (SSL_state_string)
+ #
+ # Used for dropping tcp connections to ssl.
+ # See OpenSSL ssl/ssl_stat.c SSL_state_string for info
+ # @!attribute [r] ssl_version_state
+ # @version 5.0.0
+ #
+ def ssl_version_state
+ IS_JRUBY ? [nil, nil] : @engine.ssl_vers_st
+ end
+
+ # Used to check the handshake status, in particular when a TCP connection
+ # is made with TLSv1.3 as an available protocol
+ # @version 5.0.0
+ def bad_tlsv1_3?
+ HAS_TLS1_3 && @engine.ssl_vers_st == ['TLSv1.3', 'SSLERR']
+ end
+ private :bad_tlsv1_3?
+
def readpartial(size)
while true
output = @engine.read
return output if output
@@ -52,26 +84,26 @@
# at some point (and being used in the wild)
while true
output = engine_read_all
return output if output
- begin
- data = @socket.read_nonblock(size, exception: false)
- if data == :wait_readable || data == :wait_writable
- if @socket.to_io.respond_to?(data)
- @socket.to_io.__send__(data)
- elsif data == :wait_readable
- IO.select([@socket.to_io])
- else
- IO.select(nil, [@socket.to_io])
- end
- elsif !data
- return nil
- else
- break
- end
- end while true
+ data = @socket.read_nonblock(size, exception: false)
+ if data == :wait_readable || data == :wait_writable
+ # It would make more sense to let @socket.read_nonblock raise
+ # EAGAIN if necessary but it seems like it'll misbehave on Windows.
+ # I don't have a Windows machine to debug this so I can't explain
+ # exactly whats happening in that OS. Please let me know if you
+ # find out!
+ #
+ # In the meantime, we can emulate the correct behavior by
+ # capturing :wait_readable & :wait_writable and raising EAGAIN
+ # ourselves.
+ raise IO::EAGAINWaitReadable
+ elsif data.nil?
+ raise SSLError.exception "HTTP connection?" if bad_tlsv1_3?
+ return nil
+ end
@engine.inject(data)
output = engine_read_all
return output if output
@@ -83,104 +115,104 @@
end
def write(data)
return 0 if data.empty?
- need = data.bytesize
+ data_size = data.bytesize
+ need = data_size
while true
wrote = @engine.write data
- enc = @engine.extract
- while enc
- @socket.write enc
- enc = @engine.extract
+ enc_wr = ''.dup
+ while (enc = @engine.extract)
+ enc_wr << enc
end
+ @socket.write enc_wr unless enc_wr.empty?
need -= wrote
- return data.bytesize if need == 0
+ return data_size if need == 0
- data = data[wrote..-1]
+ data = data.byteslice(wrote..-1)
end
end
alias_method :syswrite, :write
alias_method :<<, :write
# This is a temporary fix to deal with websockets code using
- # write_nonblock. The problem with implementing it properly
+ # write_nonblock.
+
+ # The problem with implementing it properly
# is that it means we'd have to have the ability to rewind
# an engine because after we write+extract, the socket
# write_nonblock call might raise an exception and later
# code would pass the same data in, but the engine would think
- # it had already written the data in. So for the time being
- # (and since write blocking is quite rare), go ahead and actually
- # block in write_nonblock.
+ # it had already written the data in.
+ #
+ # So for the time being (and since write blocking is quite rare),
+ # go ahead and actually block in write_nonblock.
+ #
def write_nonblock(data, *_)
write data
end
def flush
@socket.flush
end
- def read_and_drop(timeout = 1)
- return :timeout unless IO.select([@socket], nil, nil, timeout)
- return :eof unless read_nonblock(1024)
- :drop
- rescue Errno::EAGAIN
- # do nothing
- :eagain
- end
-
- def should_drop_bytes?
- @engine.init? || !@engine.shutdown
- end
-
def close
begin
- # Read any drop any partially initialized sockets and any received bytes during shutdown.
- # Don't let this socket hold this loop forever.
- # If it can't send more packets within 1s, then give up.
- while should_drop_bytes?
- return if [:timeout, :eof].include?(read_and_drop(1))
+ unless @engine.shutdown
+ while alert_data = @engine.extract
+ @socket.write alert_data
+ end
end
rescue IOError, SystemCallError
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
# nothing
ensure
@socket.close
end
end
+ # @!attribute [r] peeraddr
def peeraddr
@socket.peeraddr
end
+ # @!attribute [r] peercert
def peercert
return @peercert if @peercert
raw = @engine.peercert
return nil unless raw
@peercert = OpenSSL::X509::Certificate.new raw
end
end
- if defined?(JRUBY_VERSION)
+ if IS_JRUBY
+ OPENSSL_NO_SSL3 = false
+ OPENSSL_NO_TLS1 = false
+
class SSLError < StandardError
# Define this for jruby even though it isn't used.
end
-
- def self.check; end
end
class Context
attr_accessor :verify_mode
+ attr_reader :no_tlsv1, :no_tlsv1_1
- if defined?(JRUBY_VERSION)
+ def initialize
+ @no_tlsv1 = false
+ @no_tlsv1_1 = false
+ end
+
+ if IS_JRUBY
# jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
attr_reader :keystore
attr_accessor :keystore_pass
attr_accessor :ssl_cipher_list
@@ -219,16 +251,33 @@
def check
raise "Key not configured" unless @key
raise "Cert not configured" unless @cert
end
end
+
+ # disables TLSv1
+ # @!attribute [w] no_tlsv1=
+ def no_tlsv1=(tlsv1)
+ raise ArgumentError, "Invalid value of no_tlsv1=" unless ['true', 'false', true, false].include?(tlsv1)
+ @no_tlsv1 = tlsv1
+ end
+
+ # disables TLSv1 and TLSv1.1. Overrides `#no_tlsv1=`
+ # @!attribute [w] no_tlsv1_1=
+ def no_tlsv1_1=(tlsv1_1)
+ raise ArgumentError, "Invalid value of no_tlsv1_1=" unless ['true', 'false', true, false].include?(tlsv1_1)
+ @no_tlsv1_1 = tlsv1_1
+ end
+
end
VERIFY_NONE = 0
VERIFY_PEER = 1
VERIFY_FAIL_IF_NO_PEER_CERT = 2
+ # https://github.com/openssl/openssl/blob/master/include/openssl/x509_vfy.h.in
+ # /* Certificate verify flags */
VERIFICATION_FLAGS = {
"USE_CHECK_TIME" => 0x2,
"CRL_CHECK" => 0x4,
"CRL_CHECK_ALL" => 0x8,
"IGNORE_CRITICAL" => 0x10,
@@ -253,33 +302,43 @@
class Server
def initialize(socket, ctx)
@socket = socket
@ctx = ctx
+ @eng_ctx = IS_JRUBY ? @ctx : SSLContext.new(ctx)
end
- def to_io
- @socket
- end
-
def accept
@ctx.check
io = @socket.accept
- engine = Engine.server @ctx
-
+ engine = Engine.server @eng_ctx
Socket.new io, engine
end
def accept_nonblock
@ctx.check
io = @socket.accept_nonblock
- engine = Engine.server @ctx
-
+ engine = Engine.server @eng_ctx
Socket.new io, engine
end
+ # @!attribute [r] to_io
+ def to_io
+ @socket
+ end
+
+ # @!attribute [r] addr
+ # @version 5.0.0
+ def addr
+ @socket.addr
+ end
+
def close
@socket.close unless @socket.closed? # closed? call is for Windows
+ end
+
+ def closed?
+ @socket.closed?
end
end
end
end