1
0
Fork 0
mirror of https://github.com/puma/puma.git synced 2022-11-09 13:48:40 -05:00
puma--puma/test/test_puma_server_ssl.rb
joe miller 4ae0de4f4c support TLS client auth (verify_mode) in jruby
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
```
2015-11-28 18:17:01 -08:00

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