mirror of
https://github.com/puma/puma.git
synced 2022-11-09 13:48:40 -05:00
4ae0de4f4c
Adds support for `verify_mode` to configure client authentication when running under JRuby. Things to note: - Assumes the CA used to verify client certs is in the same java keystore file that is used when setting up the HTTPS TLS listener. We could split this out, but not sure if it's necessary. - Friendly/helpful error messages explaining why the verification failed are not present in the same way they are in the CRuby/OpenSSL code path. I'm not sure how to make them available. - I did not include any code to create the `keystore.jks` file in the `examples/puma/client-certs` directory because I didn't see any existing code to create the `examples/puma/keystore.jks` file. The commands to create this keystore would be: ``` cd examples/puma/client-certs openssl pkcs12 -chain -CAfile ./ca.crt -export -password pass:blahblah -inkey server.key -in server.crt -name server -out server.p12 keytool -importkeystore -srckeystore server.p12 -srcstoretype pkcs12 -srcstorepass blahblah -destkeystore keystore.jks -deststoretype JKS -storepass blahblah keytool -importcert -alias ca -noprompt -trustcacerts -file ca.crt -keystore keystore.jks -storepass blahblah ```
230 lines
6.3 KiB
Ruby
230 lines
6.3 KiB
Ruby
require "rbconfig"
|
|
require 'test/unit'
|
|
require 'socket'
|
|
require 'openssl'
|
|
|
|
require 'puma/minissl'
|
|
require 'puma/server'
|
|
|
|
require 'net/https'
|
|
|
|
class SSLEventsHelper < ::Puma::Events
|
|
attr_accessor :addr, :cert, :error
|
|
|
|
def ssl_error(server, peeraddr, peercert, error)
|
|
self.addr = peeraddr
|
|
self.cert = peercert
|
|
self.error = error
|
|
end
|
|
end
|
|
|
|
DISABLE_SSL = begin
|
|
Puma::MiniSSL.check
|
|
rescue
|
|
true
|
|
else
|
|
false
|
|
end
|
|
|
|
class TestPumaServerSSL < Test::Unit::TestCase
|
|
|
|
def setup
|
|
return if DISABLE_SSL
|
|
@port = 3212
|
|
@host = "127.0.0.1"
|
|
|
|
@app = lambda { |env| [200, {}, [env['rack.url_scheme']]] }
|
|
|
|
@ctx = Puma::MiniSSL::Context.new
|
|
|
|
if defined?(JRUBY_VERSION)
|
|
@ctx.keystore = File.expand_path "../../examples/puma/keystore.jks", __FILE__
|
|
@ctx.keystore_pass = 'blahblah'
|
|
else
|
|
@ctx.key = File.expand_path "../../examples/puma/puma_keypair.pem", __FILE__
|
|
@ctx.cert = File.expand_path "../../examples/puma/cert_puma.pem", __FILE__
|
|
end
|
|
|
|
@ctx.verify_mode = Puma::MiniSSL::VERIFY_NONE
|
|
|
|
@events = SSLEventsHelper.new STDOUT, STDERR
|
|
@server = Puma::Server.new @app, @events
|
|
@server.add_ssl_listener @host, @port, @ctx
|
|
@server.run
|
|
|
|
@http = Net::HTTP.new @host, @port
|
|
@http.use_ssl = true
|
|
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
end
|
|
|
|
def teardown
|
|
return if DISABLE_SSL
|
|
@server.stop(true)
|
|
end
|
|
|
|
def test_url_scheme_for_https
|
|
return if DISABLE_SSL
|
|
|
|
body = nil
|
|
@http.start do
|
|
req = Net::HTTP::Get.new "/", {}
|
|
|
|
@http.request(req) do |rep|
|
|
body = rep.body
|
|
end
|
|
end
|
|
|
|
assert_equal "https", body
|
|
end
|
|
|
|
def test_very_large_return
|
|
return if DISABLE_SSL
|
|
|
|
giant = "x" * 2056610
|
|
|
|
@server.app = proc do
|
|
[200, {}, [giant]]
|
|
end
|
|
|
|
body = nil
|
|
@http.start do
|
|
req = Net::HTTP::Get.new "/"
|
|
@http.request(req) do |rep|
|
|
body = rep.body
|
|
end
|
|
end
|
|
|
|
assert_equal giant.bytesize, body.bytesize
|
|
end
|
|
|
|
def test_form_submit
|
|
return if DISABLE_SSL
|
|
|
|
body = nil
|
|
@http.start do
|
|
req = Net::HTTP::Post.new '/'
|
|
req.set_form_data('a' => '1', 'b' => '2')
|
|
|
|
@http.request(req) do |rep|
|
|
body = rep.body
|
|
end
|
|
|
|
end
|
|
|
|
assert_equal "https", body
|
|
end
|
|
|
|
unless RUBY_VERSION =~ /^1.8/
|
|
def test_ssl_v3_rejection
|
|
return if DISABLE_SSL
|
|
@http.ssl_version='SSLv3'
|
|
assert_raises(OpenSSL::SSL::SSLError) do
|
|
@http.start do
|
|
Net::HTTP::Get.new '/'
|
|
end
|
|
end
|
|
unless defined?(JRUBY_VERSION)
|
|
assert_match("wrong version number", @events.error.message) if @events.error
|
|
end
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
# client-side TLS authentication tests
|
|
class TestPumaServerSSLClient < Test::Unit::TestCase
|
|
|
|
def assert_ssl_client_error_match(error, subject=nil, &blk)
|
|
@port = 3212
|
|
@host = "127.0.0.1"
|
|
|
|
@app = lambda { |env| [200, {}, [env['rack.url_scheme']]] }
|
|
|
|
@ctx = Puma::MiniSSL::Context.new
|
|
if defined?(JRUBY_VERSION)
|
|
@ctx.keystore = File.expand_path "../../examples/puma/client-certs/keystore.jks", __FILE__
|
|
@ctx.keystore_pass = 'blahblah'
|
|
else
|
|
@ctx.key = File.expand_path "../../examples/puma/client-certs/server.key", __FILE__
|
|
@ctx.cert = File.expand_path "../../examples/puma/client-certs/server.crt", __FILE__
|
|
@ctx.ca = File.expand_path "../../examples/puma/client-certs/ca.crt", __FILE__
|
|
end
|
|
@ctx.verify_mode = Puma::MiniSSL::VERIFY_PEER | Puma::MiniSSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
|
|
|
events = SSLEventsHelper.new STDOUT, STDERR
|
|
@server = Puma::Server.new @app, events
|
|
@server.add_ssl_listener @host, @port, @ctx
|
|
@server.run
|
|
|
|
@http = Net::HTTP.new @host, @port
|
|
@http.use_ssl = true
|
|
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
|
|
blk.call(@http)
|
|
|
|
client_error = false
|
|
begin
|
|
@http.start do
|
|
req = Net::HTTP::Get.new "/", {}
|
|
@http.request(req)
|
|
end
|
|
rescue OpenSSL::SSL::SSLError
|
|
client_error = true
|
|
end
|
|
|
|
sleep 0.1
|
|
assert_equal !!error, client_error
|
|
# The JRuby MiniSSL implementation lacks error capturing currently, so we can't inspect the
|
|
# messages here
|
|
unless defined?(JRUBY_VERSION)
|
|
assert_match error, events.error.message if error
|
|
assert_equal @host, events.addr if error
|
|
assert_equal subject, events.cert.subject.to_s if subject
|
|
end
|
|
ensure
|
|
@server.stop(true)
|
|
end
|
|
|
|
def test_verify_fail_if_no_client_cert
|
|
return if DISABLE_SSL
|
|
|
|
assert_ssl_client_error_match 'peer did not return a certificate' do |http|
|
|
# nothing
|
|
end
|
|
end
|
|
|
|
def test_verify_fail_if_client_unknown_ca
|
|
return if DISABLE_SSL
|
|
|
|
assert_ssl_client_error_match('self signed certificate in certificate chain', '/DC=net/DC=puma/CN=ca-unknown') do |http|
|
|
key = File.expand_path "../../examples/puma/client-certs/client_unknown.key", __FILE__
|
|
crt = File.expand_path "../../examples/puma/client-certs/client_unknown.crt", __FILE__
|
|
http.key = OpenSSL::PKey::RSA.new File.read(key)
|
|
http.cert = OpenSSL::X509::Certificate.new File.read(crt)
|
|
http.ca_file = File.expand_path "../../examples/puma/client-certs/unknown_ca.crt", __FILE__
|
|
end
|
|
end
|
|
|
|
def test_verify_fail_if_client_expired_cert
|
|
return if DISABLE_SSL
|
|
assert_ssl_client_error_match('certificate has expired', '/DC=net/DC=puma/CN=client-expired') do |http|
|
|
key = File.expand_path "../../examples/puma/client-certs/client_expired.key", __FILE__
|
|
crt = File.expand_path "../../examples/puma/client-certs/client_expired.crt", __FILE__
|
|
http.key = OpenSSL::PKey::RSA.new File.read(key)
|
|
http.cert = OpenSSL::X509::Certificate.new File.read(crt)
|
|
http.ca_file = File.expand_path "../../examples/puma/client-certs/ca.crt", __FILE__
|
|
end
|
|
end
|
|
|
|
def test_verify_client_cert
|
|
return if DISABLE_SSL
|
|
assert_ssl_client_error_match(nil) do |http|
|
|
key = File.expand_path "../../examples/puma/client-certs/client.key", __FILE__
|
|
crt = File.expand_path "../../examples/puma/client-certs/client.crt", __FILE__
|
|
http.key = OpenSSL::PKey::RSA.new File.read(key)
|
|
http.cert = OpenSSL::X509::Certificate.new File.read(crt)
|
|
http.ca_file = File.expand_path "../../examples/puma/client-certs/ca.crt", __FILE__
|
|
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
end
|
|
end
|
|
end
|