Java tutorial
/* * The MIT License * * Copyright (c) 2016, CloudBees, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.jenkinsci.remoting.protocol.impl; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.Pipe; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLSession; import org.apache.commons.io.IOUtils; import org.jenkinsci.remoting.protocol.IOBufferMatcher; import org.jenkinsci.remoting.protocol.IOBufferMatcherLayer; import org.jenkinsci.remoting.protocol.IOHubRule; import org.jenkinsci.remoting.protocol.NetworkLayerFactory; import org.jenkinsci.remoting.protocol.ProtocolStack; import org.jenkinsci.remoting.protocol.Repeat; import org.jenkinsci.remoting.protocol.RepeatRule; import org.jenkinsci.remoting.protocol.cert.RSAKeyPairRule; import org.jenkinsci.remoting.protocol.cert.SSLContextRule; import org.jenkinsci.remoting.protocol.cert.X509CertificateRule; import org.junit.After; import org.junit.Before; import org.junit.ClassRule; import org.junit.Rule; import org.junit.experimental.theories.DataPoint; import org.junit.experimental.theories.DataPoints; import org.junit.experimental.theories.Theories; import org.junit.experimental.theories.Theory; import org.junit.rules.RuleChain; import org.junit.rules.TestName; import org.junit.rules.Timeout; import org.junit.runner.RunWith; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; @RunWith(Theories.class) public class SSLEngineFilterLayerTest { private static final boolean fullTests = Boolean.getBoolean("fullTests"); private static RSAKeyPairRule clientKey = new RSAKeyPairRule(); private static RSAKeyPairRule serverKey = new RSAKeyPairRule(); private static RSAKeyPairRule caRootKey = new RSAKeyPairRule(); private static X509CertificateRule caRootCert = X509CertificateRule.create("caRoot", caRootKey, caRootKey); private static X509CertificateRule clientCert = X509CertificateRule.create("client", clientKey, caRootKey); private static X509CertificateRule serverCert = X509CertificateRule.create("server", serverKey, caRootKey); private static X509CertificateRule expiredClientCert = X509CertificateRule.create("expiredClient", clientKey, caRootKey, -10, -5, TimeUnit.DAYS); private static X509CertificateRule notYetValidServerCert = X509CertificateRule.create("notYetValidServer", serverKey, caRootKey, +5, +10, TimeUnit.DAYS); private static SSLContextRule clientCtx = new SSLContextRule("client").as(clientKey, clientCert, caRootCert) .trusting(caRootCert).trusting(serverCert); private static SSLContextRule serverCtx = new SSLContextRule("server").as(serverKey, serverCert, caRootCert) .trusting(caRootCert).trusting(clientCert); private static SSLContextRule expiredClientCtx = new SSLContextRule("expiredClient") .as(clientKey, expiredClientCert, caRootCert).trusting(caRootCert).trusting(serverCert); private static SSLContextRule notYetValidServerCtx = new SSLContextRule("notYetValidServer") .as(serverKey, notYetValidServerCert, caRootCert).trusting(caRootCert).trusting(clientCert); private static SSLContextRule untrustingClientCtx = new SSLContextRule("untrustingClient") .as(clientKey, clientCert).trusting(caRootCert); private static SSLContextRule untrustingServerCtx = new SSLContextRule("untrustingServer") .as(serverKey, serverCert).trusting(caRootCert); @ClassRule public static RuleChain staticCtx = RuleChain.outerRule(caRootKey).around(clientKey).around(serverKey) .around(caRootCert).around(clientCert).around(serverCert).around(expiredClientCert) .around(notYetValidServerCert).around(clientCtx).around(serverCtx).around(expiredClientCtx) .around(notYetValidServerCtx).around(untrustingClientCtx).around(untrustingServerCtx); @Rule public IOHubRule selector = new IOHubRule(); @Rule public TestName name = new TestName(); private Timeout globalTimeout = new Timeout(30, TimeUnit.SECONDS); @Rule public RuleChain ctx = RuleChain.outerRule(new RepeatRule()).around(globalTimeout); private Pipe clientToServer; private Pipe serverToClient; @DataPoint("blocking I/O") public static NetworkLayerFactory blocking() { return new NetworkLayerFactory.BIO(); } @DataPoint("non-blocking I/O") public static NetworkLayerFactory nonBlocking() { return new NetworkLayerFactory.NIO(); } @DataPoints public static BatchSendBufferingFilterLayer[] batchSizes() { List<BatchSendBufferingFilterLayer> result = new ArrayList<BatchSendBufferingFilterLayer>(); if (fullTests) { int length = 16; while (length < 65536) { result.add(new BatchSendBufferingFilterLayer(length)); if (length < 16) { length = length * 2; } else { length = length * 3 / 2; } } } else { result.add(new BatchSendBufferingFilterLayer(16)); result.add(new BatchSendBufferingFilterLayer(4096)); result.add(new BatchSendBufferingFilterLayer(65536)); } return result.toArray(new BatchSendBufferingFilterLayer[result.size()]); } @Before public void setUpPipe() throws Exception { clientToServer = Pipe.open(); serverToClient = Pipe.open(); } @After public void tearDownPipe() throws Exception { IOUtils.closeQuietly(clientToServer.sink()); IOUtils.closeQuietly(clientToServer.source()); IOUtils.closeQuietly(serverToClient.sink()); IOUtils.closeQuietly(serverToClient.source()); } @Theory public void smokes(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) throws Exception { SSLEngine serverEngine = serverCtx.createSSLEngine(); serverEngine.setUseClientMode(false); serverEngine.setNeedClientAuth(true); SSLEngine clientEngine = clientCtx.createSSLEngine(); clientEngine.setUseClientMode(true); ProtocolStack<IOBufferMatcher> client = ProtocolStack .on(clientFactory.create(selector.hub(), serverToClient.source(), clientToServer.sink())) .filter(new SSLEngineFilterLayer(clientEngine, null)).build(new IOBufferMatcherLayer()); ProtocolStack<IOBufferMatcher> server = ProtocolStack .on(serverFactory.create(selector.hub(), clientToServer.source(), serverToClient.sink())) .filter(new SSLEngineFilterLayer(serverEngine, null)).build(new IOBufferMatcherLayer()); byte[] expected = "Here is some sample data".getBytes("UTF-8"); ByteBuffer data = ByteBuffer.allocate(expected.length); data.put(expected); data.flip(); server.get().send(data); client.get().awaitByteContent(is(expected)); assertThat(client.get().asByteArray(), is(expected)); server.get().close(); client.get().awaitClose(); } @Theory public void clientRejectsServer(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) throws Exception { SSLEngine serverEngine = serverCtx.createSSLEngine(); serverEngine.setUseClientMode(false); serverEngine.setNeedClientAuth(true); SSLEngine clientEngine = clientCtx.createSSLEngine(); clientEngine.setUseClientMode(true); ProtocolStack<IOBufferMatcher> client = ProtocolStack .on(clientFactory.create(selector.hub(), serverToClient.source(), clientToServer.sink())) .filter(new SSLEngineFilterLayer(clientEngine, new SSLEngineFilterLayer.Listener() { @Override public void onHandshakeCompleted(SSLSession session) throws ConnectionRefusalException { throw new ConnectionRefusalException("Bad server"); } })).build(new IOBufferMatcherLayer()); ProtocolStack<IOBufferMatcher> server = ProtocolStack .on(serverFactory.create(selector.hub(), clientToServer.source(), serverToClient.sink())) .filter(new SSLEngineFilterLayer(serverEngine, null)).build(new IOBufferMatcherLayer()); IOBufferMatcher clientMatcher = client.get(); IOBufferMatcher serverMatcher = server.get(); clientMatcher.awaitClose(); serverMatcher.awaitClose(); assertThat(clientMatcher.getCloseCause(), instanceOf(ConnectionRefusalException.class)); assertThat(serverMatcher.getCloseCause(), instanceOf(ClosedChannelException.class)); } @Theory public void serverRejectsClient(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) throws Exception { Logger.getLogger(name.getMethodName()).log(Level.INFO, "Starting test with server {0} client {1}", new Object[] { serverFactory.getClass().getSimpleName(), clientFactory.getClass().getSimpleName(), }); SSLEngine serverEngine = serverCtx.createSSLEngine(); serverEngine.setUseClientMode(false); serverEngine.setNeedClientAuth(true); SSLEngine clientEngine = clientCtx.createSSLEngine(); clientEngine.setUseClientMode(true); ProtocolStack<IOBufferMatcher> client = ProtocolStack .on(clientFactory.create(selector.hub(), serverToClient.source(), clientToServer.sink())) .filter(new SSLEngineFilterLayer(clientEngine, null)).build(new IOBufferMatcherLayer()); ProtocolStack<IOBufferMatcher> server = ProtocolStack .on(serverFactory.create(selector.hub(), clientToServer.source(), serverToClient.sink())) .filter(new SSLEngineFilterLayer(serverEngine, new SSLEngineFilterLayer.Listener() { @Override public void onHandshakeCompleted(SSLSession session) throws ConnectionRefusalException { throw new ConnectionRefusalException("Bad client"); } })).build(new IOBufferMatcherLayer()); IOBufferMatcher clientMatcher = client.get(); IOBufferMatcher serverMatcher = server.get(); Logger.getLogger(name.getMethodName()).log(Level.INFO, "Waiting for client close"); clientMatcher.awaitClose(); Logger.getLogger(name.getMethodName()).log(Level.INFO, "Waiting for server close"); serverMatcher.awaitClose(); assertThat(clientMatcher.getCloseCause(), instanceOf(ClosedChannelException.class)); assertThat(serverMatcher.getCloseCause(), instanceOf(ConnectionRefusalException.class)); Logger.getLogger(name.getMethodName()).log(Level.INFO, "Done"); } @Theory public void untrustingClientDoesNotConnect(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) throws Exception { SSLEngine serverEngine = serverCtx.createSSLEngine(); serverEngine.setUseClientMode(false); serverEngine.setNeedClientAuth(true); SSLEngine clientEngine = untrustingClientCtx.createSSLEngine(); clientEngine.setUseClientMode(true); ProtocolStack<IOBufferMatcher> client = ProtocolStack .on(clientFactory.create(selector.hub(), serverToClient.source(), clientToServer.sink())) .filter(new SSLEngineFilterLayer(clientEngine, null)).build(new IOBufferMatcherLayer()); ProtocolStack<IOBufferMatcher> server = ProtocolStack .on(serverFactory.create(selector.hub(), clientToServer.source(), serverToClient.sink())) .filter(new SSLEngineFilterLayer(serverEngine, null)).build(new IOBufferMatcherLayer()); IOBufferMatcher clientMatcher = client.get(); IOBufferMatcher serverMatcher = server.get(); clientMatcher.awaitClose(); serverMatcher.awaitClose(); assertThat(clientMatcher.getCloseCause(), instanceOf(SSLHandshakeException.class)); assertThat(serverMatcher.getCloseCause(), instanceOf(ClosedChannelException.class)); } @Theory public void expiredClientDoesNotConnect(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) throws Exception { SSLEngine serverEngine = serverCtx.createSSLEngine(); serverEngine.setUseClientMode(false); serverEngine.setNeedClientAuth(true); SSLEngine clientEngine = expiredClientCtx.createSSLEngine(); clientEngine.setUseClientMode(true); ProtocolStack<IOBufferMatcher> client = ProtocolStack .on(clientFactory.create(selector.hub(), serverToClient.source(), clientToServer.sink())) .filter(new SSLEngineFilterLayer(clientEngine, null)).build(new IOBufferMatcherLayer()); ProtocolStack<IOBufferMatcher> server = ProtocolStack .on(serverFactory.create(selector.hub(), clientToServer.source(), serverToClient.sink())) .filter(new SSLEngineFilterLayer(serverEngine, null)).build(new IOBufferMatcherLayer()); IOBufferMatcher clientMatcher = client.get(); IOBufferMatcher serverMatcher = server.get(); clientMatcher.awaitClose(); serverMatcher.awaitClose(); assertThat(clientMatcher.getCloseCause(), instanceOf(ClosedChannelException.class)); assertThat(serverMatcher.getCloseCause(), instanceOf(SSLHandshakeException.class)); } @Theory public void clientDoesNotConnectToNotYetValidServer(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) throws Exception { SSLEngine serverEngine = notYetValidServerCtx.createSSLEngine(); serverEngine.setUseClientMode(false); serverEngine.setNeedClientAuth(true); SSLEngine clientEngine = expiredClientCtx.createSSLEngine(); clientEngine.setUseClientMode(true); ProtocolStack<IOBufferMatcher> client = ProtocolStack .on(clientFactory.create(selector.hub(), serverToClient.source(), clientToServer.sink())) .filter(new SSLEngineFilterLayer(clientEngine, null)).build(new IOBufferMatcherLayer()); ProtocolStack<IOBufferMatcher> server = ProtocolStack .on(serverFactory.create(selector.hub(), clientToServer.source(), serverToClient.sink())) .filter(new SSLEngineFilterLayer(serverEngine, null)).build(new IOBufferMatcherLayer()); IOBufferMatcher clientMatcher = client.get(); IOBufferMatcher serverMatcher = server.get(); clientMatcher.awaitClose(); serverMatcher.awaitClose(); assertThat(clientMatcher.getCloseCause(), instanceOf(SSLHandshakeException.class)); assertThat(serverMatcher.getCloseCause(), instanceOf(ClosedChannelException.class)); } @Theory @Repeat(value = 16, stopAfter = 1) public void concurrentStress_1_1(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) throws Exception { concurrentStress(serverFactory, clientFactory, 1, 1); } @Theory @Repeat(value = 16, stopAfter = 1) public void concurrentStress_512_512(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) throws Exception { concurrentStress(serverFactory, clientFactory, 512, 512); } @Theory @Repeat(value = 16, stopAfter = 1) public void concurrentStress_1k_1k(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) throws Exception { concurrentStress(serverFactory, clientFactory, 1024, 1024); } @Theory @Repeat(value = 16, stopAfter = 1) public void concurrentStress_2k_1k(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) throws Exception { concurrentStress(serverFactory, clientFactory, 2048, 1024); } @Theory @Repeat(value = 16, stopAfter = 1) public void concurrentStress_1k_2k(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) throws Exception { concurrentStress(serverFactory, clientFactory, 1024, 2048); } @Theory @Repeat(value = 16, stopAfter = 1) public void concurrentStress_2k_2k(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) throws Exception { concurrentStress(serverFactory, clientFactory, 2048, 2048); } @Theory public void concurrentStress_4k_4k_minus_1(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) throws Exception { concurrentStress(serverFactory, clientFactory, 4095, 4095); } @Theory public void concurrentStress_4k_4k(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) throws Exception { concurrentStress(serverFactory, clientFactory, 4096, 4096); } @Theory public void concurrentStress_4k_4k_plus_1(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) throws Exception { concurrentStress(serverFactory, clientFactory, 4097, 4097); } @Theory public void concurrentStress_16k_16k_minus_1(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) throws Exception { concurrentStress(serverFactory, clientFactory, 16383, 16383); } @Theory public void concurrentStress_16k_16k(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) throws Exception { concurrentStress(serverFactory, clientFactory, 16384, 16384); } @Theory public void concurrentStress_16k_16k_plus_1(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) throws Exception { concurrentStress(serverFactory, clientFactory, 16385, 16385); } @Theory public void concurrentStress_64k_64k(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory) throws Exception { concurrentStress(serverFactory, clientFactory, 65536, 65536); } private void concurrentStress(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory, int serverLimit, int clientLimit) throws java.io.IOException, InterruptedException, java.util.concurrent.ExecutionException, TimeoutException { Logger.getLogger(name.getMethodName()).log(Level.INFO, "Starting test with server {0} client {1} serverLimit {2} clientLimit {3}", new Object[] { serverFactory.getClass().getSimpleName(), clientFactory.getClass().getSimpleName(), serverLimit, clientLimit }); SSLEngine serverEngine = serverCtx.createSSLEngine(); serverEngine.setUseClientMode(false); serverEngine.setNeedClientAuth(true); SSLEngine clientEngine = clientCtx.createSSLEngine(); clientEngine.setUseClientMode(true); ProtocolStack<IOBufferMatcher> clientStack = ProtocolStack .on(clientFactory.create(selector.hub(), serverToClient.source(), clientToServer.sink())) .filter(new SSLEngineFilterLayer(clientEngine, null)).build(new IOBufferMatcherLayer()); ProtocolStack<IOBufferMatcher> serverStack = ProtocolStack .on(serverFactory.create(selector.hub(), clientToServer.source(), serverToClient.sink())) .filter(new SSLEngineFilterLayer(serverEngine, null)).build(new IOBufferMatcherLayer()); final IOBufferMatcher client = clientStack.get(); final IOBufferMatcher server = serverStack.get(); Future<Void> clientWork = selector.executorService().submit(new SequentialSender(client, clientLimit, 11)); Future<Void> serverWork = selector.executorService().submit(new SequentialSender(server, serverLimit, 13)); clientWork.get(); serverWork.get(); client.awaitByteContent(SequentialSender.matcher(serverLimit)); server.awaitByteContent(SequentialSender.matcher(clientLimit)); client.close(); server.close(); client.awaitClose(); server.awaitClose(); assertThat(client.asByteArray(), SequentialSender.matcher(serverLimit)); assertThat(server.asByteArray(), SequentialSender.matcher(clientLimit)); } @Theory public void sendingBiggerAndBiggerBatches(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory, BatchSendBufferingFilterLayer batch) throws java.io.IOException, InterruptedException, java.util.concurrent.ExecutionException { Logger.getLogger(name.getMethodName()).log(Level.INFO, "Starting test with server {0} client {1} batch {2}", new Object[] { serverFactory.getClass().getSimpleName(), clientFactory.getClass().getSimpleName(), batch }); SSLEngine serverEngine = serverCtx.createSSLEngine(); serverEngine.setUseClientMode(false); serverEngine.setNeedClientAuth(true); SSLEngine clientEngine = clientCtx.createSSLEngine(); clientEngine.setUseClientMode(true); ProtocolStack<IOBufferMatcher> clientStack = ProtocolStack .on(clientFactory.create(selector.hub(), serverToClient.source(), clientToServer.sink())) .filter(new SSLEngineFilterLayer(clientEngine, null)).build(new IOBufferMatcherLayer()); ProtocolStack<IOBufferMatcher> serverStack = ProtocolStack .on(serverFactory.create(selector.hub(), clientToServer.source(), serverToClient.sink())) .filter(new SSLEngineFilterLayer(serverEngine, null)).filter(batch) .build(new IOBufferMatcherLayer()); final IOBufferMatcher client = clientStack.get(); final IOBufferMatcher server = serverStack.get(); int amount = fullTests ? 65536 * 4 : 16384; Future<Void> serverWork = selector.executorService().submit(new SequentialSender(server, amount, 13)); serverWork.get(); batch.flush(); client.awaitByteContent(SequentialSender.matcher(amount)); client.close(); server.close(); client.awaitClose(); server.awaitClose(); assertThat(client.asByteArray(), SequentialSender.matcher(amount)); } @Theory public void bidiSendingBiggerAndBiggerBatches(NetworkLayerFactory serverFactory, NetworkLayerFactory clientFactory, BatchSendBufferingFilterLayer batch) throws java.io.IOException, InterruptedException, java.util.concurrent.ExecutionException { Logger.getLogger(name.getMethodName()).log(Level.INFO, "Starting test with server {0} client {1} batch {2}", new Object[] { serverFactory.getClass().getSimpleName(), clientFactory.getClass().getSimpleName(), batch }); SSLEngine serverEngine = serverCtx.createSSLEngine(); serverEngine.setUseClientMode(false); serverEngine.setNeedClientAuth(true); SSLEngine clientEngine = clientCtx.createSSLEngine(); clientEngine.setUseClientMode(true); BatchSendBufferingFilterLayer clientBatch = batch.clone(); ProtocolStack<IOBufferMatcher> clientStack = ProtocolStack .on(clientFactory.create(selector.hub(), serverToClient.source(), clientToServer.sink())) .filter(new NoOpFilterLayer()).filter(new SSLEngineFilterLayer(clientEngine, null)) .filter(clientBatch).filter(new NoOpFilterLayer()).build(new IOBufferMatcherLayer()); ProtocolStack<IOBufferMatcher> serverStack = ProtocolStack .on(serverFactory.create(selector.hub(), clientToServer.source(), serverToClient.sink())) .filter(new NoOpFilterLayer()).filter(new SSLEngineFilterLayer(serverEngine, null)).filter(batch) .filter(new NoOpFilterLayer()).build(new IOBufferMatcherLayer()); final IOBufferMatcher client = clientStack.get(); final IOBufferMatcher server = serverStack.get(); int clientAmount = fullTests ? 65536 * 4 : 16384; Future<Void> clientWork = selector.executorService().submit(new SequentialSender(client, clientAmount, 11)); int serverAmount = fullTests ? 65536 * 4 : 16384; Future<Void> serverWork = selector.executorService().submit(new SequentialSender(server, serverAmount, 13)); clientWork.get(); serverWork.get(); clientBatch.flush(); batch.flush(); client.awaitByteContent(SequentialSender.matcher(clientAmount)); server.awaitByteContent(SequentialSender.matcher(serverAmount)); } }