diff --git a/examples/puma/client-certs/keystore.jks b/examples/puma/client-certs/keystore.jks new file mode 100644 index 00000000..6d734fad Binary files /dev/null and b/examples/puma/client-certs/keystore.jks differ diff --git a/examples/puma/client-certs/server.p12 b/examples/puma/client-certs/server.p12 new file mode 100644 index 00000000..6fb6ba60 Binary files /dev/null and b/examples/puma/client-certs/server.p12 differ diff --git a/ext/puma_http11/org/jruby/puma/MiniSSL.java b/ext/puma_http11/org/jruby/puma/MiniSSL.java index b284ed5d..3655df78 100644 --- a/ext/puma_http11/org/jruby/puma/MiniSSL.java +++ b/ext/puma_http11/org/jruby/puma/MiniSSL.java @@ -13,6 +13,7 @@ import org.jruby.runtime.builtin.IRubyObject; import org.jruby.util.ByteList; import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; @@ -136,23 +137,36 @@ public class MiniSSL extends RubyObject { public IRubyObject initialize(ThreadContext threadContext, IRubyObject miniSSLContext) throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException { KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); + KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType()); char[] password = miniSSLContext.callMethod(threadContext, "keystore_pass").convertToString().asJavaString().toCharArray(); - ks.load(new FileInputStream(miniSSLContext.callMethod(threadContext, "keystore").convertToString().asJavaString()), - password); + String keystoreFile = miniSSLContext.callMethod(threadContext, "keystore").convertToString().asJavaString(); + ks.load(new FileInputStream(keystoreFile), password); + ts.load(new FileInputStream(keystoreFile), password); KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); kmf.init(ks, password); + TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); + tmf.init(ts); + SSLContext sslCtx = SSLContext.getInstance("TLS"); - sslCtx.init(kmf.getKeyManagers(), null, null); + sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); engine = sslCtx.createSSLEngine(); String[] protocols = new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" }; engine.setEnabledProtocols(protocols); engine.setUseClientMode(false); + long verify_mode = miniSSLContext.callMethod(threadContext, "verify_mode").convertToInteger().getLongValue(); + if ((verify_mode & 0x1) != 0) { // 'peer' + engine.setWantClientAuth(true); + } + if ((verify_mode & 0x2) != 0) { // 'force_peer' + engine.setNeedClientAuth(true); + } + SSLSession session = engine.getSession(); inboundNetData = new MiniSSLBuffer(session.getPacketBufferSize()); outboundAppData = new MiniSSLBuffer(session.getApplicationBufferSize()); diff --git a/test/test_puma_server_ssl.rb b/test/test_puma_server_ssl.rb index 12e68b97..cd506ec9 100644 --- a/test/test_puma_server_ssl.rb +++ b/test/test_puma_server_ssl.rb @@ -132,92 +132,99 @@ class TestPumaServerSSL < Test::Unit::TestCase end # client-side TLS authentication tests -unless defined?(JRUBY_VERSION) - class TestPumaServerSSLClient < Test::Unit::TestCase +class TestPumaServerSSLClient < Test::Unit::TestCase - def assert_ssl_client_error_match(error, subject=nil, &blk) - @port = 3212 - @host = "127.0.0.1" + 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']]] } + @app = lambda { |env| [200, {}, [env['rack.url_scheme']]] } - @ctx = Puma::MiniSSL::Context.new + @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__ - @ctx.verify_mode = Puma::MiniSSL::VERIFY_PEER | Puma::MiniSSL::VERIFY_FAIL_IF_NO_PEER_CERT + 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 + 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 + @http = Net::HTTP.new @host, @port + @http.use_ssl = true + @http.verify_mode = OpenSSL::SSL::VERIFY_NONE - blk.call(@http) + 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 + 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 + 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 - ensure - @server.stop(true) end + ensure + @server.stop(true) + end - def test_verify_fail_if_no_client_cert - return if DISABLE_SSL + 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 + 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 + 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 + 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 + 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 + 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