mirror of
https://github.com/puma/puma.git
synced 2022-11-09 13:48:40 -05:00
Merge pull request #833 from joemiller/jruby-client-tls-auth
support TLS client auth (verify_mode) in jruby
This commit is contained in:
commit
611d86f302
4 changed files with 85 additions and 64 deletions
BIN
examples/puma/client-certs/keystore.jks
Normal file
BIN
examples/puma/client-certs/keystore.jks
Normal file
Binary file not shown.
BIN
examples/puma/client-certs/server.p12
Normal file
BIN
examples/puma/client-certs/server.p12
Normal file
Binary file not shown.
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue