2018-09-17 12:41:14 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2017-11-20 08:24:02 -05:00
|
|
|
begin
|
|
|
|
require 'io/wait'
|
2018-10-28 20:29:18 -04:00
|
|
|
rescue LoadError
|
2017-11-20 08:24:02 -05:00
|
|
|
end
|
|
|
|
|
2020-05-13 11:58:20 -04:00
|
|
|
# need for Puma::MiniSSL::OPENSSL constants used in `HAS_TLS1_3`
|
|
|
|
require 'puma/puma_http11'
|
|
|
|
|
2013-03-18 19:20:17 -04:00
|
|
|
module Puma
|
|
|
|
module MiniSSL
|
2020-09-17 11:15:19 -04:00
|
|
|
# Define constant at runtime, as it's easy to determine at built time,
|
2020-05-13 11:58:20 -04:00
|
|
|
# but Puma could (it shouldn't) be loaded with an older OpenSSL version
|
2020-09-17 11:15:19 -04:00
|
|
|
# @version 5.0.0
|
2020-05-13 11:58:20 -04:00
|
|
|
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
|
|
|
|
|
2013-03-18 19:20:17 -04:00
|
|
|
class Socket
|
|
|
|
def initialize(socket, engine)
|
|
|
|
@socket = socket
|
|
|
|
@engine = engine
|
2015-01-13 23:11:26 -05:00
|
|
|
@peercert = nil
|
2013-03-18 19:20:17 -04:00
|
|
|
end
|
2012-08-22 19:53:25 -04:00
|
|
|
|
2020-09-25 14:50:57 -04:00
|
|
|
# @!attribute [r] to_io
|
2013-03-18 19:20:17 -04:00
|
|
|
def to_io
|
|
|
|
@socket
|
|
|
|
end
|
2012-08-22 19:53:25 -04:00
|
|
|
|
2018-03-05 23:52:34 -05:00
|
|
|
def closed?
|
|
|
|
@socket.closed?
|
|
|
|
end
|
|
|
|
|
2020-09-17 11:15:19 -04:00
|
|
|
# Returns a two element array,
|
|
|
|
# first is protocol version (SSL_get_version),
|
2020-05-13 11:58:20 -04:00
|
|
|
# second is 'handshake' state (SSL_state_string)
|
|
|
|
#
|
2020-09-17 11:15:19 -04:00
|
|
|
# Used for dropping tcp connections to ssl.
|
|
|
|
# See OpenSSL ssl/ssl_stat.c SSL_state_string for info
|
2020-09-25 14:50:57 -04:00
|
|
|
# @!attribute [r] ssl_version_state
|
2020-09-17 11:15:19 -04:00
|
|
|
# @version 5.0.0
|
2020-05-13 11:58:20 -04:00
|
|
|
#
|
|
|
|
def ssl_version_state
|
|
|
|
IS_JRUBY ? [nil, nil] : @engine.ssl_vers_st
|
|
|
|
end
|
|
|
|
|
2020-09-17 11:15:19 -04:00
|
|
|
# Used to check the handshake status, in particular when a TCP connection
|
2020-05-13 11:58:20 -04:00
|
|
|
# is made with TLSv1.3 as an available protocol
|
2020-09-17 11:15:19 -04:00
|
|
|
# @version 5.0.0
|
2020-05-13 11:58:20 -04:00
|
|
|
def bad_tlsv1_3?
|
|
|
|
HAS_TLS1_3 && @engine.ssl_vers_st == ['TLSv1.3', 'SSLERR']
|
|
|
|
end
|
|
|
|
private :bad_tlsv1_3?
|
|
|
|
|
2013-03-18 19:20:17 -04:00
|
|
|
def readpartial(size)
|
|
|
|
while true
|
|
|
|
output = @engine.read
|
|
|
|
return output if output
|
2012-08-22 19:53:25 -04:00
|
|
|
|
2013-03-18 19:20:17 -04:00
|
|
|
data = @socket.readpartial(size)
|
|
|
|
@engine.inject(data)
|
|
|
|
output = @engine.read
|
2012-08-23 01:34:10 -04:00
|
|
|
|
2013-03-18 19:20:17 -04:00
|
|
|
return output if output
|
2012-08-23 01:34:10 -04:00
|
|
|
|
2013-03-18 19:20:17 -04:00
|
|
|
while neg_data = @engine.extract
|
|
|
|
@socket.write neg_data
|
|
|
|
end
|
2012-08-23 01:34:10 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-11-24 22:57:12 -05:00
|
|
|
def engine_read_all
|
|
|
|
output = @engine.read
|
2020-05-13 11:58:20 -04:00
|
|
|
raise SSLError.exception "HTTP connection?" if bad_tlsv1_3?
|
2013-11-24 22:57:12 -05:00
|
|
|
while output and additional_output = @engine.read
|
|
|
|
output << additional_output
|
|
|
|
end
|
|
|
|
output
|
|
|
|
end
|
|
|
|
|
2017-05-01 12:54:00 -04:00
|
|
|
def read_nonblock(size, *_)
|
|
|
|
# *_ is to deal with keyword args that were added
|
|
|
|
# at some point (and being used in the wild)
|
2013-03-18 19:20:17 -04:00
|
|
|
while true
|
2013-11-24 22:57:12 -05:00
|
|
|
output = engine_read_all
|
2013-03-18 19:20:17 -04:00
|
|
|
return output if output
|
2012-08-23 01:34:10 -04:00
|
|
|
|
2019-07-19 12:07:47 -04:00
|
|
|
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?
|
|
|
|
return nil
|
|
|
|
end
|
2012-08-23 01:34:10 -04:00
|
|
|
|
2013-03-18 19:20:17 -04:00
|
|
|
@engine.inject(data)
|
2013-11-24 22:57:12 -05:00
|
|
|
output = engine_read_all
|
2012-08-22 19:53:25 -04:00
|
|
|
|
2013-03-18 19:20:17 -04:00
|
|
|
return output if output
|
2012-08-22 19:53:25 -04:00
|
|
|
|
2013-03-18 19:20:17 -04:00
|
|
|
while neg_data = @engine.extract
|
|
|
|
@socket.write neg_data
|
|
|
|
end
|
2012-08-22 19:53:25 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-03-18 19:20:17 -04:00
|
|
|
def write(data)
|
2018-03-06 00:26:04 -05:00
|
|
|
return 0 if data.empty?
|
|
|
|
|
2013-05-06 14:32:03 -04:00
|
|
|
need = data.bytesize
|
2012-08-22 19:53:25 -04:00
|
|
|
|
2013-03-18 19:20:17 -04:00
|
|
|
while true
|
|
|
|
wrote = @engine.write data
|
|
|
|
enc = @engine.extract
|
2012-08-22 19:53:25 -04:00
|
|
|
|
2013-06-18 02:20:54 -04:00
|
|
|
while enc
|
|
|
|
@socket.write enc
|
|
|
|
enc = @engine.extract
|
2013-03-18 19:20:17 -04:00
|
|
|
end
|
2012-08-22 19:53:25 -04:00
|
|
|
|
2013-03-18 19:20:17 -04:00
|
|
|
need -= wrote
|
2012-08-22 19:53:25 -04:00
|
|
|
|
2013-05-06 14:32:03 -04:00
|
|
|
return data.bytesize if need == 0
|
2012-08-22 19:53:25 -04:00
|
|
|
|
2014-05-05 17:30:15 -04:00
|
|
|
data = data[wrote..-1]
|
2013-03-18 19:20:17 -04:00
|
|
|
end
|
2012-08-22 19:53:25 -04:00
|
|
|
end
|
|
|
|
|
2013-03-18 19:20:17 -04:00
|
|
|
alias_method :syswrite, :write
|
2016-09-17 17:16:14 -04:00
|
|
|
alias_method :<<, :write
|
2012-09-10 11:50:43 -04:00
|
|
|
|
2017-05-01 12:54:00 -04:00
|
|
|
# This is a temporary fix to deal with websockets code using
|
2020-09-17 11:15:19 -04:00
|
|
|
# write_nonblock.
|
2020-09-17 11:40:45 -04:00
|
|
|
|
2020-09-17 11:15:19 -04:00
|
|
|
# The problem with implementing it properly
|
2017-05-01 12:54:00 -04:00
|
|
|
# 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
|
2020-09-17 11:15:19 -04:00
|
|
|
# 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.
|
|
|
|
#
|
2017-05-01 12:54:00 -04:00
|
|
|
def write_nonblock(data, *_)
|
|
|
|
write data
|
|
|
|
end
|
|
|
|
|
2013-03-18 19:20:17 -04:00
|
|
|
def flush
|
|
|
|
@socket.flush
|
|
|
|
end
|
2012-08-23 00:43:40 -04:00
|
|
|
|
2017-06-13 02:09:34 -04:00
|
|
|
def read_and_drop(timeout = 1)
|
|
|
|
return :timeout unless IO.select([@socket], nil, nil, timeout)
|
2020-03-24 17:13:31 -04:00
|
|
|
case @socket.read_nonblock(1024, exception: false)
|
|
|
|
when nil
|
|
|
|
:eof
|
|
|
|
when :wait_readable
|
|
|
|
:eagain
|
|
|
|
else
|
|
|
|
:drop
|
|
|
|
end
|
2017-06-13 02:09:34 -04:00
|
|
|
end
|
2016-07-25 20:20:17 -04:00
|
|
|
|
2017-06-13 02:09:34 -04:00
|
|
|
def should_drop_bytes?
|
|
|
|
@engine.init? || !@engine.shutdown
|
|
|
|
end
|
2016-07-25 20:20:17 -04:00
|
|
|
|
2017-06-13 02:09:34 -04:00
|
|
|
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.
|
2020-03-07 09:15:43 -05:00
|
|
|
return if [:timeout, :eof].include?(read_and_drop(1)) while should_drop_bytes?
|
2016-07-25 20:20:17 -04:00
|
|
|
rescue IOError, SystemCallError
|
2017-07-19 14:22:36 -04:00
|
|
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
2016-07-25 20:20:17 -04:00
|
|
|
# nothing
|
|
|
|
ensure
|
|
|
|
@socket.close
|
|
|
|
end
|
2013-03-18 19:20:17 -04:00
|
|
|
end
|
2012-08-23 00:43:40 -04:00
|
|
|
|
2020-09-25 14:50:57 -04:00
|
|
|
# @!attribute [r] peeraddr
|
2013-03-18 19:20:17 -04:00
|
|
|
def peeraddr
|
|
|
|
@socket.peeraddr
|
|
|
|
end
|
2015-01-13 23:11:26 -05:00
|
|
|
|
2020-09-25 14:50:57 -04:00
|
|
|
# @!attribute [r] peercert
|
2015-01-13 23:11:26 -05:00
|
|
|
def peercert
|
|
|
|
return @peercert if @peercert
|
|
|
|
|
|
|
|
raw = @engine.peercert
|
|
|
|
return nil unless raw
|
|
|
|
|
|
|
|
@peercert = OpenSSL::X509::Certificate.new raw
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-05-13 11:58:20 -04:00
|
|
|
if IS_JRUBY
|
2020-09-01 18:00:36 -04:00
|
|
|
OPENSSL_NO_SSL3 = false
|
|
|
|
OPENSSL_NO_TLS1 = false
|
|
|
|
|
2015-01-13 23:11:26 -05:00
|
|
|
class SSLError < StandardError
|
|
|
|
# Define this for jruby even though it isn't used.
|
|
|
|
end
|
2012-08-23 00:43:40 -04:00
|
|
|
end
|
2012-08-22 19:53:25 -04:00
|
|
|
|
2013-03-18 19:20:17 -04:00
|
|
|
class Context
|
|
|
|
attr_accessor :verify_mode
|
2019-07-09 17:28:07 -04:00
|
|
|
attr_reader :no_tlsv1, :no_tlsv1_1
|
2018-04-17 09:25:22 -04:00
|
|
|
|
|
|
|
def initialize
|
2019-07-09 17:28:07 -04:00
|
|
|
@no_tlsv1 = false
|
|
|
|
@no_tlsv1_1 = false
|
2018-04-17 09:25:22 -04:00
|
|
|
end
|
2012-11-30 07:47:47 -05:00
|
|
|
|
2020-05-13 11:58:20 -04:00
|
|
|
if IS_JRUBY
|
2014-05-05 17:30:15 -04:00
|
|
|
# jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
|
|
|
|
attr_reader :keystore
|
|
|
|
attr_accessor :keystore_pass
|
2017-11-24 10:14:23 -05:00
|
|
|
attr_accessor :ssl_cipher_list
|
2012-11-30 07:47:47 -05:00
|
|
|
|
2014-05-05 17:30:15 -04:00
|
|
|
def keystore=(keystore)
|
|
|
|
raise ArgumentError, "No such keystore file '#{keystore}'" unless File.exist? keystore
|
|
|
|
@keystore = keystore
|
|
|
|
end
|
2016-07-24 17:29:23 -04:00
|
|
|
|
|
|
|
def check
|
|
|
|
raise "Keystore not configured" unless @keystore
|
|
|
|
end
|
|
|
|
|
2014-05-05 17:30:15 -04:00
|
|
|
else
|
|
|
|
# non-jruby Context properties
|
|
|
|
attr_reader :key
|
|
|
|
attr_reader :cert
|
2015-01-13 23:11:26 -05:00
|
|
|
attr_reader :ca
|
2017-11-24 10:14:23 -05:00
|
|
|
attr_accessor :ssl_cipher_filter
|
2014-05-05 17:30:15 -04:00
|
|
|
|
|
|
|
def key=(key)
|
|
|
|
raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
|
|
|
|
@key = key
|
|
|
|
end
|
2012-11-30 07:47:47 -05:00
|
|
|
|
2014-05-05 17:30:15 -04:00
|
|
|
def cert=(cert)
|
|
|
|
raise ArgumentError, "No such cert file '#{cert}'" unless File.exist? cert
|
|
|
|
@cert = cert
|
|
|
|
end
|
2015-01-13 23:11:26 -05:00
|
|
|
|
|
|
|
def ca=(ca)
|
|
|
|
raise ArgumentError, "No such ca file '#{ca}'" unless File.exist? ca
|
|
|
|
@ca = ca
|
|
|
|
end
|
2016-07-24 17:29:23 -04:00
|
|
|
|
|
|
|
def check
|
|
|
|
raise "Key not configured" unless @key
|
|
|
|
raise "Cert not configured" unless @cert
|
|
|
|
end
|
2013-03-18 19:20:17 -04:00
|
|
|
end
|
2018-04-17 09:25:22 -04:00
|
|
|
|
2019-07-09 17:28:07 -04:00
|
|
|
# disables TLSv1
|
2020-09-25 14:50:57 -04:00
|
|
|
# @!attribute [w] no_tlsv1=
|
2018-04-17 09:25:22 -04:00
|
|
|
def no_tlsv1=(tlsv1)
|
2020-09-17 11:15:19 -04:00
|
|
|
raise ArgumentError, "Invalid value of no_tlsv1=" unless ['true', 'false', true, false].include?(tlsv1)
|
2018-04-17 09:25:22 -04:00
|
|
|
@no_tlsv1 = tlsv1
|
|
|
|
end
|
|
|
|
|
2019-07-09 17:28:07 -04:00
|
|
|
# disables TLSv1 and TLSv1.1. Overrides `#no_tlsv1=`
|
2020-09-25 14:50:57 -04:00
|
|
|
# @!attribute [w] no_tlsv1_1=
|
2019-07-09 17:28:07 -04:00
|
|
|
def no_tlsv1_1=(tlsv1_1)
|
2020-09-17 11:15:19 -04:00
|
|
|
raise ArgumentError, "Invalid value of no_tlsv1_1=" unless ['true', 'false', true, false].include?(tlsv1_1)
|
2019-07-09 17:28:07 -04:00
|
|
|
@no_tlsv1_1 = tlsv1_1
|
|
|
|
end
|
|
|
|
|
2012-11-30 07:47:47 -05:00
|
|
|
end
|
2012-08-23 00:43:40 -04:00
|
|
|
|
2013-03-18 19:20:17 -04:00
|
|
|
VERIFY_NONE = 0
|
|
|
|
VERIFY_PEER = 1
|
2015-01-13 23:11:26 -05:00
|
|
|
VERIFY_FAIL_IF_NO_PEER_CERT = 2
|
2012-08-23 00:43:40 -04:00
|
|
|
|
2013-03-18 19:20:17 -04:00
|
|
|
class Server
|
|
|
|
def initialize(socket, ctx)
|
|
|
|
@socket = socket
|
|
|
|
@ctx = ctx
|
|
|
|
end
|
2012-08-22 19:53:25 -04:00
|
|
|
|
2020-09-25 14:50:57 -04:00
|
|
|
# @!attribute [r] to_io
|
2013-03-18 19:20:17 -04:00
|
|
|
def to_io
|
|
|
|
@socket
|
|
|
|
end
|
2012-08-22 19:53:25 -04:00
|
|
|
|
2013-03-18 19:20:17 -04:00
|
|
|
def accept
|
2016-07-24 17:29:23 -04:00
|
|
|
@ctx.check
|
2013-03-18 19:20:17 -04:00
|
|
|
io = @socket.accept
|
2014-05-05 17:30:15 -04:00
|
|
|
engine = Engine.server @ctx
|
2012-08-22 19:53:25 -04:00
|
|
|
|
2013-03-18 19:20:17 -04:00
|
|
|
Socket.new io, engine
|
|
|
|
end
|
2012-08-23 00:43:40 -04:00
|
|
|
|
2013-03-18 19:20:17 -04:00
|
|
|
def accept_nonblock
|
2016-07-24 17:29:23 -04:00
|
|
|
@ctx.check
|
2013-03-18 19:20:17 -04:00
|
|
|
io = @socket.accept_nonblock
|
2014-05-05 17:30:15 -04:00
|
|
|
engine = Engine.server @ctx
|
2012-09-10 11:50:43 -04:00
|
|
|
|
2013-03-18 19:20:17 -04:00
|
|
|
Socket.new io, engine
|
|
|
|
end
|
2012-09-10 11:50:43 -04:00
|
|
|
|
2020-09-25 14:50:57 -04:00
|
|
|
# @!attribute [r] addr
|
2020-09-17 11:15:19 -04:00
|
|
|
# @version 5.0.0
|
2019-11-19 13:36:15 -05:00
|
|
|
def addr
|
|
|
|
@socket.addr
|
|
|
|
end
|
|
|
|
|
2013-03-18 19:20:17 -04:00
|
|
|
def close
|
2018-03-17 12:02:16 -04:00
|
|
|
@socket.close unless @socket.closed? # closed? call is for Windows
|
2013-03-18 19:20:17 -04:00
|
|
|
end
|
2012-08-23 00:43:40 -04:00
|
|
|
end
|
2012-08-22 19:53:25 -04:00
|
|
|
end
|
|
|
|
end
|