INT-4198: TCP: Add Hook to Customize SSLEngine

JIRA: https://jira.spring.io/browse/INT-4198

Enable setting properties like `needClientAuth` on the `SSLEngine` - when not using
NIO, this can be set on the server socket with a socket support implementation.

Add `nio-connection-support` to namespace.

Improved "Advanced Techniques" documentation, using this use case as an example.

Fail fast with NIO when SSL handshaking fails.

Polishing - PR Comments

More Polishing

* Final polishing
- fix several typos in log messages
- clean up `TcpConnectionFactoryFactoryBean` JavaDocs from redundant imports
- remove redundant `InitializationBean` functionality from the `DefaultTcpNetSSLSocketFactorySupport` as well
This commit is contained in:
Gary Russell
2016-12-28 14:58:16 -05:00
committed by Artem Bilan
parent bdab0aa1d3
commit a0f0b6ab64
14 changed files with 297 additions and 108 deletions

View File

@@ -72,6 +72,7 @@
using-nio="true"
ssl-context-support="sslContextSupport"
ssl-handshake-timeout="43"
nio-connection-support="nioConnectionSupport"
/>
<bean id="sslContextSupport" class="org.springframework.integration.ip.tcp.connection.DefaultTcpSSLContextSupport">
@@ -81,6 +82,9 @@
<constructor-arg value="secret"/>
</bean>
<bean id="nioConnectionSupport"
class="org.springframework.integration.ip.tcp.connection.DefaultTcpNioSSLConnectionSupport" />
<ip:tcp-connection-factory id="secureServer"
type="server"
port="#{tcpIpUtils.findAvailableServerSocket(5250)}"

View File

@@ -36,7 +36,6 @@ import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.UrlResource;
import org.springframework.core.serializer.Deserializer;
import org.springframework.core.serializer.Serializer;
@@ -324,29 +323,26 @@ public class ParserUnitTests {
assertEquals(124, tcpIn.getPhase());
TcpMessageMapper cfS1Mapper = TestUtils.getPropertyValue(cfS1, "mapper", TcpMessageMapper.class);
assertSame(mapper, cfS1Mapper);
assertTrue((Boolean) TestUtils.getPropertyValue(cfS1Mapper, "applySequence"));
assertTrue(TestUtils.getPropertyValue(cfS1Mapper, "applySequence", Boolean.class));
Object socketSupport = TestUtils.getPropertyValue(cfS1, "tcpSocketFactorySupport");
assertTrue(socketSupport instanceof DefaultTcpNetSSLSocketFactorySupport);
assertNotNull(TestUtils.getPropertyValue(socketSupport, "sslContext"));
TcpSSLContextSupport contextSupport = TestUtils.getPropertyValue(cfS1, "tcpSocketFactorySupport.sslContextSupport", TcpSSLContextSupport.class);
assertSame(contextSupport, this.contextSupport);
assertTrue(TestUtils.getPropertyValue(contextSupport, "keyStore") instanceof ClassPathResource);
assertTrue(TestUtils.getPropertyValue(contextSupport, "trustStore") instanceof ClassPathResource);
contextSupport = new DefaultTcpSSLContextSupport("http:foo", "file:bar", "", "");
assertTrue(TestUtils.getPropertyValue(contextSupport, "keyStore") instanceof UrlResource);
assertTrue(TestUtils.getPropertyValue(contextSupport, "trustStore") instanceof UrlResource);
TcpSSLContextSupport tcpSSLContextSupport = new DefaultTcpSSLContextSupport("http:foo", "file:bar", "", "");
assertTrue(TestUtils.getPropertyValue(tcpSSLContextSupport, "keyStore") instanceof UrlResource);
assertTrue(TestUtils.getPropertyValue(tcpSSLContextSupport, "trustStore") instanceof UrlResource);
}
@Test
public void testInTcpNioSSLDefaultConfig() {
assertFalse(cfS1Nio.isLookupHost());
assertTrue((Boolean) TestUtils.getPropertyValue(cfS1Nio, "mapper.applySequence"));
assertTrue(TestUtils.getPropertyValue(cfS1Nio, "mapper.applySequence", Boolean.class));
Object connectionSupport = TestUtils.getPropertyValue(cfS1Nio, "tcpNioConnectionSupport");
assertTrue(connectionSupport instanceof DefaultTcpNioSSLConnectionSupport);
assertNotNull(TestUtils.getPropertyValue(connectionSupport, "sslContext"));
assertEquals(43, TestUtils.getPropertyValue(this.cfS1Nio, "sslHandshakeTimeout"));
assertSame(this.ctx.getBean(DefaultTcpNioSSLConnectionSupport.class),
TestUtils.getPropertyValue(this.cfS1Nio, "tcpNioConnectionSupport"));
}
@Test

View File

@@ -16,17 +16,23 @@
package org.springframework.integration.ip.tcp.connection;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.nio.channels.ClosedChannelException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -39,6 +45,8 @@ import java.util.concurrent.atomic.AtomicReference;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLServerSocket;
import org.junit.Test;
import org.mockito.Mockito;
@@ -274,7 +282,6 @@ Certificate fingerprints:
"test.truststore.ks", "secret", "secret");
DefaultTcpNetSSLSocketFactorySupport tcpSocketFactorySupport =
new DefaultTcpNetSSLSocketFactorySupport(sslContextSupport);
tcpSocketFactorySupport.afterPropertiesSet();
server.setTcpSocketFactorySupport(tcpSocketFactorySupport);
final List<Message<?>> messages = new ArrayList<Message<?>>();
final CountDownLatch latch = new CountDownLatch(1);
@@ -303,13 +310,23 @@ Certificate fingerprints:
@Test
public void testNetClientAndServerSSLDifferentContexts() throws Exception {
testNetClientAndServerSSLDifferentContexts(false);
try {
testNetClientAndServerSSLDifferentContexts(true);
fail("expected Exception");
}
catch (SSLException | SocketException e) {
// NOSONAR
}
}
private void testNetClientAndServerSSLDifferentContexts(boolean badClient) throws Exception {
System.setProperty("javax.net.debug", "all"); // SSL activity in the console
TcpNetServerConnectionFactory server = new TcpNetServerConnectionFactory(0);
TcpSSLContextSupport serverSslContextSupport = new DefaultTcpSSLContextSupport("server.ks",
"server.truststore.ks", "secret", "secret");
DefaultTcpNetSSLSocketFactorySupport serverTcpSocketFactorySupport =
new DefaultTcpNetSSLSocketFactorySupport(serverSslContextSupport);
serverTcpSocketFactorySupport.afterPropertiesSet();
server.setTcpSocketFactorySupport(serverTcpSocketFactorySupport);
final List<Message<?>> messages = new ArrayList<Message<?>>();
final CountDownLatch latch = new CountDownLatch(1);
@@ -318,15 +335,23 @@ Certificate fingerprints:
latch.countDown();
return false;
});
server.setTcpSocketSupport(new DefaultTcpSocketSupport() {
@Override
public void postProcessServerSocket(ServerSocket serverSocket) {
((SSLServerSocket) serverSocket).setNeedClientAuth(true);
}
});
server.start();
TestingUtilities.waitListening(server, null);
TcpNetClientConnectionFactory client = new TcpNetClientConnectionFactory("localhost", server.getPort());
TcpSSLContextSupport clientSslContextSupport = new DefaultTcpSSLContextSupport("client.ks",
TcpSSLContextSupport clientSslContextSupport = new DefaultTcpSSLContextSupport(
badClient ? "server.ks" : "client.ks",
"client.truststore.ks", "secret", "secret");
DefaultTcpNetSSLSocketFactorySupport clientTcpSocketFactorySupport =
new DefaultTcpNetSSLSocketFactorySupport(clientSslContextSupport);
clientTcpSocketFactorySupport.afterPropertiesSet();
client.setTcpSocketFactorySupport(clientTcpSocketFactorySupport);
client.start();
@@ -349,7 +374,6 @@ Certificate fingerprints:
sslContextSupport.setProtocol("SSL");
DefaultTcpNioSSLConnectionSupport tcpNioConnectionSupport =
new DefaultTcpNioSSLConnectionSupport(sslContextSupport);
tcpNioConnectionSupport.afterPropertiesSet();
server.setTcpNioConnectionSupport(tcpNioConnectionSupport);
final List<Message<?>> messages = new ArrayList<Message<?>>();
final CountDownLatch latch = new CountDownLatch(1);
@@ -391,6 +415,63 @@ Certificate fingerprints:
server.stop();
}
@Test
public void testNioClientAndServerSSLDifferentContexts() throws Exception {
testNioClientAndServerSSLDifferentContexts(false);
try {
testNioClientAndServerSSLDifferentContexts(true);
fail("expected Exception");
}
catch (IOException e) {
if (!(e instanceof ClosedChannelException)) {
assertThat(e.getMessage(), containsString("Socket closed during SSL Handshake"));
}
}
}
private void testNioClientAndServerSSLDifferentContexts(boolean badClient) throws Exception {
System.setProperty("javax.net.debug", "all"); // SSL activity in the console
TcpNioServerConnectionFactory server = new TcpNioServerConnectionFactory(0);
TcpSSLContextSupport serverSslContextSupport = new DefaultTcpSSLContextSupport("server.ks",
"server.truststore.ks", "secret", "secret");
DefaultTcpNioSSLConnectionSupport tcpNioConnectionSupport =
new DefaultTcpNioSSLConnectionSupport(serverSslContextSupport) {
@Override
protected void postProcessSSLEngine(SSLEngine sslEngine) {
sslEngine.setNeedClientAuth(true);
}
};
server.setTcpNioConnectionSupport(tcpNioConnectionSupport);
final List<Message<?>> messages = new ArrayList<Message<?>>();
final CountDownLatch latch = new CountDownLatch(1);
server.registerListener(message -> {
messages.add(message);
latch.countDown();
return false;
});
server.start();
TestingUtilities.waitListening(server, null);
TcpNioClientConnectionFactory client = new TcpNioClientConnectionFactory("localhost", server.getPort());
TcpSSLContextSupport clientSslContextSupport = new DefaultTcpSSLContextSupport(
badClient ? "server.ks" : "client.ks",
"client.truststore.ks", "secret", "secret");
DefaultTcpNioSSLConnectionSupport clientTcpNioConnectionSupport =
new DefaultTcpNioSSLConnectionSupport(clientSslContextSupport);
client.setTcpNioConnectionSupport(clientTcpNioConnectionSupport);
client.start();
TcpConnection connection = client.getConnection();
connection.send(new GenericMessage<String>("Hello, world!"));
assertTrue(latch.await(10, TimeUnit.SECONDS));
assertEquals("Hello, world!", new String((byte[]) messages.get(0).getPayload()));
client.stop();
server.stop();
}
@Test
public void testNioClientAndServerSSLDifferentContextsLargeDataWithReply() throws Exception {
System.setProperty("javax.net.debug", "all"); // SSL activity in the console
@@ -399,7 +480,6 @@ Certificate fingerprints:
"server.truststore.ks", "secret", "secret");
DefaultTcpNioSSLConnectionSupport serverTcpNioConnectionSupport =
new DefaultTcpNioSSLConnectionSupport(serverSslContextSupport);
serverTcpNioConnectionSupport.afterPropertiesSet();
server.setTcpNioConnectionSupport(serverTcpNioConnectionSupport);
final List<Message<?>> messages = new ArrayList<Message<?>>();
final CountDownLatch latch = new CountDownLatch(2);
@@ -411,7 +491,7 @@ Certificate fingerprints:
replier.send(message);
}
catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
latch.countDown();
return false;
@@ -433,7 +513,6 @@ Certificate fingerprints:
"client.truststore.ks", "secret", "secret");
DefaultTcpNioSSLConnectionSupport clientTcpNioConnectionSupport =
new DefaultTcpNioSSLConnectionSupport(clientSslContextSupport);
clientTcpNioConnectionSupport.afterPropertiesSet();
client.setTcpNioConnectionSupport(clientTcpNioConnectionSupport);
client.registerListener(message -> {
messages.add(message);

View File

@@ -382,7 +382,7 @@ public class TcpNioConnectionReadTests {
assertTrue(errorMessageLetch.await(10, TimeUnit.SECONDS));
assertThat(errorMessageRef.get().getMessage(),
containsString("Connection is closed"));
anyOf(containsString("Connection is closed"), containsString("Stream closed after 2 of 3")));
assertTrue(semaphore.tryAcquire(10000, TimeUnit.MILLISECONDS));
assertTrue(removed.size() > 0);
@@ -473,7 +473,6 @@ public class TcpNioConnectionReadTests {
private void testClosureMidMessageGuts(AbstractByteArraySerializer serializer, String shortMessage)
throws Exception {
final List<Message<?>> responses = new ArrayList<Message<?>>();
final Semaphore semaphore = new Semaphore(0);
final List<TcpConnection> added = new ArrayList<TcpConnection>();
final List<TcpConnection> removed = new ArrayList<TcpConnection>();
@@ -500,6 +499,7 @@ public class TcpNioConnectionReadTests {
removed.add(connection);
semaphore.release();
}
});
Socket socket = SocketFactory.getDefault().createSocket("localhost", scf.getPort());
socket.getOutputStream().write(shortMessage.getBytes());