1
0
Fork 0
mirror of https://github.com/puma/puma.git synced 2022-11-09 13:48:40 -05:00
puma--puma/lib/puma/minissl.rb
Julian Langschaedel e8d25b30f3 ssl: Add Client Side Certificate Auth
Add Client Side Certificate Auth feature and handling to puma's MiniSSL. Also exposes SSL errors to puma/apps.

 compatibility notes: MRI only

 shell example:

   puma -b 'ssl://127.0.0.1:9292?key=path_to_key&cert=path_to_cert&ca=path_to_ca&verify_mode=force_peer'

 code example: (examples/client_side_ssl)

    app = proc {|env| p env['puma.peercert']; [200, {}, ["hey"]] }

    events = SSLEvents.new($stdout, $stderr)
    server = Puma::Server.new(app, events)

    admin_context             = Puma::MiniSSL::Context.new
    admin_context.key         = KEY_PATH
    admin_context.cert        = CERT_PATH
    admin_context.ca          = CA_CERT_PATH
    admin_context.verify_mode = Puma::MiniSSL::VERIFY_PEER | Puma::MiniSSL::VERIFY_FAIL_IF_NO_PEER_CERT

    server.add_ssl_listener("0.0.0.0", ADMIN_PORT, admin_context)
    server.min_threads = MIN_THREADS
    server.max_threads = MAX_THREADS
    server.persistent_timeout = IDLE_TIMEOUT
    server.run.join

 additional credits: Andy Alness <andy.alness@gmail.com>
2015-06-06 23:15:00 +02:00

175 lines
3.5 KiB
Ruby

module Puma
module MiniSSL
class Socket
def initialize(socket, engine)
@socket = socket
@engine = engine
@peercert = nil
end
def to_io
@socket
end
def readpartial(size)
while true
output = @engine.read
return output if output
data = @socket.readpartial(size)
@engine.inject(data)
output = @engine.read
return output if output
while neg_data = @engine.extract
@socket.write neg_data
end
end
end
def engine_read_all
output = @engine.read
while output and additional_output = @engine.read
output << additional_output
end
output
end
def read_nonblock(size)
while true
output = engine_read_all
return output if output
data = @socket.read_nonblock(size)
@engine.inject(data)
output = engine_read_all
return output if output
while neg_data = @engine.extract
@socket.write neg_data
end
end
end
def write(data)
need = data.bytesize
while true
wrote = @engine.write data
enc = @engine.extract
while enc
@socket.write enc
enc = @engine.extract
end
need -= wrote
return data.bytesize if need == 0
data = data[wrote..-1]
end
end
alias_method :syswrite, :write
def flush
@socket.flush
end
def close
@socket.close
end
def peeraddr
@socket.peeraddr
end
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)
class SSLError < StandardError
# Define this for jruby even though it isn't used.
end
end
class Context
attr_accessor :verify_mode
if defined?(JRUBY_VERSION)
# jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
attr_reader :keystore
attr_accessor :keystore_pass
def keystore=(keystore)
raise ArgumentError, "No such keystore file '#{keystore}'" unless File.exist? keystore
@keystore = keystore
end
else
# non-jruby Context properties
attr_reader :key
attr_reader :cert
attr_reader :ca
def key=(key)
raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
@key = key
end
def cert=(cert)
raise ArgumentError, "No such cert file '#{cert}'" unless File.exist? cert
@cert = cert
end
def ca=(ca)
raise ArgumentError, "No such ca file '#{ca}'" unless File.exist? ca
@ca = ca
end
end
end
VERIFY_NONE = 0
VERIFY_PEER = 1
VERIFY_FAIL_IF_NO_PEER_CERT = 2
class Server
def initialize(socket, ctx)
@socket = socket
@ctx = ctx
end
def to_io
@socket
end
def accept
io = @socket.accept
engine = Engine.server @ctx
Socket.new io, engine
end
def accept_nonblock
io = @socket.accept_nonblock
engine = Engine.server @ctx
Socket.new io, engine
end
def close
@socket.close
end
end
end
end