diff --git a/quickfixj-core/pom.xml b/quickfixj-core/pom.xml index 3bbbef9cdf..f08d354bb9 100644 --- a/quickfixj-core/pom.xml +++ b/quickfixj-core/pom.xml @@ -61,6 +61,12 @@ 4.1.111.Final test + + org.burningwave + tools + 0.27.2 + test + org.apache.mina mina-core diff --git a/quickfixj-core/src/main/doc/usermanual/usage/configuration.html b/quickfixj-core/src/main/doc/usermanual/usage/configuration.html index 23a402c975..df84d5f0ff 100644 --- a/quickfixj-core/src/main/doc/usermanual/usage/configuration.html +++ b/quickfixj-core/src/main/doc/usermanual/usage/configuration.html @@ -691,6 +691,22 @@ QuickFIX Settings + + UseSNI + + Enables the SSL engine to use Server Name Indication (SNI). This option is only applicable for initiators. + If provided, SNIHostName will be used as the server name. Otherwise, SocketConnectHost or SocketConnectHost<n> will be used. + Note: When this option is disabled, the JVM may still implicitly send the SSL server_name extension. + + YN + N + + + SNIHostName + SNI host name to be used as desired Server Name Indication (SNI) parameter. + + + Socks Proxy Options (Initiator only) diff --git a/quickfixj-core/src/main/java/quickfix/mina/initiator/IoSessionInitiator.java b/quickfixj-core/src/main/java/quickfix/mina/initiator/IoSessionInitiator.java index 349971e551..de725bfe96 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/initiator/IoSessionInitiator.java +++ b/quickfixj-core/src/main/java/quickfix/mina/initiator/IoSessionInitiator.java @@ -38,6 +38,7 @@ import quickfix.mina.ProtocolFactory; import quickfix.mina.SessionConnector; import quickfix.mina.message.FIXProtocolCodecFactory; +import quickfix.mina.ssl.InitiatorSslFilter; import quickfix.mina.ssl.SSLConfig; import quickfix.mina.ssl.SSLContextFactory; import quickfix.mina.ssl.SSLSupport; @@ -185,7 +186,7 @@ private void setupIoConnector() throws ConfigError, GeneralSecurityException { private void installSslFilter(CompositeIoFilterChainBuilder ioFilterChainBuilder) throws GeneralSecurityException { final SSLContext sslContext = SSLContextFactory.getInstance(sslConfig); - final SslFilter sslFilter = new SslFilter(sslContext, false); + final SslFilter sslFilter = new InitiatorSslFilter(sslContext, getSniHostName(sslConfig)); sslFilter.setEnabledCipherSuites(sslConfig.getEnabledCipherSuites() != null ? sslConfig.getEnabledCipherSuites() : SSLSupport.getDefaultCipherSuites(sslContext)); sslFilter.setEnabledProtocols(sslConfig.getEnabledProtocols() != null ? sslConfig.getEnabledProtocols() @@ -194,6 +195,22 @@ private void installSslFilter(CompositeIoFilterChainBuilder ioFilterChainBuilder ioFilterChainBuilder.addLast(SSLSupport.FILTER_NAME, sslFilter); } + public String getSniHostName(SSLConfig sslConfig) { + if (!sslConfig.isUseSNI()) { + return null; + } + + if (sslConfig.getSniHostName() != null) { + return sslConfig.getSniHostName(); + } + + if (socketAddresses[nextSocketAddressIndex] instanceof InetSocketAddress) { + return ((InetSocketAddress) socketAddresses[nextSocketAddressIndex]).getHostName(); + } + + return null; + } + @Override public void run() { resetIoConnector(); diff --git a/quickfixj-core/src/main/java/quickfix/mina/ssl/InitiatorSslFilter.java b/quickfixj-core/src/main/java/quickfix/mina/ssl/InitiatorSslFilter.java new file mode 100644 index 0000000000..7c4e5b438d --- /dev/null +++ b/quickfixj-core/src/main/java/quickfix/mina/ssl/InitiatorSslFilter.java @@ -0,0 +1,64 @@ +package quickfix.mina.ssl; + +import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.ssl.SslFilter; + +import javax.net.ssl.SNIHostName; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLParameters; +import java.net.InetSocketAddress; +import java.util.Arrays; + +public final class InitiatorSslFilter extends SslFilter { + + private final String sniHostName; + + public InitiatorSslFilter(SSLContext sslContext, String sniHostName) { + super(sslContext, false); + this.sniHostName = sniHostName; + } + + @Override + protected SSLEngine createEngine(IoSession session, InetSocketAddress addr) { + SSLEngine sslEngine; + + if (addr != null) { + sslEngine = sslContext.createSSLEngine(addr.getHostName(), addr.getPort()); + } else { + sslEngine = sslContext.createSSLEngine(); + } + + if (wantClientAuth) { + sslEngine.setWantClientAuth(true); + } + + if (needClientAuth) { + sslEngine.setNeedClientAuth(true); + } + + if (enabledCipherSuites != null) { + sslEngine.setEnabledCipherSuites(enabledCipherSuites); + } + + if (enabledProtocols != null) { + sslEngine.setEnabledProtocols(enabledProtocols); + } + + if (getEndpointIdentificationAlgorithm() != null) { + SSLParameters sslParameters = sslEngine.getSSLParameters(); + sslParameters.setEndpointIdentificationAlgorithm(getEndpointIdentificationAlgorithm()); + sslEngine.setSSLParameters(sslParameters); + } + + if (sniHostName != null) { + SSLParameters sslParameters = sslEngine.getSSLParameters(); + sslParameters.setServerNames(Arrays.asList(new SNIHostName(sniHostName))); + sslEngine.setSSLParameters(sslParameters); + } + + sslEngine.setUseClientMode(!session.isServer()); + + return sslEngine; + } +} diff --git a/quickfixj-core/src/main/java/quickfix/mina/ssl/SSLConfig.java b/quickfixj-core/src/main/java/quickfix/mina/ssl/SSLConfig.java index 4d45fe9f8c..57db1c01b9 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/ssl/SSLConfig.java +++ b/quickfixj-core/src/main/java/quickfix/mina/ssl/SSLConfig.java @@ -38,6 +38,8 @@ public class SSLConfig { private String[] enabledCipherSuites; private boolean needClientAuth; private String endpointIdentificationAlgorithm; + private boolean useSNI; + private String sniHostName; public String[] getEnabledCipherSuites() { return enabledCipherSuites; @@ -87,7 +89,15 @@ public String getEndpointIdentificationAlgorithm() { return endpointIdentificationAlgorithm; } - public void setEnabledCipherSuites(String[] enabledCipherSuites) { + public boolean isUseSNI() { + return useSNI; + } + + public String getSniHostName() { + return sniHostName; + } + + public void setEnabledCipherSuites(String[] enabledCipherSuites) { this.enabledCipherSuites = enabledCipherSuites; } @@ -119,7 +129,15 @@ public void setEndpointIdentificationAlgorithm(String endpointIdentificationAlgo this.endpointIdentificationAlgorithm = endpointIdentificationAlgorithm; } - public void setTrustManagerFactoryAlgorithm(String trustManagerFactoryAlgorithm) { + public void setUseSNI(boolean useSNI) { + this.useSNI = useSNI; + } + + public void setSniHostName(String sniHostName) { + this.sniHostName = sniHostName; + } + + public void setTrustManagerFactoryAlgorithm(String trustManagerFactoryAlgorithm) { this.trustManagerFactoryAlgorithm = trustManagerFactoryAlgorithm; } @@ -151,12 +169,14 @@ public boolean equals(Object o) { Objects.equals(trustStoreType, sslConfig.trustStoreType) && Arrays.equals(enabledProtocols, sslConfig.enabledProtocols) && Arrays.equals(enabledCipherSuites, sslConfig.enabledCipherSuites) && - Objects.equals(endpointIdentificationAlgorithm, sslConfig.endpointIdentificationAlgorithm); + Objects.equals(endpointIdentificationAlgorithm, sslConfig.endpointIdentificationAlgorithm) && + Objects.equals(useSNI, sslConfig.useSNI) && + Objects.equals(sniHostName, sslConfig.sniHostName); } @Override public int hashCode() { - int result = Objects.hash(keyStoreName, keyManagerFactoryAlgorithm, keyStoreType, trustStoreName, trustManagerFactoryAlgorithm, trustStoreType, needClientAuth, endpointIdentificationAlgorithm); + int result = Objects.hash(keyStoreName, keyManagerFactoryAlgorithm, keyStoreType, trustStoreName, trustManagerFactoryAlgorithm, trustStoreType, needClientAuth, endpointIdentificationAlgorithm, useSNI, sniHostName); result = 31 * result + Arrays.hashCode(keyStorePassword); result = 31 * result + Arrays.hashCode(trustStorePassword); result = 31 * result + Arrays.hashCode(enabledProtocols); diff --git a/quickfixj-core/src/main/java/quickfix/mina/ssl/SSLSupport.java b/quickfixj-core/src/main/java/quickfix/mina/ssl/SSLSupport.java index 52550ad813..c8a0e6f5dd 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/ssl/SSLSupport.java +++ b/quickfixj-core/src/main/java/quickfix/mina/ssl/SSLSupport.java @@ -40,6 +40,8 @@ public class SSLSupport { public static final String SETTING_TRUST_STORE_TYPE = "TrustStoreType"; public static final String SETTING_NEED_CLIENT_AUTH = "NeedClientAuth"; public static final String SETTING_ENDPOINT_IDENTIFICATION_ALGORITHM = "EndpointIdentificationAlgorithm"; + public static final String SETTING_USE_SNI = "UseSNI"; + public static final String SETTING_SNI_HOST_NAME = "SNIHostName"; public static final String SETTING_ENABLED_PROTOCOLS = "EnabledProtocols"; public static final String SETTING_CIPHER_SUITES = "CipherSuites"; static final String DEFAULT_STORE_TYPE = "JKS"; @@ -112,6 +114,8 @@ public static SSLConfig getSslConfig(SessionSettings sessionSettings, SessionID sslConfig.setEnabledProtocols(getEnabledProtocols(sessionSettings, sessionID)); sslConfig.setNeedClientAuth(isNeedClientAuth(sessionSettings, sessionID)); sslConfig.setEndpointIdentificationAlgorithm(getEndpointIdentificationAlgorithm(sessionSettings, sessionID)); + sslConfig.setUseSNI(isUseSNI(sessionSettings, sessionID)); + sslConfig.setSniHostName(getSNIHostName(sessionSettings, sessionID)); return sslConfig; } @@ -153,4 +157,12 @@ public static boolean isNeedClientAuth(SessionSettings sessionSettings, SessionI public static String getEndpointIdentificationAlgorithm(SessionSettings sessionSettings, SessionID sessionID) { return getString(sessionSettings, sessionID, SETTING_ENDPOINT_IDENTIFICATION_ALGORITHM, null); } + + public static boolean isUseSNI(SessionSettings sessionSettings, SessionID sessionID) { + return "Y".equals(getString(sessionSettings, sessionID, SETTING_USE_SNI, "N")); + } + + public static String getSNIHostName(SessionSettings sessionSettings, SessionID sessionID) { + return getString(sessionSettings, sessionID, SETTING_SNI_HOST_NAME, null); + } } diff --git a/quickfixj-core/src/test/java/quickfix/mina/ssl/SSLCertificateTest.java b/quickfixj-core/src/test/java/quickfix/mina/ssl/SSLCertificateTest.java index 6acad9cf5e..16b4387457 100644 --- a/quickfixj-core/src/test/java/quickfix/mina/ssl/SSLCertificateTest.java +++ b/quickfixj-core/src/test/java/quickfix/mina/ssl/SSLCertificateTest.java @@ -1,1061 +1,1519 @@ -/******************************************************************************* - * Copyright (c) quickfixengine.org All rights reserved. - * - * This file is part of the QuickFIX FIX Engine - * - * This file may be distributed under the terms of the quickfixengine.org - * license as defined by quickfixengine.org and appearing in the file - * LICENSE included in the packaging of this file. - * - * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING - * THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE. - * - * See http://www.quickfixengine.org/LICENSE for licensing information. - * - * Contact ask@quickfixengine.org if any conditions of this licensing - * are not clear to you. - ******************************************************************************/ - -package quickfix.mina.ssl; - -import org.apache.mina.core.filterchain.IoFilterAdapter; -import org.apache.mina.core.filterchain.IoFilterChain; -import org.apache.mina.core.session.IoSession; -import org.apache.mina.filter.ssl.SslFilter; -import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import quickfix.Acceptor; -import quickfix.ApplicationAdapter; -import quickfix.ConfigError; -import quickfix.DefaultMessageFactory; -import quickfix.FixVersions; -import quickfix.Initiator; -import quickfix.MemoryStoreFactory; -import quickfix.MessageFactory; -import quickfix.MessageStoreFactory; -import quickfix.RuntimeError; -import quickfix.Session; -import quickfix.SessionFactory; -import quickfix.SessionID; -import quickfix.SessionSettings; -import quickfix.ThreadedSocketAcceptor; -import quickfix.ThreadedSocketInitiator; -import quickfix.mina.IoSessionResponder; -import quickfix.mina.ProtocolFactory; -import quickfix.mina.SessionConnector; - -import javax.net.ssl.SSLHandshakeException; -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLSession; -import java.lang.reflect.Field; -import java.math.BigInteger; -import java.security.cert.Certificate; -import java.security.cert.X509Certificate; -import java.util.HashMap; -import java.util.Properties; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; -import java.util.logging.Level; -import org.apache.mina.util.AvailablePortFinder; -import org.junit.After; -import quickfix.mina.SocksProxyServer; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; - -public class SSLCertificateTest { - - // Note: To diagnose cipher suite errors, run with -Djavax.net.debug=ssl:handshake - private static final String CIPHER_SUITES_TLS = "TLS_RSA_WITH_AES_128_CBC_SHA"; - - @After - public void cleanup() { - try { - Thread.sleep(500); - } catch (InterruptedException ex) { - java.util.logging.Logger.getLogger(SSLCertificateTest.class.getName()).log(Level.SEVERE, null, ex); - } - } - - @Test - public void shouldAuthenticateServerCertificate() throws Exception { - int freePort = AvailablePortFinder.getNextAvailable(); - TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/server.keystore", false, - "single-session/empty.keystore", CIPHER_SUITES_TLS, "TLSv1.2", "JKS", "JKS", freePort)); - - try { - acceptor.start(); - - TestInitiator initiator = new TestInitiator( - createInitiatorSettings("single-session/empty.keystore", "single-session/client.truststore", - CIPHER_SUITES_TLS, "TLSv1.2", "ZULU", "ALFA", Integer.toString(freePort), "JKS", "JKS")); - - try { - initiator.start(); - - initiator.assertNoSslExceptionThrown(); - initiator.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); - initiator.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA"), - new BigInteger("1448538842")); - - acceptor.assertNoSslExceptionThrown(); - acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); - acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); - } finally { - initiator.stop(); - } - } finally { - acceptor.stop(); - } - } - - @Test - public void shouldLoginViaSocks4Proxy() throws Exception { - shouldAuthenticateServerCertificateViaSocksProxy("4"); - } - - @Test - public void shouldLoginViaSocks4aProxy() throws Exception { - shouldAuthenticateServerCertificateViaSocksProxy("4a"); - } - - @Test - public void shouldLoginViaSocks5Proxy() throws Exception { - shouldAuthenticateServerCertificateViaSocksProxy("5"); - } - - public void shouldAuthenticateServerCertificateViaSocksProxy(String proxyVersion) throws Exception { - int proxyPort = AvailablePortFinder.getNextAvailable(); - - SocksProxyServer proxyServer = new SocksProxyServer(proxyPort); - proxyServer.start(); - - try { - int port = AvailablePortFinder.getNextAvailable(); - TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/server.keystore", false, - "single-session/empty.keystore", CIPHER_SUITES_TLS, "TLSv1.2", "JKS", "JKS", port)); - - try { - acceptor.start(); - - SessionSettings initiatorSettings = createInitiatorSettings("single-session/empty.keystore", "single-session/client.truststore", - CIPHER_SUITES_TLS, "TLSv1.2", "ZULU", "ALFA", Integer.toString(port), "JKS", "JKS"); - - Properties defaults = initiatorSettings.getDefaultProperties(); - - defaults.put(Initiator.SETTING_PROXY_HOST, "localhost"); - defaults.put(Initiator.SETTING_PROXY_PORT, Integer.toString(proxyPort)); - defaults.put(Initiator.SETTING_PROXY_TYPE, "socks"); - defaults.put(Initiator.SETTING_PROXY_VERSION, proxyVersion); - defaults.put(Initiator.SETTING_PROXY_USER, "proxy-user"); - defaults.put(Initiator.SETTING_PROXY_PASSWORD, "proxy-password"); - - TestInitiator initiator = new TestInitiator(initiatorSettings); - - try { - initiator.start(); - - initiator.assertNoSslExceptionThrown(); - initiator.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); - initiator.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA"), new BigInteger("1448538842")); - - acceptor.assertNoSslExceptionThrown(); - acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); - acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); - } finally { - initiator.stop(); - } - } finally { - acceptor.stop(); - } - } finally { - proxyServer.stop(); - } - } - - /** - * Server certificate has Common Name = localhost and no Server Alternative Name extension. - */ - @Test - public void shouldAuthenticateServerNameUsingServerCommonName() throws Exception { - int freePort = AvailablePortFinder.getNextAvailable(); - TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/server-cn.keystore", false, - "single-session/empty.keystore", CIPHER_SUITES_TLS, "TLSv1.2", "JKS", "JKS", freePort)); - - try { - acceptor.start(); - - TestInitiator initiator = new TestInitiator( - createInitiatorSettings("single-session/empty.keystore", "single-session/client-cn.truststore", - CIPHER_SUITES_TLS, "TLSv1.2", "ZULU", "ALFA", Integer.toString(freePort), "JKS", "JKS")); - - try { - initiator.start(); - - initiator.assertNoSslExceptionThrown(); - initiator.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); - initiator.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA"), - new BigInteger("1683903911")); - - acceptor.assertNoSslExceptionThrown(); - acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); - acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); - } finally { - initiator.stop(); - } - } finally { - acceptor.stop(); - } - } - - /** - * Server certificate has Common Name = server, but it has Server Alternative Name extension (DNS name). - */ - @Test - public void shouldAuthenticateServerNameUsingSNIExtension() throws Exception { - int freePort = AvailablePortFinder.getNextAvailable(); - TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/server-sni.keystore", false, - "single-session/empty.keystore", CIPHER_SUITES_TLS, "TLSv1.2", "JKS", "JKS", freePort)); - - try { - acceptor.start(); - - TestInitiator initiator = new TestInitiator( - createInitiatorSettings("single-session/empty.keystore", "single-session/client-sni.truststore", - CIPHER_SUITES_TLS, "TLSv1.2", "ZULU", "ALFA", Integer.toString(freePort), "JKS", "JKS")); - - try { - initiator.start(); - - initiator.assertNoSslExceptionThrown(); - initiator.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); - initiator.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA"), - new BigInteger("1683904647")); - - acceptor.assertNoSslExceptionThrown(); - acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); - acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); - } finally { - initiator.stop(); - } - } finally { - acceptor.stop(); - } - } - - /** - * Server certificate has Common Name = server and no Server Alternative Name extension. - */ - @Test - public void shouldFailWhenHostnameDoesNotMatchServerName() throws Exception { - int freePort = AvailablePortFinder.getNextAvailable(); - - TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/server-bad-cn.keystore", false, - "single-session/empty.keystore", CIPHER_SUITES_TLS, "TLSv1.2", "JKS", "JKS", freePort)); - - try { - acceptor.start(); - - TestInitiator initiator = new TestInitiator( - createInitiatorSettings("single-session/empty.keystore", "single-session/client-bad-cn.truststore", - CIPHER_SUITES_TLS, "TLSv1.2", "ZULU", "ALFA", Integer.toString(freePort), "JKS", "JKS", "HTTPS")); - - try { - initiator.start(); - - initiator.assertSslExceptionThrown("No name matching localhost found", SSLHandshakeException.class); - initiator.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); - initiator.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); - - acceptor.assertSslExceptionThrown("Received fatal alert: certificate_unknown", SSLHandshakeException.class); - acceptor.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); - acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); - } finally { - initiator.stop(); - } - } finally { - acceptor.stop(); - } - } - - @Test - public void shouldAuthenticateServerAndClientCertificates() throws Exception { - int freePort = AvailablePortFinder.getNextAvailable(); - TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/server.keystore", true, - "single-session/server.truststore", CIPHER_SUITES_TLS, "TLSv1.2", "JKS", "JKS", freePort)); - - try { - acceptor.start(); - - TestInitiator initiator = new TestInitiator( - createInitiatorSettings("single-session/client.keystore", "single-session/client.truststore", - CIPHER_SUITES_TLS, "TLSv1.2", "ZULU", "ALFA", Integer.toString(freePort), "JKS", "JKS")); - - try { - initiator.start(); - - initiator.assertNoSslExceptionThrown(); - initiator.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); - initiator.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA"), - new BigInteger("1448538842")); - - acceptor.assertNoSslExceptionThrown(); - acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); - acceptor.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU"), - new BigInteger("1448538787")); - } finally { - initiator.stop(); - } - } finally { - acceptor.stop(); - } - } - - @Test - public void shouldAuthenticateServerAndClientCertificatesWhenUsingDifferentKeystoreFormats() throws Exception { - int freePort = AvailablePortFinder.getNextAvailable(); - TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/server-pkcs12.keystore", true, - "single-session/server-jceks.truststore", CIPHER_SUITES_TLS, "TLSv1.2", "PKCS12", - "JCEKS", freePort)); - - try { - acceptor.start(); - - TestInitiator initiator = new TestInitiator(createInitiatorSettings("single-session/client-jceks.keystore", - "single-session/client-jceks.keystore", CIPHER_SUITES_TLS, "TLSv1.2", "ZULU", "ALFA", - Integer.toString(freePort), "JCEKS", "JCEKS")); - - try { - initiator.start(); - - initiator.assertNoSslExceptionThrown(); - initiator.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); - initiator.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA"), - new BigInteger("1449683167")); - - acceptor.assertNoSslExceptionThrown(); - acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); - acceptor.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU"), - new BigInteger("1449683336")); - } finally { - initiator.stop(); - } - } finally { - acceptor.stop(); - } - } - - @Test - public void shouldAuthenticateServerAndClientCertificatesForIndividualSessions() throws Exception { - TestAcceptor acceptor = new TestAcceptor(createMultiSessionAcceptorSettings( - "multi-session/server.keystore", true, new String[] { "multi-session/server1.truststore", - "multi-session/server2.truststore", "multi-session/server3.truststore" }, - CIPHER_SUITES_TLS, "TLSv1.2")); - - try { - acceptor.start(); - - TestInitiator initiator1 = new TestInitiator( - createInitiatorSettings("multi-session/client1.keystore", "multi-session/client1.keystore", - CIPHER_SUITES_TLS, "TLSv1.2", "ZULU0", "ALFA0", "12340", "JKS", "JKS")); - TestInitiator initiator2 = new TestInitiator( - createInitiatorSettings("multi-session/client2.keystore", "multi-session/client2.keystore", - CIPHER_SUITES_TLS, "TLSv1.2", "ZULU1", "ALFA1", "12341", "JKS", "JKS")); - TestInitiator initiator3 = new TestInitiator( - createInitiatorSettings("multi-session/client3.keystore", "multi-session/client3.keystore", - CIPHER_SUITES_TLS, "TLSv1.2", "ZULU2", "ALFA2", "12342", "JKS", "JKS")); - - try { - initiator1.start(); - initiator2.start(); - initiator3.start(); - - initiator1.assertNoSslExceptionThrown(); - initiator1.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU0", "ALFA0")); - initiator1.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU0", "ALFA0"), - new BigInteger("1449581686")); - - initiator2.assertNoSslExceptionThrown(); - initiator2.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU1", "ALFA1")); - initiator2.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU1", "ALFA1"), - new BigInteger("1449581686")); - - initiator3.assertNoSslExceptionThrown(); - initiator3.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU2", "ALFA2")); - initiator3.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU2", "ALFA2"), - new BigInteger("1449581686")); - - acceptor.assertNoSslExceptionThrown(); - acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA0", "ZULU0")); - acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA1", "ZULU1")); - acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA2", "ZULU2")); - acceptor.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA0", "ZULU0"), - new BigInteger("1449581008")); - acceptor.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA1", "ZULU1"), - new BigInteger("1449581372")); - acceptor.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA2", "ZULU2"), - new BigInteger("1449581412")); - - } finally { - initiator1.stop(); - initiator2.stop(); - initiator3.stop(); - } - } finally { - acceptor.stop(); - } - } - - @Test - public void shouldFailIndividualSessionsWhenInvalidCertificatesUsed() throws Exception { - TestAcceptor acceptor = new TestAcceptor(createMultiSessionAcceptorSettings( - "multi-session/server.keystore", true, new String[] { "multi-session/server1.truststore", - "multi-session/server2.truststore", "multi-session/server3.truststore" }, - CIPHER_SUITES_TLS, "TLSv1.2")); - - try { - acceptor.start(); - - TestInitiator initiator1 = new TestInitiator( - createInitiatorSettings("multi-session/client2.keystore", "multi-session/client2.keystore", - CIPHER_SUITES_TLS, "TLSv1.2", "ZULU0", "ALFA0", "12340", "JKS", "JKS")); - TestInitiator initiator2 = new TestInitiator( - createInitiatorSettings("multi-session/client1.keystore", "multi-session/client1.keystore", - CIPHER_SUITES_TLS, "TLSv1.2", "ZULU1", "ALFA1", "12341", "JKS", "JKS")); - TestInitiator initiator3 = new TestInitiator( - createInitiatorSettings("multi-session/client3.keystore", "multi-session/client3.keystore", - CIPHER_SUITES_TLS, "TLSv1.2", "ZULU2", "ALFA2", "12342", "JKS", "JKS")); - - try { - initiator1.start(); - initiator2.start(); - initiator3.start(); - - initiator1.assertSslExceptionThrown(); - initiator1.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU0", "ALFA0")); - initiator1.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU0", "ALFA0")); - - initiator2.assertSslExceptionThrown(); - initiator2.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU1", "ALFA1")); - initiator2.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU1", "ALFA1")); - - initiator3.assertNoSslExceptionThrown(); - initiator3.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU2", "ALFA2")); - initiator3.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU2", "ALFA2"), - new BigInteger("1449581686")); - - acceptor.assertSslExceptionThrown(); - acceptor.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA0", "ZULU0")); - acceptor.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA1", "ZULU1")); - acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA2", "ZULU2")); - acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA0", "ZULU0")); - acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA1", "ZULU1")); - acceptor.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA2", "ZULU2"), - new BigInteger("1449581412")); - } finally { - initiator1.stop(); - initiator2.stop(); - initiator3.stop(); - } - } finally { - acceptor.stop(); - } - } - - @Test - public void shouldFailWhenUsingEmptyServerKeyStore() throws Exception { - int freePort = AvailablePortFinder.getNextAvailable(); - TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/empty.keystore", false, - "single-session/empty.keystore", null, null, "JKS", "JKS", freePort)); - - try { - acceptor.start(); - - TestInitiator initiator = new TestInitiator(createInitiatorSettings("single-session/empty.keystore", - "single-session/empty.keystore", null, null, "ZULU", "ALFA", Integer.toString(freePort), "JKS", "JKS")); - - try { - initiator.start(); - - initiator.assertSslExceptionThrown(); - initiator.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); - initiator.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); - - acceptor.assertSslExceptionThrown(); - acceptor.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); - acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); - } finally { - initiator.stop(); - } - } finally { - acceptor.stop(); - } - } - - @Test - public void shouldFailWhenUsingEmptyClientTruststore() throws Exception { - int freePort = AvailablePortFinder.getNextAvailable(); - TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/server.keystore", false, - "single-session/empty.keystore", CIPHER_SUITES_TLS, "TLSv1.2", "JKS", "JKS", freePort)); - - try { - acceptor.start(); - - TestInitiator initiator = new TestInitiator( - createInitiatorSettings("single-session/empty.keystore", "single-session/empty.keystore", - CIPHER_SUITES_TLS, "TLSv1.2", "ZULU", "ALFA", Integer.toString(freePort), "JKS", "JKS")); - - try { - initiator.start(); - - initiator.assertSslExceptionThrown(); - initiator.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); - initiator.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); - - // client disconnects before acceptor throws an exception - acceptor.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); - acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); - } finally { - initiator.stop(); - } - } finally { - acceptor.stop(); - } - } - - @Test - public void shouldFailWhenUsingEmptyServerTrustore() throws Exception { - int freePort = AvailablePortFinder.getNextAvailable(); - TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/server.keystore", true, - "single-session/empty.keystore", CIPHER_SUITES_TLS, "TLSv1.2", "JKS", "JKS", freePort)); - - try { - acceptor.start(); - - TestInitiator initiator = new TestInitiator( - createInitiatorSettings("single-session/client.keystore", "single-session/client.truststore", - CIPHER_SUITES_TLS, "TLSv1.2", "ZULU", "ALFA", Integer.toString(freePort), "JKS", "JKS")); - - try { - initiator.start(); - - initiator.assertSslExceptionThrown(); - initiator.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); - initiator.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); - - acceptor.assertSslExceptionThrown(); - acceptor.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); - acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); - } finally { - initiator.stop(); - } - } finally { - acceptor.stop(); - } - } - - @Test - public void shouldFailWhenUsingBadClientCertificate() throws Exception { - int freePort = AvailablePortFinder.getNextAvailable(); - TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/server.keystore", true, - "single-session/server.truststore", CIPHER_SUITES_TLS, "TLSv1.2", "JKS", "JKS", freePort)); - - try { - acceptor.start(); - - TestInitiator initiator = new TestInitiator( - createInitiatorSettings("single-session/server.keystore", "single-session/client.truststore", - CIPHER_SUITES_TLS, "TLSv1.2", "ZULU", "ALFA", Integer.toString(freePort), "JKS", "JKS")); - - try { - initiator.start(); - - initiator.assertSslExceptionThrown(); - initiator.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); - initiator.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); - - acceptor.assertSslExceptionThrown(); - acceptor.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); - acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); - } finally { - initiator.stop(); - } - } finally { - acceptor.stop(); - } - } - - @Test - public void shouldFailWhenUsingBadServerCertificate() throws Exception { - int freePort = AvailablePortFinder.getNextAvailable(); - TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/client.keystore", false, - "single-session/empty.keystore", CIPHER_SUITES_TLS, "TLSv1.2", "JKS", "JKS", freePort)); - - try { - acceptor.start(); - - TestInitiator initiator = new TestInitiator( - createInitiatorSettings("single-session/empty.keystore", "single-session/client.truststore", - CIPHER_SUITES_TLS, "TLSv1.2", "ZULU", "ALFA", Integer.toString(freePort), "JKS", "JKS")); - - try { - initiator.start(); - - initiator.assertSslExceptionThrown(); - initiator.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); - initiator.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); - - acceptor.assertSslExceptionThrown(); - acceptor.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); - acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); - } finally { - initiator.stop(); - } - } finally { - acceptor.stop(); - } - } - - @Test - public void shouldConnectDifferentTypesOfSessions() throws Exception { - int sslPort = AvailablePortFinder.getNextAvailable(); - int nonSslPort = AvailablePortFinder.getNextAvailable(); - TestAcceptor acceptor = new TestAcceptor(createMixedSessionAcceptorSettings(sslPort, nonSslPort, "single-session/server.keystore")); - - try { - acceptor.start(); - - TestInitiator sslInitiator = new TestInitiator( - createInitiatorSettings("single-session/client.keystore", "single-session/client.truststore", - CIPHER_SUITES_TLS, "TLSv1.2", "ZULU_SSL", "ALFA_SSL", Integer.toString(sslPort), "JKS", "JKS")); - - TestInitiator nonSslInitiator = new TestInitiator(createInitiatorSettings("ZULU_NON_SSL", "ALFA_NON_SSL", nonSslPort)); - - try { - sslInitiator.start(); - nonSslInitiator.start(); - - sslInitiator.assertNoSslExceptionThrown(); - sslInitiator.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU_SSL", "ALFA_SSL")); - sslInitiator.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU_SSL", "ALFA_SSL"), - new BigInteger("1448538842")); - - acceptor.assertNoSslExceptionThrown(); - acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA_SSL", "ZULU_SSL")); - acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA_SSL", "ZULU_SSL")); - - nonSslInitiator.assertNoSslExceptionThrown(); - nonSslInitiator.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU_NON_SSL", "ALFA_NON_SSL")); - nonSslInitiator.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU_NON_SSL", "ALFA_NON_SSL")); - - acceptor.assertNoSslExceptionThrown(); - acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA_NON_SSL", "ZULU_NON_SSL")); - acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA_NON_SSL", "ZULU_NON_SSL")); - - } finally { - sslInitiator.stop(); - nonSslInitiator.stop(); - } - } finally { - acceptor.stop(); - } - } - - static abstract class TestConnector { - private static final Logger LOGGER = LoggerFactory.getLogger(TestConnector.class); - private static final int TIMEOUT_SECONDS = 5; - - private final SessionConnector connector; - private final CountDownLatch exceptionThrownLatch; - private final AtomicReference exception; - - public TestConnector(SessionSettings sessionSettings) throws ConfigError { - this.connector = prepareConnector(sessionSettings); - this.exceptionThrownLatch = new CountDownLatch(1); - this.exception = new AtomicReference<>(); - } - - private SessionConnector prepareConnector(SessionSettings sessionSettings) throws ConfigError { - SessionConnector sessionConnector = createConnector(sessionSettings); - sessionConnector.setIoFilterChainBuilder(chain -> chain.addFirst("Exception handler", new IoFilterAdapter() { - @Override - public void exceptionCaught(NextFilter nextFilter, IoSession session, Throwable cause) { - LOGGER.info("exceptionCaught", cause); - exception.set(cause); - exceptionThrownLatch.countDown(); - nextFilter.exceptionCaught(session, cause); - } - })); - - return sessionConnector; - } - - public abstract SessionConnector createConnector(SessionSettings sessionSettings) throws ConfigError; - - private SSLSession findSSLSession(Session session) throws Exception { - IoSession ioSession = findIoSession(session); - - if (ioSession == null) - return null; - - IoFilterChain filterChain = ioSession.getFilterChain(); - SslFilter sslFilter = (SslFilter) filterChain.get(SSLSupport.FILTER_NAME); - - if (sslFilter == null) - return null; - - return (SSLSession) ioSession.getAttribute(SslFilter.SSL_SECURED); - } - - private Session findSession(SessionID sessionID) { - for (Session session : connector.getManagedSessions()) { - if (session.getSessionID().equals(sessionID)) - return session; - } - - return null; - } - - private IoSession findIoSession(Session session) throws Exception { - IoSessionResponder ioSessionResponder = (IoSessionResponder) session.getResponder(); - - if (ioSessionResponder == null) - return null; - - Field field = IoSessionResponder.class.getDeclaredField("ioSession"); - field.setAccessible(true); - - return (IoSession) field.get(ioSessionResponder); - } - - public void assertAuthenticated(SessionID sessionID, BigInteger serialNumber) throws Exception { - Session session = findSession(sessionID); - SSLSession sslSession = findSSLSession(session); - - Certificate[] peerCertificates = sslSession.getPeerCertificates(); - - for (Certificate peerCertificate : peerCertificates) { - if (!(peerCertificate instanceof X509Certificate)) { - continue; - } - - if (((X509Certificate)peerCertificate).getSerialNumber().compareTo(serialNumber) == 0) { - return; - } - } - - throw new AssertionError("Certificate with serial number " + serialNumber + " was not authenticated"); - } - - public void assertNotAuthenticated(SessionID sessionID) throws Exception { - Session session = findSession(sessionID); - SSLSession sslSession = findSSLSession(session); - - if (sslSession == null) - return; - - try { - Certificate[] peerCertificates = sslSession.getPeerCertificates(); - - if (peerCertificates != null && peerCertificates.length > 0) { - throw new AssertionError("Certificate was authenticated"); - } - } catch (SSLPeerUnverifiedException e) { - } - } - - public void assertLoggedOn(SessionID sessionID) { - Session session = findSession(sessionID); - - if (!session.isLoggedOn()) - throw new AssertionError("Session is not logged on"); - } - - public void assertNotLoggedOn(SessionID sessionID) { - Session session = findSession(sessionID); - - if (session.isLoggedOn()) - throw new AssertionError("Session is logged on"); - } - - public void assertSslExceptionThrown() throws Exception { - assertSslExceptionThrown(null, null); - } - - public void assertSslExceptionThrown(String errorMessage, Class> errorType) throws Exception { - boolean reachedZero = exceptionThrownLatch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS); - - if (!reachedZero) { - throw new AssertionError("No SSL exception thrown"); - } - - Throwable throwable = exception.get(); - - if (errorMessage != null) { - assertEquals(errorMessage, throwable.getMessage()); - } - - if (errorType != null) { - assertSame(errorType, throwable.getClass()); - } - } - - public void assertNoSslExceptionThrown() throws Exception { - boolean reachedZero = exceptionThrownLatch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS); - - if (reachedZero) { - throw new AssertionError("SSL exception thrown"); - } - } - - public void start() throws RuntimeError, ConfigError { - connector.start(); - } - - public void stop() { - connector.stop(); - } - } - - static class TestAcceptor extends TestConnector { - private static final Logger LOGGER = LoggerFactory.getLogger(TestAcceptor.class); - - public TestAcceptor(SessionSettings sessionSettings) throws ConfigError { - super(sessionSettings); - } - - @Override - public SessionConnector createConnector(SessionSettings sessionSettings) throws ConfigError { - LOGGER.info("Creating acceptor: {}", sessionSettings); - - MessageStoreFactory messageStoreFactory = new MemoryStoreFactory(); - MessageFactory messageFactory = new DefaultMessageFactory(); - - return new ThreadedSocketAcceptor(new ApplicationAdapter(), - messageStoreFactory, sessionSettings, messageFactory); - } - } - - static class TestInitiator extends TestConnector { - private static final Logger LOGGER = LoggerFactory.getLogger(TestInitiator.class); - - public TestInitiator(SessionSettings sessionSettings) throws ConfigError { - super(sessionSettings); - } - - @Override - public SessionConnector createConnector(SessionSettings sessionSettings) throws ConfigError { - LOGGER.info("Creating initiator: {}", sessionSettings); - - MessageStoreFactory messageStoreFactory = new MemoryStoreFactory(); - MessageFactory messageFactory = new DefaultMessageFactory(); - - return new ThreadedSocketInitiator(new ApplicationAdapter(), - messageStoreFactory, sessionSettings, messageFactory); - } - } - - /** - * Creates acceptor settings that contains two sessions. One with SSL support, one without. - */ - private SessionSettings createMixedSessionAcceptorSettings(int sslPort, int nonSslPort, String keyStoreName) { - HashMap defaults = new HashMap<>(); - defaults.put(SessionFactory.SETTING_CONNECTION_TYPE, "acceptor"); - defaults.put(Session.SETTING_START_TIME, "00:00:00"); - defaults.put(Session.SETTING_END_TIME, "00:00:00"); - defaults.put(Session.SETTING_HEARTBTINT, "30"); - - SessionSettings sessionSettings = new SessionSettings(); - sessionSettings.set(defaults); - - SessionID sslSession = new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA_SSL", "ZULU_SSL"); - sessionSettings.setString(sslSession, "BeginString", FixVersions.BEGINSTRING_FIX44); - sessionSettings.setString(sslSession, "DataDictionary", "FIX44.xml"); - sessionSettings.setString(sslSession, "TargetCompID", "ZULU_SSL"); - sessionSettings.setString(sslSession, "SenderCompID", "ALFA_SSL"); - sessionSettings.setString(sslSession, SSLSupport.SETTING_USE_SSL, "Y"); - sessionSettings.setString(sslSession, SSLSupport.SETTING_KEY_STORE_NAME, keyStoreName); - sessionSettings.setString(sslSession, SSLSupport.SETTING_KEY_STORE_PWD, "password"); - sessionSettings.setString(sslSession, SSLSupport.SETTING_NEED_CLIENT_AUTH, "N"); - sessionSettings.setString(sslSession, "SocketAcceptPort", Integer.toString(sslPort)); - - SessionID nonSslSession = new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA_NON_SSL", "ZULU_NON_SSL"); - sessionSettings.setString(nonSslSession, "BeginString", FixVersions.BEGINSTRING_FIX44); - sessionSettings.setString(nonSslSession, "DataDictionary", "FIX44.xml"); - sessionSettings.setString(nonSslSession, "TargetCompID", "ZULU_NON_SSL"); - sessionSettings.setString(nonSslSession, "SenderCompID", "ALFA_NON_SSL"); - sessionSettings.setString(nonSslSession, "SocketAcceptPort", Integer.toString(nonSslPort)); - - return sessionSettings; - } - - private SessionSettings createMultiSessionAcceptorSettings(String keyStoreName, boolean needClientAuth, - String[] trustStoreNames, String cipherSuites, String protocols) { - HashMap defaults = new HashMap<>(); - defaults.put(SessionFactory.SETTING_CONNECTION_TYPE, "acceptor"); - defaults.put(SSLSupport.SETTING_USE_SSL, "Y"); - defaults.put(SSLSupport.SETTING_KEY_STORE_NAME, keyStoreName); - defaults.put(SSLSupport.SETTING_KEY_STORE_PWD, "password"); - defaults.put(SSLSupport.SETTING_NEED_CLIENT_AUTH, needClientAuth ? "Y" : "N"); - defaults.put(Session.SETTING_START_TIME, "00:00:00"); - defaults.put(Session.SETTING_END_TIME, "00:00:00"); - defaults.put(Session.SETTING_HEARTBTINT, "30"); - - if (cipherSuites != null) { - defaults.put(SSLSupport.SETTING_CIPHER_SUITES, cipherSuites); - } - - if (protocols != null) { - defaults.put(SSLSupport.SETTING_ENABLED_PROTOCOLS, protocols); - } - - SessionSettings sessionSettings = new SessionSettings(); - sessionSettings.set(defaults); - - for (int i = 0; i < trustStoreNames.length; i++) { - SessionID sessionID = new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA" + i, "ZULU" + i); - sessionSettings.setString(sessionID, "BeginString", FixVersions.BEGINSTRING_FIX44); - sessionSettings.setString(sessionID, "DataDictionary", "FIX44.xml"); - sessionSettings.setString(sessionID, "TargetCompID", "ZULU" + i); - sessionSettings.setString(sessionID, "SenderCompID", "ALFA" + i); - sessionSettings.setString(sessionID, SSLSupport.SETTING_TRUST_STORE_NAME, trustStoreNames[i]); - sessionSettings.setString(sessionID, SSLSupport.SETTING_TRUST_STORE_PWD, "password"); - sessionSettings.setString(sessionID, "SocketAcceptPort", "1234" + i); - } - - return sessionSettings; - } - - private SessionSettings createAcceptorSettings(String keyStoreName, boolean needClientAuth, String trustStoreName, - String cipherSuites, String protocols, String keyStoreType, String trustStoreType, int port) { - HashMap defaults = new HashMap<>(); - defaults.put(SessionFactory.SETTING_CONNECTION_TYPE, "acceptor"); - defaults.put(SSLSupport.SETTING_USE_SSL, "Y"); - defaults.put(SSLSupport.SETTING_KEY_STORE_NAME, keyStoreName); - defaults.put(SSLSupport.SETTING_KEY_STORE_PWD, "password"); - - if (keyStoreType != null) { - defaults.put(SSLSupport.SETTING_KEY_STORE_TYPE, keyStoreType); - } - - if (trustStoreName != null) { - defaults.put(SSLSupport.SETTING_TRUST_STORE_NAME, trustStoreName); - defaults.put(SSLSupport.SETTING_TRUST_STORE_PWD, "password"); - - if (trustStoreType != null) { - defaults.put(SSLSupport.SETTING_TRUST_STORE_TYPE, trustStoreType); - } - } - - defaults.put(SSLSupport.SETTING_NEED_CLIENT_AUTH, needClientAuth ? "Y" : "N"); - defaults.put(Acceptor.SETTING_SOCKET_ACCEPT_PORT, Integer.toString(port)); - defaults.put(Session.SETTING_START_TIME, "00:00:00"); - defaults.put(Session.SETTING_END_TIME, "00:00:00"); - defaults.put(Session.SETTING_HEARTBTINT, "30"); - - if (cipherSuites != null) { - defaults.put(SSLSupport.SETTING_CIPHER_SUITES, cipherSuites); - } - - if (protocols != null) { - defaults.put(SSLSupport.SETTING_ENABLED_PROTOCOLS, protocols); - } - - SessionID sessionID = new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU"); - - SessionSettings sessionSettings = new SessionSettings(); - sessionSettings.set(defaults); - sessionSettings.setString(sessionID, "BeginString", FixVersions.BEGINSTRING_FIX44); - sessionSettings.setString(sessionID, "DataDictionary", "FIX44.xml"); - sessionSettings.setString(sessionID, "SenderCompID", "ALFA"); - sessionSettings.setString(sessionID, "TargetCompID", "ZULU"); - - return sessionSettings; - } - - private SessionSettings createInitiatorSettings(String keyStoreName, String trustStoreName, String cipherSuites, - String protocols, String senderId, String targetId, String port, String keyStoreType, - String trustStoreType) { - return createInitiatorSettings(keyStoreName, trustStoreName, cipherSuites, protocols, senderId, targetId, port,keyStoreType, trustStoreType, null); - } - - private SessionSettings createInitiatorSettings(String keyStoreName, String trustStoreName, String cipherSuites, - String protocols, String senderId, String targetId, String port, String keyStoreType, - String trustStoreType, String endpointIdentificationAlgorithm) { - HashMap defaults = new HashMap<>(); - defaults.put(SessionFactory.SETTING_CONNECTION_TYPE, "initiator"); - defaults.put(Initiator.SETTING_SOCKET_CONNECT_PROTOCOL, ProtocolFactory.getTypeString(ProtocolFactory.SOCKET)); - defaults.put(SSLSupport.SETTING_USE_SSL, "Y"); - defaults.put(SSLSupport.SETTING_KEY_STORE_NAME, keyStoreName); - defaults.put(SSLSupport.SETTING_KEY_STORE_PWD, "password"); - - if (keyStoreType != null) { - defaults.put(SSLSupport.SETTING_KEY_STORE_TYPE, keyStoreType); - } - - if (trustStoreName != null) { - defaults.put(SSLSupport.SETTING_TRUST_STORE_NAME, trustStoreName); - defaults.put(SSLSupport.SETTING_TRUST_STORE_PWD, "password"); - - if (trustStoreType != null) { - defaults.put(SSLSupport.SETTING_TRUST_STORE_TYPE, trustStoreType); - } - } - - defaults.put(Initiator.SETTING_SOCKET_CONNECT_HOST, "localhost"); - defaults.put(Initiator.SETTING_SOCKET_CONNECT_PORT, port); - defaults.put(Initiator.SETTING_RECONNECT_INTERVAL, "2"); - defaults.put(Session.SETTING_START_TIME, "00:00:00"); - defaults.put(Session.SETTING_END_TIME, "00:00:00"); - defaults.put(Session.SETTING_HEARTBTINT, "30"); - - if (cipherSuites != null) { - defaults.put(SSLSupport.SETTING_CIPHER_SUITES, cipherSuites); - } - - if (protocols != null) { - defaults.put(SSLSupport.SETTING_ENABLED_PROTOCOLS, protocols); - } - - if (endpointIdentificationAlgorithm != null) { - defaults.put(SSLSupport.SETTING_ENDPOINT_IDENTIFICATION_ALGORITHM, endpointIdentificationAlgorithm); - } - - SessionID sessionID = new SessionID(FixVersions.BEGINSTRING_FIX44, senderId, targetId); - - SessionSettings sessionSettings = new SessionSettings(); - sessionSettings.set(defaults); - sessionSettings.setString(sessionID, "BeginString", FixVersions.BEGINSTRING_FIX44); - sessionSettings.setString(sessionID, "DataDictionary", "FIX44.xml"); - sessionSettings.setString(sessionID, "SenderCompID", senderId); - sessionSettings.setString(sessionID, "TargetCompID", targetId); - - return sessionSettings; - } - - private SessionSettings createInitiatorSettings(String senderId, String targetId, int port) { - HashMap defaults = new HashMap<>(); - defaults.put(SessionFactory.SETTING_CONNECTION_TYPE, "initiator"); - defaults.put(Initiator.SETTING_SOCKET_CONNECT_PROTOCOL, ProtocolFactory.getTypeString(ProtocolFactory.SOCKET)); - defaults.put(Initiator.SETTING_SOCKET_CONNECT_HOST, "localhost"); - defaults.put(Initiator.SETTING_SOCKET_CONNECT_PORT, Integer.toString(port)); - defaults.put(Initiator.SETTING_RECONNECT_INTERVAL, "2"); - defaults.put(Session.SETTING_START_TIME, "00:00:00"); - defaults.put(Session.SETTING_END_TIME, "00:00:00"); - defaults.put(Session.SETTING_HEARTBTINT, "30"); - - SessionID sessionID = new SessionID(FixVersions.BEGINSTRING_FIX44, senderId, targetId); - - SessionSettings sessionSettings = new SessionSettings(); - sessionSettings.set(defaults); - sessionSettings.setString(sessionID, "BeginString", FixVersions.BEGINSTRING_FIX44); - sessionSettings.setString(sessionID, "DataDictionary", "FIX44.xml"); - sessionSettings.setString(sessionID, "SenderCompID", senderId); - sessionSettings.setString(sessionID, "TargetCompID", targetId); - - return sessionSettings; - } -} +/******************************************************************************* + * Copyright (c) quickfixengine.org All rights reserved. + * + * This file is part of the QuickFIX FIX Engine + * + * This file may be distributed under the terms of the quickfixengine.org + * license as defined by quickfixengine.org and appearing in the file + * LICENSE included in the packaging of this file. + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING + * THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * See http://www.quickfixengine.org/LICENSE for licensing information. + * + * Contact ask@quickfixengine.org if any conditions of this licensing + * are not clear to you. + ******************************************************************************/ + +package quickfix.mina.ssl; + +import org.apache.mina.core.filterchain.IoFilterAdapter; +import org.apache.mina.core.session.IoSession; +import org.burningwave.tools.net.DefaultHostResolver; +import org.burningwave.tools.net.HostResolutionRequestInterceptor; +import org.burningwave.tools.net.MappedHostResolver; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import quickfix.Acceptor; +import quickfix.ApplicationAdapter; +import quickfix.ConfigError; +import quickfix.DefaultMessageFactory; +import quickfix.FixVersions; +import quickfix.Initiator; +import quickfix.MemoryStoreFactory; +import quickfix.MessageFactory; +import quickfix.MessageStoreFactory; +import quickfix.RuntimeError; +import quickfix.Session; +import quickfix.SessionFactory; +import quickfix.SessionID; +import quickfix.SessionSettings; +import quickfix.ThreadedSocketAcceptor; +import quickfix.ThreadedSocketInitiator; +import quickfix.mina.ProtocolFactory; +import quickfix.mina.SessionConnector; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLSession; +import java.math.BigInteger; +import java.security.Principal; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import org.apache.mina.util.AvailablePortFinder; +import org.junit.After; +import quickfix.mina.SocksProxyServer; +import quickfix.test.util.SSLUtil; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +@RunWith(Parameterized.class) +public class SSLCertificateTest { + + private static final String LOCALHOST_ALIAS = "localhost-quickfixj"; + + @Parameters + public static List parameters() { + return Arrays.asList(new String[][]{{"TLS_RSA_WITH_AES_128_CBC_SHA", "TLSv1.2"}, {"TLS_AES_256_GCM_SHA384", "TLSv1.3"}}); + } + + // Note: To diagnose cipher suite errors, run with -Djavax.net.debug=ssl:handshake + private final String enabledCipherSuites; + private final String enabledProtocols; + + public SSLCertificateTest(String enabledCipherSuites, String enabledProtocols) { + this.enabledCipherSuites = enabledCipherSuites; + this.enabledProtocols = enabledProtocols; + } + + @Before + public void setUp() { + Map hostAliases = new HashMap<>(); + hostAliases.put(LOCALHOST_ALIAS, "127.0.0.1"); + + HostResolutionRequestInterceptor.INSTANCE.install(new MappedHostResolver(hostAliases), DefaultHostResolver.INSTANCE); + } + + @After + public void tearDown() { + try { + Thread.sleep(500); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + + HostResolutionRequestInterceptor.INSTANCE.uninstall(); + } + + @Test + public void shouldAuthenticateServerCertificate() throws Exception { + int freePort = AvailablePortFinder.getNextAvailable(); + TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/server.keystore", false, + "single-session/empty.keystore", enabledCipherSuites, enabledProtocols, "JKS", "JKS", freePort)); + + try { + acceptor.start(); + + TestInitiator initiator = new TestInitiator( + createInitiatorSettings("single-session/empty.keystore", "single-session/client.truststore", + enabledCipherSuites, enabledProtocols, "ZULU", "ALFA", Integer.toString(freePort), "JKS", "JKS")); + + try { + initiator.start(); + + initiator.assertNoSslExceptionThrown(); + initiator.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + initiator.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA"), + new BigInteger("1448538842")); + initiator.assertNoSNIHostName(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + + acceptor.assertNoSslExceptionThrown(); + acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU"), false); + } finally { + initiator.stop(); + } + } finally { + acceptor.stop(); + } + } + + @Test + public void shouldLoginViaSocks4Proxy() throws Exception { + shouldAuthenticateServerCertificateViaSocksProxy("4"); + } + + @Test + public void shouldLoginViaSocks4aProxy() throws Exception { + shouldAuthenticateServerCertificateViaSocksProxy("4a"); + } + + @Test + public void shouldLoginViaSocks5Proxy() throws Exception { + shouldAuthenticateServerCertificateViaSocksProxy("5"); + } + + public void shouldAuthenticateServerCertificateViaSocksProxy(String proxyVersion) throws Exception { + int proxyPort = AvailablePortFinder.getNextAvailable(); + + SocksProxyServer proxyServer = new SocksProxyServer(proxyPort); + proxyServer.start(); + + try { + int port = AvailablePortFinder.getNextAvailable(); + TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/server.keystore", false, + "single-session/empty.keystore", enabledCipherSuites, enabledProtocols, "JKS", "JKS", port)); + + try { + acceptor.start(); + + SessionSettings initiatorSettings = createInitiatorSettings("single-session/empty.keystore", "single-session/client.truststore", + enabledCipherSuites, enabledProtocols, "ZULU", "ALFA", Integer.toString(port), "JKS", "JKS"); + + Properties defaults = initiatorSettings.getDefaultProperties(); + + defaults.put(Initiator.SETTING_PROXY_HOST, "localhost"); + defaults.put(Initiator.SETTING_PROXY_PORT, Integer.toString(proxyPort)); + defaults.put(Initiator.SETTING_PROXY_TYPE, "socks"); + defaults.put(Initiator.SETTING_PROXY_VERSION, proxyVersion); + defaults.put(Initiator.SETTING_PROXY_USER, "proxy-user"); + defaults.put(Initiator.SETTING_PROXY_PASSWORD, "proxy-password"); + + TestInitiator initiator = new TestInitiator(initiatorSettings); + + try { + initiator.start(); + + initiator.assertNoSslExceptionThrown(); + initiator.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + initiator.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA"), new BigInteger("1448538842")); + initiator.assertNoSNIHostName(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + + acceptor.assertNoSslExceptionThrown(); + acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU"), false); + } finally { + initiator.stop(); + } + } finally { + acceptor.stop(); + } + } finally { + proxyServer.stop(); + } + } + + /** + * Server certificate has Common Name = localhost and no Server Alternative Name extension. + */ + @Test + public void shouldAuthenticateServerNameUsingServerCommonName() throws Exception { + int freePort = AvailablePortFinder.getNextAvailable(); + TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/server-cn.keystore", false, + "single-session/empty.keystore", enabledCipherSuites, enabledProtocols, "JKS", "JKS", freePort)); + + try { + acceptor.start(); + + TestInitiator initiator = new TestInitiator( + createInitiatorSettings("single-session/empty.keystore", "single-session/client-cn.truststore", + enabledCipherSuites, enabledProtocols, "ZULU", "ALFA", Integer.toString(freePort), "JKS", "JKS", "HTTPS")); + + try { + initiator.start(); + + initiator.assertNoSslExceptionThrown(); + initiator.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + initiator.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA"), + new BigInteger("1683903911")); + initiator.assertNoSNIHostName(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + + acceptor.assertNoSslExceptionThrown(); + acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU"), false); + } finally { + initiator.stop(); + } + } finally { + acceptor.stop(); + } + } + + /** + * Server certificate has Common Name = server, but it has Server Alternative Name extension (DNS name). + */ + @Test + public void shouldAuthenticateServerNameUsingSANExtension() throws Exception { + int freePort = AvailablePortFinder.getNextAvailable(); + + TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/server-sni.keystore", false, + "single-session/empty.keystore", enabledCipherSuites, enabledProtocols, "JKS", "JKS", freePort)); + + try { + acceptor.start(); + + TestInitiator initiator = new TestInitiator( + createInitiatorSettings("single-session/empty.keystore", "single-session/client-sni.truststore", + enabledCipherSuites, enabledProtocols, "ZULU", "ALFA", Integer.toString(freePort), "JKS", "JKS")); + + try { + initiator.start(); + + initiator.assertNoSslExceptionThrown(); + initiator.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + initiator.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA"), + new BigInteger("1683904647")); + initiator.assertNoSNIHostName(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + + acceptor.assertNoSslExceptionThrown(); + acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU"), false); + } finally { + initiator.stop(); + } + } finally { + acceptor.stop(); + } + } + + /** + * Server certificate has Common Name = server and no Server Alternative Name extension. + */ + @Test + public void shouldFailWhenHostnameDoesNotMatchServerName() throws Exception { + int freePort = AvailablePortFinder.getNextAvailable(); + + TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/server-bad-cn.keystore", false, + "single-session/empty.keystore", enabledCipherSuites, enabledProtocols, "JKS", "JKS", freePort)); + + try { + acceptor.start(); + + TestInitiator initiator = new TestInitiator( + createInitiatorSettings("single-session/empty.keystore", "single-session/client-bad-cn.truststore", + enabledCipherSuites, enabledProtocols, "ZULU", "ALFA", Integer.toString(freePort), "JKS", "JKS", "HTTPS")); + + try { + initiator.start(); + + initiator.assertSslExceptionThrown("No name matching localhost found", SSLHandshakeException.class); + initiator.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + initiator.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + + acceptor.assertSslExceptionThrown("Received fatal alert: certificate_unknown", SSLHandshakeException.class); + acceptor.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + } finally { + initiator.stop(); + } + } finally { + acceptor.stop(); + } + } + + // proxy - N, useSNI = N, SNIHostName - Y + @Test + public void shouldNotSendServerNameWhenProxyDisabledSniDisabledHostNameProvided() throws Exception { + int freePort = AvailablePortFinder.getNextAvailable(); + + TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/server.keystore", false, + "single-session/empty.keystore", enabledCipherSuites, enabledProtocols, "JKS", "JKS", freePort)); + + try { + acceptor.start(); + + TestInitiator initiator = new TestInitiator( + createInitiatorSettings("single-session/empty.keystore", "single-session/client.truststore", + enabledCipherSuites, enabledProtocols, "ZULU", "ALFA", Integer.toString(freePort), "JKS", "JKS", null, null, -1, false, "foo")); + + try { + initiator.start(); + + initiator.assertNoSslExceptionThrown(); + initiator.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + initiator.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA"), + new BigInteger("1448538842")); + initiator.assertNoSNIHostName(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + + acceptor.assertNoSslExceptionThrown(); + acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU"), false); + } finally { + initiator.stop(); + } + } finally { + acceptor.stop(); + } + } + + // proxy - N, useSNI = Y, SNIHostName - N + @Test + public void shouldSendServerNameWhenProxyDisabledSniEnabledHostNameNotProvided() throws Exception { + int freePort = AvailablePortFinder.getNextAvailable(); + + TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/server.keystore", false, + "single-session/empty.keystore", enabledCipherSuites, enabledProtocols, "JKS", "JKS", freePort)); + + try { + acceptor.start(); + + TestInitiator initiator = new TestInitiator( + createInitiatorSettings("single-session/empty.keystore", "single-session/client.truststore", + enabledCipherSuites, enabledProtocols, "ZULU", "ALFA", Integer.toString(freePort), "JKS", "JKS", null, null, -1, true, null)); + + try { + initiator.start(); + + initiator.assertNoSslExceptionThrown(); + initiator.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + initiator.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA"), + new BigInteger("1448538842")); + initiator.assertSNIHostName(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA"), "localhost"); + + acceptor.assertNoSslExceptionThrown(); + acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU"), false); + } finally { + initiator.stop(); + } + } finally { + acceptor.stop(); + } + } + + // proxy - N, useSNI = Y, SNIHostName - Y + @Test + public void shouldSendServerNameWhenProxyDisabledSniEnabledHostNameProvided() throws Exception { + int freePort = AvailablePortFinder.getNextAvailable(); + + TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/server.keystore", false, + "single-session/empty.keystore", enabledCipherSuites, enabledProtocols, "JKS", "JKS", freePort)); + + try { + acceptor.start(); + + TestInitiator initiator = new TestInitiator( + createInitiatorSettings("single-session/empty.keystore", "single-session/client.truststore", + enabledCipherSuites, enabledProtocols, "ZULU", "ALFA", Integer.toString(freePort), "JKS", "JKS", null, null, -1, true, "foo")); + + try { + initiator.start(); + + initiator.assertNoSslExceptionThrown(); + initiator.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + initiator.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA"), + new BigInteger("1448538842")); + initiator.assertSNIHostName(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA"), "foo"); + + acceptor.assertNoSslExceptionThrown(); + acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU"), false); + } finally { + initiator.stop(); + } + } finally { + acceptor.stop(); + } + } + + // proxy - Y, useSNI = N, SNIHostName - N + @Test + public void shouldSendServerNameWhenProxyEnabledSniDisabledHostNameNotProvided() throws Exception { + int proxyPort = AvailablePortFinder.getNextAvailable(); + + SocksProxyServer proxyServer = new SocksProxyServer(proxyPort); + proxyServer.start(); + + try { + int freePort = AvailablePortFinder.getNextAvailable(); + + TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/server.keystore", false, + "single-session/empty.keystore", enabledCipherSuites, enabledProtocols, "JKS", "JKS", freePort)); + + try { + acceptor.start(); + + TestInitiator initiator = new TestInitiator( + createInitiatorSettings("single-session/empty.keystore", "single-session/client.truststore", + enabledCipherSuites, enabledProtocols, "ZULU", "ALFA", Integer.toString(freePort), "JKS", "JKS", null, LOCALHOST_ALIAS, proxyPort, false, null)); + + try { + initiator.start(); + + initiator.assertNoSslExceptionThrown(); + initiator.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + initiator.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA"), + new BigInteger("1448538842")); + initiator.assertNoSNIHostName(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + + acceptor.assertNoSslExceptionThrown(); + acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU"), false); + } finally { + initiator.stop(); + } + } finally { + acceptor.stop(); + } + } finally { + proxyServer.stop(); + } + } + + // proxy - Y, useSNI = N, SNIHostName - Y + @Test + public void shouldSendServerNameWhenProxyEnabledSniDisabledHostNameProvided() throws Exception { + int proxyPort = AvailablePortFinder.getNextAvailable(); + + SocksProxyServer proxyServer = new SocksProxyServer(proxyPort); + proxyServer.start(); + + try { + int freePort = AvailablePortFinder.getNextAvailable(); + + TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/server.keystore", false, + "single-session/empty.keystore", enabledCipherSuites, enabledProtocols, "JKS", "JKS", freePort)); + + try { + acceptor.start(); + + TestInitiator initiator = new TestInitiator( + createInitiatorSettings("single-session/empty.keystore", "single-session/client.truststore", + enabledCipherSuites, enabledProtocols, "ZULU", "ALFA", Integer.toString(freePort), "JKS", "JKS", null, LOCALHOST_ALIAS, proxyPort, false, "foo")); + + try { + initiator.start(); + + initiator.assertNoSslExceptionThrown(); + initiator.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + initiator.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA"), + new BigInteger("1448538842")); + initiator.assertNoSNIHostName(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + + acceptor.assertNoSslExceptionThrown(); + acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU"), false); + } finally { + initiator.stop(); + } + } finally { + acceptor.stop(); + } + } finally { + proxyServer.stop(); + } + } + + // proxy - Y, useSNI = Y, SNIHostName - N + @Test + public void shouldSendServerNameWhenProxyEnabledSniEnabledHostNameNotProvided() throws Exception { + int proxyPort = AvailablePortFinder.getNextAvailable(); + + SocksProxyServer proxyServer = new SocksProxyServer(proxyPort); + proxyServer.start(); + + try { + int freePort = AvailablePortFinder.getNextAvailable(); + + TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/server.keystore", false, + "single-session/empty.keystore", enabledCipherSuites, enabledProtocols, "JKS", "JKS", freePort)); + + try { + acceptor.start(); + + TestInitiator initiator = new TestInitiator( + createInitiatorSettings("single-session/empty.keystore", "single-session/client.truststore", + enabledCipherSuites, enabledProtocols, "ZULU", "ALFA", Integer.toString(freePort), "JKS", "JKS", null, LOCALHOST_ALIAS, proxyPort, true, null)); + + try { + initiator.start(); + + initiator.assertNoSslExceptionThrown(); + initiator.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + initiator.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA"), + new BigInteger("1448538842")); + initiator.assertSNIHostName(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA"), "localhost"); + + acceptor.assertNoSslExceptionThrown(); + acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU"), false); + } finally { + initiator.stop(); + } + } finally { + acceptor.stop(); + } + } finally { + proxyServer.stop(); + } + } + + // proxy - Y, useSNI = Y, SNIHostName - Y + @Test + public void shouldSendServerNameWhenProxyEnabledSniEnabledHostNameProvided() throws Exception { + int proxyPort = AvailablePortFinder.getNextAvailable(); + + SocksProxyServer proxyServer = new SocksProxyServer(proxyPort); + proxyServer.start(); + + try { + int freePort = AvailablePortFinder.getNextAvailable(); + + TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/server.keystore", false, + "single-session/empty.keystore", enabledCipherSuites, enabledProtocols, "JKS", "JKS", freePort)); + + try { + acceptor.start(); + + TestInitiator initiator = new TestInitiator( + createInitiatorSettings("single-session/empty.keystore", "single-session/client.truststore", + enabledCipherSuites, enabledProtocols, "ZULU", "ALFA", Integer.toString(freePort), "JKS", "JKS", null, LOCALHOST_ALIAS, proxyPort, true, "foo")); + + try { + initiator.start(); + + initiator.assertNoSslExceptionThrown(); + initiator.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + initiator.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA"), + new BigInteger("1448538842")); + initiator.assertSNIHostName(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA"), "foo"); + + acceptor.assertNoSslExceptionThrown(); + acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU"), false); + } finally { + initiator.stop(); + } + } finally { + acceptor.stop(); + } + } finally { + proxyServer.stop(); + } + } + + @Test + public void shouldAuthenticateServerAndClientCertificates() throws Exception { + int freePort = AvailablePortFinder.getNextAvailable(); + TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/server.keystore", true, + "single-session/server.truststore", enabledCipherSuites, enabledProtocols, "JKS", "JKS", freePort)); + + try { + acceptor.start(); + + TestInitiator initiator = new TestInitiator( + createInitiatorSettings("single-session/client.keystore", "single-session/client.truststore", + enabledCipherSuites, enabledProtocols, "ZULU", "ALFA", Integer.toString(freePort), "JKS", "JKS")); + + try { + initiator.start(); + + initiator.assertNoSslExceptionThrown(); + initiator.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + initiator.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA"), + new BigInteger("1448538842")); + initiator.assertNoSNIHostName(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + + acceptor.assertNoSslExceptionThrown(); + acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + acceptor.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU"), + new BigInteger("1448538787")); + } finally { + initiator.stop(); + } + } finally { + acceptor.stop(); + } + } + + @Test + public void shouldAuthenticateServerAndClientCertificatesWhenUsingDifferentKeystoreFormats() throws Exception { + int freePort = AvailablePortFinder.getNextAvailable(); + TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/server-pkcs12.keystore", true, + "single-session/server-jceks.truststore", enabledCipherSuites, enabledProtocols, "PKCS12", + "JCEKS", freePort)); + + try { + acceptor.start(); + + TestInitiator initiator = new TestInitiator(createInitiatorSettings("single-session/client-jceks.keystore", + "single-session/client-jceks.keystore", enabledCipherSuites, enabledProtocols, "ZULU", "ALFA", + Integer.toString(freePort), "JCEKS", "JCEKS")); + + try { + initiator.start(); + + initiator.assertNoSslExceptionThrown(); + initiator.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + initiator.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA"), + new BigInteger("1449683167")); + initiator.assertNoSNIHostName(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + + acceptor.assertNoSslExceptionThrown(); + acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + acceptor.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU"), + new BigInteger("1449683336")); + } finally { + initiator.stop(); + } + } finally { + acceptor.stop(); + } + } + + @Test + public void shouldAuthenticateServerAndClientCertificatesForIndividualSessions() throws Exception { + TestAcceptor acceptor = new TestAcceptor(createMultiSessionAcceptorSettings( + "multi-session/server.keystore", true, new String[] { "multi-session/server1.truststore", + "multi-session/server2.truststore", "multi-session/server3.truststore" }, + enabledCipherSuites, enabledProtocols)); + + try { + acceptor.start(); + + TestInitiator initiator1 = new TestInitiator( + createInitiatorSettings("multi-session/client1.keystore", "multi-session/client1.keystore", + enabledCipherSuites, enabledProtocols, "ZULU0", "ALFA0", "12340", "JKS", "JKS")); + TestInitiator initiator2 = new TestInitiator( + createInitiatorSettings("multi-session/client2.keystore", "multi-session/client2.keystore", + enabledCipherSuites, enabledProtocols, "ZULU1", "ALFA1", "12341", "JKS", "JKS")); + TestInitiator initiator3 = new TestInitiator( + createInitiatorSettings("multi-session/client3.keystore", "multi-session/client3.keystore", + enabledCipherSuites, enabledProtocols, "ZULU2", "ALFA2", "12342", "JKS", "JKS")); + + try { + initiator1.start(); + initiator2.start(); + initiator3.start(); + + initiator1.assertNoSslExceptionThrown(); + initiator1.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU0", "ALFA0")); + initiator1.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU0", "ALFA0"), + new BigInteger("1449581686")); + initiator1.assertNoSNIHostName(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU0", "ALFA0")); + + initiator2.assertNoSslExceptionThrown(); + initiator2.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU1", "ALFA1")); + initiator2.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU1", "ALFA1"), + new BigInteger("1449581686")); + initiator2.assertNoSNIHostName(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU1", "ALFA1")); + + initiator3.assertNoSslExceptionThrown(); + initiator3.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU2", "ALFA2")); + initiator3.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU2", "ALFA2"), + new BigInteger("1449581686")); + initiator3.assertNoSNIHostName(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU2", "ALFA2")); + + acceptor.assertNoSslExceptionThrown(); + acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA0", "ZULU0")); + acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA1", "ZULU1")); + acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA2", "ZULU2")); + acceptor.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA0", "ZULU0"), + new BigInteger("1449581008")); + acceptor.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA1", "ZULU1"), + new BigInteger("1449581372")); + acceptor.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA2", "ZULU2"), + new BigInteger("1449581412")); + + } finally { + initiator1.stop(); + initiator2.stop(); + initiator3.stop(); + } + } finally { + acceptor.stop(); + } + } + + @Test + public void shouldFailIndividualSessionsWhenInvalidCertificatesUsed() throws Exception { + TestAcceptor acceptor = new TestAcceptor(createMultiSessionAcceptorSettings( + "multi-session/server.keystore", true, new String[] { "multi-session/server1.truststore", + "multi-session/server2.truststore", "multi-session/server3.truststore" }, + enabledCipherSuites, enabledProtocols)); + + try { + acceptor.start(); + + TestInitiator initiator1 = new TestInitiator( + createInitiatorSettings("multi-session/client2.keystore", "multi-session/client2.keystore", + enabledCipherSuites, enabledProtocols, "ZULU0", "ALFA0", "12340", "JKS", "JKS")); + TestInitiator initiator2 = new TestInitiator( + createInitiatorSettings("multi-session/client1.keystore", "multi-session/client1.keystore", + enabledCipherSuites, enabledProtocols, "ZULU1", "ALFA1", "12341", "JKS", "JKS")); + TestInitiator initiator3 = new TestInitiator( + createInitiatorSettings("multi-session/client3.keystore", "multi-session/client3.keystore", + enabledCipherSuites, enabledProtocols, "ZULU2", "ALFA2", "12342", "JKS", "JKS")); + + try { + initiator1.start(); + initiator2.start(); + initiator3.start(); + + initiator1.assertSslExceptionThrown(); + initiator1.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU0", "ALFA0")); + initiator1.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU0", "ALFA0")); + + initiator2.assertSslExceptionThrown(); + initiator2.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU1", "ALFA1")); + initiator2.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU1", "ALFA1")); + + initiator3.assertNoSslExceptionThrown(); + initiator3.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU2", "ALFA2")); + initiator3.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU2", "ALFA2"), + new BigInteger("1449581686")); + initiator3.assertNoSNIHostName(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU2", "ALFA2")); + + acceptor.assertSslExceptionThrown(); + acceptor.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA0", "ZULU0")); + acceptor.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA1", "ZULU1")); + acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA2", "ZULU2")); + acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA0", "ZULU0")); + acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA1", "ZULU1")); + acceptor.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA2", "ZULU2"), + new BigInteger("1449581412")); + } finally { + initiator1.stop(); + initiator2.stop(); + initiator3.stop(); + } + } finally { + acceptor.stop(); + } + } + + @Test + public void shouldFailWhenUsingEmptyServerKeyStore() throws Exception { + int freePort = AvailablePortFinder.getNextAvailable(); + TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/empty.keystore", false, + "single-session/empty.keystore", null, null, "JKS", "JKS", freePort)); + + try { + acceptor.start(); + + TestInitiator initiator = new TestInitiator(createInitiatorSettings("single-session/empty.keystore", + "single-session/empty.keystore", null, null, "ZULU", "ALFA", Integer.toString(freePort), "JKS", "JKS")); + + try { + initiator.start(); + + initiator.assertSslExceptionThrown(); + initiator.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + initiator.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + + acceptor.assertSslExceptionThrown(); + acceptor.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + } finally { + initiator.stop(); + } + } finally { + acceptor.stop(); + } + } + + @Test + public void shouldFailWhenUsingEmptyClientTruststore() throws Exception { + int freePort = AvailablePortFinder.getNextAvailable(); + TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/server.keystore", false, + "single-session/empty.keystore", enabledCipherSuites, enabledProtocols, "JKS", "JKS", freePort)); + + try { + acceptor.start(); + + TestInitiator initiator = new TestInitiator( + createInitiatorSettings("single-session/empty.keystore", "single-session/empty.keystore", + enabledCipherSuites, enabledProtocols, "ZULU", "ALFA", Integer.toString(freePort), "JKS", "JKS")); + + try { + initiator.start(); + + initiator.assertSslExceptionThrown(); + initiator.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + initiator.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + + // client disconnects before acceptor throws an exception + acceptor.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + } finally { + initiator.stop(); + } + } finally { + acceptor.stop(); + } + } + + @Test + public void shouldFailWhenUsingEmptyServerTrustore() throws Exception { + int freePort = AvailablePortFinder.getNextAvailable(); + TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/server.keystore", true, + "single-session/empty.keystore", enabledCipherSuites, enabledProtocols, "JKS", "JKS", freePort)); + + try { + acceptor.start(); + + TestInitiator initiator = new TestInitiator( + createInitiatorSettings("single-session/client.keystore", "single-session/client.truststore", + enabledCipherSuites, enabledProtocols, "ZULU", "ALFA", Integer.toString(freePort), "JKS", "JKS")); + + try { + initiator.start(); + + initiator.assertSslExceptionThrown(); + initiator.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + initiator.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + + acceptor.assertSslExceptionThrown(); + acceptor.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + } finally { + initiator.stop(); + } + } finally { + acceptor.stop(); + } + } + + @Test + public void shouldFailWhenUsingBadClientCertificate() throws Exception { + int freePort = AvailablePortFinder.getNextAvailable(); + TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/server.keystore", true, + "single-session/server.truststore", enabledCipherSuites, enabledProtocols, "JKS", "JKS", freePort)); + + try { + acceptor.start(); + + TestInitiator initiator = new TestInitiator( + createInitiatorSettings("single-session/server.keystore", "single-session/client.truststore", + enabledCipherSuites, enabledProtocols, "ZULU", "ALFA", Integer.toString(freePort), "JKS", "JKS")); + + try { + initiator.start(); + + initiator.assertSslExceptionThrown(); + initiator.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + initiator.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + + acceptor.assertSslExceptionThrown(); + acceptor.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + } finally { + initiator.stop(); + } + } finally { + acceptor.stop(); + } + } + + @Test + public void shouldFailWhenUsingBadServerCertificate() throws Exception { + int freePort = AvailablePortFinder.getNextAvailable(); + TestAcceptor acceptor = new TestAcceptor(createAcceptorSettings("single-session/client.keystore", false, + "single-session/empty.keystore", enabledCipherSuites, enabledProtocols, "JKS", "JKS", freePort)); + + try { + acceptor.start(); + + TestInitiator initiator = new TestInitiator( + createInitiatorSettings("single-session/empty.keystore", "single-session/client.truststore", + enabledCipherSuites, enabledProtocols, "ZULU", "ALFA", Integer.toString(freePort), "JKS", "JKS")); + + try { + initiator.start(); + + initiator.assertSslExceptionThrown(); + initiator.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + initiator.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU", "ALFA")); + + acceptor.assertSslExceptionThrown(); + acceptor.assertNotLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU")); + } finally { + initiator.stop(); + } + } finally { + acceptor.stop(); + } + } + + @Test + public void shouldConnectDifferentTypesOfSessions() throws Exception { + int sslPort = AvailablePortFinder.getNextAvailable(); + int nonSslPort = AvailablePortFinder.getNextAvailable(); + TestAcceptor acceptor = new TestAcceptor(createMixedSessionAcceptorSettings(sslPort, nonSslPort, "single-session/server.keystore")); + + try { + acceptor.start(); + + TestInitiator sslInitiator = new TestInitiator( + createInitiatorSettings("single-session/client.keystore", "single-session/client.truststore", + enabledCipherSuites, enabledProtocols, "ZULU_SSL", "ALFA_SSL", Integer.toString(sslPort), "JKS", "JKS")); + + TestInitiator nonSslInitiator = new TestInitiator(createInitiatorSettings("ZULU_NON_SSL", "ALFA_NON_SSL", nonSslPort)); + + try { + sslInitiator.start(); + nonSslInitiator.start(); + + sslInitiator.assertNoSslExceptionThrown(); + sslInitiator.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU_SSL", "ALFA_SSL")); + sslInitiator.assertAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU_SSL", "ALFA_SSL"), + new BigInteger("1448538842")); + sslInitiator.assertNoSNIHostName(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU_SSL", "ALFA_SSL")); + + acceptor.assertNoSslExceptionThrown(); + acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA_SSL", "ZULU_SSL")); + acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA_SSL", "ZULU_SSL"), false); + + nonSslInitiator.assertNoSslExceptionThrown(); + nonSslInitiator.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU_NON_SSL", "ALFA_NON_SSL")); + nonSslInitiator.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ZULU_NON_SSL", "ALFA_NON_SSL")); + + acceptor.assertNoSslExceptionThrown(); + acceptor.assertLoggedOn(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA_NON_SSL", "ZULU_NON_SSL")); + acceptor.assertNotAuthenticated(new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA_NON_SSL", "ZULU_NON_SSL")); + + } finally { + sslInitiator.stop(); + nonSslInitiator.stop(); + } + } finally { + acceptor.stop(); + } + } + + static abstract class TestConnector { + private static final Logger LOGGER = LoggerFactory.getLogger(TestConnector.class); + private static final int TIMEOUT_SECONDS = 5; + + private final SessionConnector connector; + private final CountDownLatch exceptionThrownLatch; + private final AtomicReference exception; + + public TestConnector(SessionSettings sessionSettings) throws ConfigError { + this.connector = prepareConnector(sessionSettings); + this.exceptionThrownLatch = new CountDownLatch(1); + this.exception = new AtomicReference<>(); + } + + private SessionConnector prepareConnector(SessionSettings sessionSettings) throws ConfigError { + SessionConnector sessionConnector = createConnector(sessionSettings); + sessionConnector.setIoFilterChainBuilder(chain -> chain.addFirst("Exception handler", new IoFilterAdapter() { + @Override + public void exceptionCaught(NextFilter nextFilter, IoSession session, Throwable cause) { + LOGGER.info("exceptionCaught", cause); + exception.set(cause); + exceptionThrownLatch.countDown(); + nextFilter.exceptionCaught(session, cause); + } + })); + + return sessionConnector; + } + + public abstract SessionConnector createConnector(SessionSettings sessionSettings) throws ConfigError; + + private Session findSession(SessionID sessionID) { + for (Session session : connector.getManagedSessions()) { + if (session.getSessionID().equals(sessionID)) + return session; + } + + return null; + } + + public void assertAuthenticated(SessionID sessionID, BigInteger serialNumber) { + Session session = findSession(sessionID); + SSLSession sslSession = SSLUtil.findSSLSession(session); + + if (sslSession == null) { + throw new AssertionError("No SSL session found: " + sessionID); + } + + Certificate[] peerCertificates = SSLUtil.getPeerCertificates(sslSession); + + if (peerCertificates == null || peerCertificates.length == 0) { + throw new AssertionError("Session was not authenticated: " + sslSession); + } + + for (Certificate peerCertificate : peerCertificates) { + if (!(peerCertificate instanceof X509Certificate)) { + continue; + } + + if (((X509Certificate)peerCertificate).getSerialNumber().compareTo(serialNumber) == 0) { + return; + } + } + + throw new AssertionError("Certificate with serial number " + serialNumber + " was not authenticated"); + } + + public void assertNotAuthenticated(SessionID sessionID) { + assertNotAuthenticated(sessionID, true); + } + + /** + * Asserts that the session associated with the given {@code sessionID} is not authenticated. + * The behavior of this method depends on the {@code authOn} parameter: + * + * + * If {@code authOn} is {@code true}, the method checks if the SSL session associated + * with the given session ID is still alive. If the SSL session persists beyond the + * specified timeout period, an {@link AssertionError} is thrown. + * If {@code authOn} is {@code false}, the method checks if there are any peer certificates + * associated with the SSL session. If peer certificates are found, an {@link AssertionError} + * is thrown, indicating that the session was authenticated. + * + * + * @param sessionID the session ID to check for authentication status + * @param authOn a flag indicating whether authentication is currently enabled + * @throws AssertionError if the session is still authenticated after the timeout period + * (when {@code authOn} is {@code true}) or if peer certificates are found + * (when {@code authOn} is {@code false}) + */ + public void assertNotAuthenticated(SessionID sessionID, boolean authOn) { + Session session = findSession(sessionID); + SSLSession sslSession = SSLUtil.findSSLSession(session); + + if (sslSession == null) { + return; + } + + if (authOn) { + // when authentication is on, the SSL session maybe still be alive (invalid) for some time + long startTime = System.nanoTime(); + + while (SSLUtil.findSSLSession(session) != null) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Thread was interrupted", e); + } + + if (TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - startTime) >= TIMEOUT_SECONDS) { + throw new AssertionError("SSL session still exists for session: " + sessionID); + } + } + } else { + // when authentication is off, there must be no peer certificates + Certificate[] peerCertificates = SSLUtil.getPeerCertificates(sslSession); + + if (peerCertificates != null && peerCertificates.length > 0) { + throw new AssertionError("Certificate was authenticated"); + } + } + } + + public void assertLoggedOn(SessionID sessionID) { + Session session = findSession(sessionID); + + if (session == null) { + throw new AssertionError("No session found: " + sessionID); + } + + if (!session.isLoggedOn()) { + throw new AssertionError("Session is not logged on: " + session); + } + } + + public void assertNotLoggedOn(SessionID sessionID) { + Session session = findSession(sessionID); + + if (session == null) { + throw new AssertionError("No session found: " + sessionID); + } + + if (session.isLoggedOn()) { + throw new AssertionError("Session is logged on: " + session); + } + } + + public void assertSslExceptionThrown() throws Exception { + assertSslExceptionThrown(null, null); + } + + public void assertSslExceptionThrown(String expectedErrorMessage, Class> expectedErrorType) throws Exception { + boolean reachedZero = exceptionThrownLatch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS); + + if (!reachedZero) { + throw new AssertionError("No SSL exception thrown"); + } + + Throwable throwable = exception.get(); + + if (expectedErrorMessage != null) { + String thrownErrorMessage = throwable.getMessage(); + assertTrue("Thrown error message: " + thrownErrorMessage + " does not contain: " + expectedErrorMessage, + thrownErrorMessage != null && thrownErrorMessage.contains(expectedErrorMessage)); + } + + if (expectedErrorType != null) { + assertSame(expectedErrorType, throwable.getClass()); + } + } + + public void assertNoSslExceptionThrown() throws Exception { + boolean reachedZero = exceptionThrownLatch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS); + + if (reachedZero) { + throw new AssertionError("SSL exception thrown"); + } + } + + public void assertSNIHostName(SessionID sessionID, String expectedSniHostName) { + Session session = findSession(sessionID); + SSLSession sslSession = SSLUtil.findSSLSession(session); + + if (sslSession == null) { + throw new AssertionError("No SSL session found: " + sessionID); + } + + String sniHostName = SSLUtil.getSniHostName(sslSession); + + if (sniHostName == null) { + throw new AssertionError("SNIHostName not found: " + sessionID); + } + + assertEquals("Expected SNIHostName: " + expectedSniHostName + " does not match actual: " + sniHostName, expectedSniHostName, sniHostName); + } + + public void assertNoSNIHostName(SessionID sessionID) { + Session session = findSession(sessionID); + SSLSession sslSession = SSLUtil.findSSLSession(session); + + if (sslSession == null) { + throw new AssertionError("No SSL session found: " + sessionID); + } + + String sniHostName = SSLUtil.getSniHostName(sslSession); + + if (sniHostName != null) { + throw new AssertionError("SNIHostName found: " + sniHostName); + } + } + + public void start() throws RuntimeError, ConfigError { + connector.start(); + } + + public void stop() { + try { + logSSLInfo(); + } catch (Exception e) { + LOGGER.error("Failed to log SSL info", e); + } + + connector.stop(); + } + + private void logSSLInfo() { + List sessionsIDs = connector.getSessions(); + LOGGER.info("All session IDs: {}", sessionsIDs); + + for (SessionID sessionID : sessionsIDs) { + Session session = findSession(sessionID); + + if (session == null) { + LOGGER.info("No session found for ID: {}", sessionID); + continue; + } + + SSLSession sslSession = SSLUtil.findSSLSession(session); + + if (sslSession == null) { + LOGGER.info("No SSL session found for session: {}", session); + continue; + } + + Throwable exception = this.exception.get(); + String exceptionMessage = exception != null ? exception.getMessage() : null; + Class> exceptionType = exception != null ? exception.getClass() : null; + Certificate[] peerCertificates = SSLUtil.getPeerCertificates(sslSession); + Principal peerPrincipal = SSLUtil.getPeerPrincipal(sslSession); + SSLEngine sslEngine = SSLUtil.getSSLEngine(session); + SSLEngineResult.HandshakeStatus handshakeStatus = sslEngine != null ? sslEngine.getHandshakeStatus() : null; + + LOGGER.info("SSL session info [sessionID={},isLoggedOn={},sslSession={},sslSession.valid={},peerCertificates={},peerPrincipal={},exceptionMessage={},exceptionType={},handshakeStatus={}]", + sessionID, session.isLoggedOn(), sslSession, sslSession.isValid(), peerCertificates, peerPrincipal, exceptionMessage, exceptionType, handshakeStatus); + } + } + } + + static class TestAcceptor extends TestConnector { + private static final Logger LOGGER = LoggerFactory.getLogger(TestAcceptor.class); + + public TestAcceptor(SessionSettings sessionSettings) throws ConfigError { + super(sessionSettings); + } + + @Override + public SessionConnector createConnector(SessionSettings sessionSettings) throws ConfigError { + LOGGER.info("Creating acceptor: {}", sessionSettings); + + MessageStoreFactory messageStoreFactory = new MemoryStoreFactory(); + MessageFactory messageFactory = new DefaultMessageFactory(); + + return new ThreadedSocketAcceptor(new ApplicationAdapter(), + messageStoreFactory, sessionSettings, messageFactory); + } + } + + static class TestInitiator extends TestConnector { + private static final Logger LOGGER = LoggerFactory.getLogger(TestInitiator.class); + + public TestInitiator(SessionSettings sessionSettings) throws ConfigError { + super(sessionSettings); + } + + @Override + public SessionConnector createConnector(SessionSettings sessionSettings) throws ConfigError { + LOGGER.info("Creating initiator: {}", sessionSettings); + + MessageStoreFactory messageStoreFactory = new MemoryStoreFactory(); + MessageFactory messageFactory = new DefaultMessageFactory(); + + return new ThreadedSocketInitiator(new ApplicationAdapter(), + messageStoreFactory, sessionSettings, messageFactory); + } + } + + /** + * Creates acceptor settings that contains two sessions. One with SSL support, one without. + */ + private SessionSettings createMixedSessionAcceptorSettings(int sslPort, int nonSslPort, String keyStoreName) { + HashMap defaults = new HashMap<>(); + defaults.put(SessionFactory.SETTING_CONNECTION_TYPE, "acceptor"); + defaults.put(Session.SETTING_START_TIME, "00:00:00"); + defaults.put(Session.SETTING_END_TIME, "00:00:00"); + defaults.put(Session.SETTING_HEARTBTINT, "30"); + + SessionSettings sessionSettings = new SessionSettings(); + sessionSettings.set(defaults); + + SessionID sslSession = new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA_SSL", "ZULU_SSL"); + sessionSettings.setString(sslSession, "BeginString", FixVersions.BEGINSTRING_FIX44); + sessionSettings.setString(sslSession, "DataDictionary", "FIX44.xml"); + sessionSettings.setString(sslSession, "TargetCompID", "ZULU_SSL"); + sessionSettings.setString(sslSession, "SenderCompID", "ALFA_SSL"); + sessionSettings.setString(sslSession, SSLSupport.SETTING_USE_SSL, "Y"); + sessionSettings.setString(sslSession, SSLSupport.SETTING_KEY_STORE_NAME, keyStoreName); + sessionSettings.setString(sslSession, SSLSupport.SETTING_KEY_STORE_PWD, "password"); + sessionSettings.setString(sslSession, SSLSupport.SETTING_NEED_CLIENT_AUTH, "N"); + sessionSettings.setString(sslSession, "SocketAcceptPort", Integer.toString(sslPort)); + + SessionID nonSslSession = new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA_NON_SSL", "ZULU_NON_SSL"); + sessionSettings.setString(nonSslSession, "BeginString", FixVersions.BEGINSTRING_FIX44); + sessionSettings.setString(nonSslSession, "DataDictionary", "FIX44.xml"); + sessionSettings.setString(nonSslSession, "TargetCompID", "ZULU_NON_SSL"); + sessionSettings.setString(nonSslSession, "SenderCompID", "ALFA_NON_SSL"); + sessionSettings.setString(nonSslSession, "SocketAcceptPort", Integer.toString(nonSslPort)); + + return sessionSettings; + } + + private SessionSettings createMultiSessionAcceptorSettings(String keyStoreName, boolean needClientAuth, + String[] trustStoreNames, String cipherSuites, String protocols) { + HashMap defaults = new HashMap<>(); + defaults.put(SessionFactory.SETTING_CONNECTION_TYPE, "acceptor"); + defaults.put(SSLSupport.SETTING_USE_SSL, "Y"); + defaults.put(SSLSupport.SETTING_KEY_STORE_NAME, keyStoreName); + defaults.put(SSLSupport.SETTING_KEY_STORE_PWD, "password"); + defaults.put(SSLSupport.SETTING_NEED_CLIENT_AUTH, needClientAuth ? "Y" : "N"); + defaults.put(Session.SETTING_START_TIME, "00:00:00"); + defaults.put(Session.SETTING_END_TIME, "00:00:00"); + defaults.put(Session.SETTING_HEARTBTINT, "30"); + + if (cipherSuites != null) { + defaults.put(SSLSupport.SETTING_CIPHER_SUITES, cipherSuites); + } + + if (protocols != null) { + defaults.put(SSLSupport.SETTING_ENABLED_PROTOCOLS, protocols); + } + + SessionSettings sessionSettings = new SessionSettings(); + sessionSettings.set(defaults); + + for (int i = 0; i < trustStoreNames.length; i++) { + SessionID sessionID = new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA" + i, "ZULU" + i); + sessionSettings.setString(sessionID, "BeginString", FixVersions.BEGINSTRING_FIX44); + sessionSettings.setString(sessionID, "DataDictionary", "FIX44.xml"); + sessionSettings.setString(sessionID, "TargetCompID", "ZULU" + i); + sessionSettings.setString(sessionID, "SenderCompID", "ALFA" + i); + sessionSettings.setString(sessionID, SSLSupport.SETTING_TRUST_STORE_NAME, trustStoreNames[i]); + sessionSettings.setString(sessionID, SSLSupport.SETTING_TRUST_STORE_PWD, "password"); + sessionSettings.setString(sessionID, "SocketAcceptPort", "1234" + i); + } + + return sessionSettings; + } + + private SessionSettings createAcceptorSettings(String keyStoreName, boolean needClientAuth, String trustStoreName, + String cipherSuites, String protocols, String keyStoreType, String trustStoreType, int port) { + HashMap defaults = new HashMap<>(); + defaults.put(SessionFactory.SETTING_CONNECTION_TYPE, "acceptor"); + defaults.put(SSLSupport.SETTING_USE_SSL, "Y"); + defaults.put(SSLSupport.SETTING_KEY_STORE_NAME, keyStoreName); + defaults.put(SSLSupport.SETTING_KEY_STORE_PWD, "password"); + + if (keyStoreType != null) { + defaults.put(SSLSupport.SETTING_KEY_STORE_TYPE, keyStoreType); + } + + if (trustStoreName != null) { + defaults.put(SSLSupport.SETTING_TRUST_STORE_NAME, trustStoreName); + defaults.put(SSLSupport.SETTING_TRUST_STORE_PWD, "password"); + + if (trustStoreType != null) { + defaults.put(SSLSupport.SETTING_TRUST_STORE_TYPE, trustStoreType); + } + } + + defaults.put(SSLSupport.SETTING_NEED_CLIENT_AUTH, needClientAuth ? "Y" : "N"); + defaults.put(Acceptor.SETTING_SOCKET_ACCEPT_PORT, Integer.toString(port)); + defaults.put(Session.SETTING_START_TIME, "00:00:00"); + defaults.put(Session.SETTING_END_TIME, "00:00:00"); + defaults.put(Session.SETTING_HEARTBTINT, "30"); + + if (cipherSuites != null) { + defaults.put(SSLSupport.SETTING_CIPHER_SUITES, cipherSuites); + } + + if (protocols != null) { + defaults.put(SSLSupport.SETTING_ENABLED_PROTOCOLS, protocols); + } + + SessionID sessionID = new SessionID(FixVersions.BEGINSTRING_FIX44, "ALFA", "ZULU"); + + SessionSettings sessionSettings = new SessionSettings(); + sessionSettings.set(defaults); + sessionSettings.setString(sessionID, "BeginString", FixVersions.BEGINSTRING_FIX44); + sessionSettings.setString(sessionID, "DataDictionary", "FIX44.xml"); + sessionSettings.setString(sessionID, "SenderCompID", "ALFA"); + sessionSettings.setString(sessionID, "TargetCompID", "ZULU"); + + return sessionSettings; + } + + private SessionSettings createInitiatorSettings(String keyStoreName, String trustStoreName, String cipherSuites, + String protocols, String senderId, String targetId, String port, String keyStoreType, + String trustStoreType) { + return createInitiatorSettings(keyStoreName, trustStoreName, cipherSuites, protocols, senderId, targetId, port,keyStoreType, trustStoreType, null); + } + + private SessionSettings createInitiatorSettings(String keyStoreName, String trustStoreName, String cipherSuites, + String protocols, String senderId, String targetId, String port, String keyStoreType, + String trustStoreType, String endpointIdentificationAlgorithm) { + return createInitiatorSettings(keyStoreName, trustStoreName, cipherSuites, protocols, senderId, targetId, port,keyStoreType, trustStoreType, endpointIdentificationAlgorithm, null, -1, false, null); + } + + private SessionSettings createInitiatorSettings(String keyStoreName, String trustStoreName, String cipherSuites, + String protocols, String senderId, String targetId, String port, String keyStoreType, + String trustStoreType, String endpointIdentificationAlgorithm, + String proxyHost, int proxyPort, + boolean useSni, String sniHostName) { + + HashMap defaults = new HashMap<>(); + defaults.put(SessionFactory.SETTING_CONNECTION_TYPE, "initiator"); + defaults.put(Initiator.SETTING_SOCKET_CONNECT_PROTOCOL, ProtocolFactory.getTypeString(ProtocolFactory.SOCKET)); + defaults.put(SSLSupport.SETTING_USE_SSL, "Y"); + defaults.put(SSLSupport.SETTING_KEY_STORE_NAME, keyStoreName); + defaults.put(SSLSupport.SETTING_KEY_STORE_PWD, "password"); + + if (proxyHost != null) { + defaults.put(Initiator.SETTING_PROXY_HOST, proxyHost); + defaults.put(Initiator.SETTING_PROXY_PORT, Integer.toString(proxyPort)); + defaults.put(Initiator.SETTING_PROXY_TYPE, "socks"); + defaults.put(Initiator.SETTING_PROXY_VERSION, "5"); + defaults.put(Initiator.SETTING_PROXY_USER, "proxy-user"); + defaults.put(Initiator.SETTING_PROXY_PASSWORD, "proxy-password"); + } + + if (keyStoreType != null) { + defaults.put(SSLSupport.SETTING_KEY_STORE_TYPE, keyStoreType); + } + + if (trustStoreName != null) { + defaults.put(SSLSupport.SETTING_TRUST_STORE_NAME, trustStoreName); + defaults.put(SSLSupport.SETTING_TRUST_STORE_PWD, "password"); + + if (trustStoreType != null) { + defaults.put(SSLSupport.SETTING_TRUST_STORE_TYPE, trustStoreType); + } + } + + defaults.put(Initiator.SETTING_SOCKET_CONNECT_HOST, "localhost"); + defaults.put(Initiator.SETTING_SOCKET_CONNECT_PORT, port); + defaults.put(Initiator.SETTING_RECONNECT_INTERVAL, "2"); + defaults.put(Session.SETTING_START_TIME, "00:00:00"); + defaults.put(Session.SETTING_END_TIME, "00:00:00"); + defaults.put(Session.SETTING_HEARTBTINT, "30"); + + if (cipherSuites != null) { + defaults.put(SSLSupport.SETTING_CIPHER_SUITES, cipherSuites); + } + + if (protocols != null) { + defaults.put(SSLSupport.SETTING_ENABLED_PROTOCOLS, protocols); + } + + if (endpointIdentificationAlgorithm != null) { + defaults.put(SSLSupport.SETTING_ENDPOINT_IDENTIFICATION_ALGORITHM, endpointIdentificationAlgorithm); + } + + defaults.put(SSLSupport.SETTING_USE_SNI, useSni ? "Y" : "N"); + + if (sniHostName != null) { + defaults.put(SSLSupport.SETTING_SNI_HOST_NAME, sniHostName); + } + + SessionID sessionID = new SessionID(FixVersions.BEGINSTRING_FIX44, senderId, targetId); + + SessionSettings sessionSettings = new SessionSettings(); + sessionSettings.set(defaults); + sessionSettings.setString(sessionID, "BeginString", FixVersions.BEGINSTRING_FIX44); + sessionSettings.setString(sessionID, "DataDictionary", "FIX44.xml"); + sessionSettings.setString(sessionID, "SenderCompID", senderId); + sessionSettings.setString(sessionID, "TargetCompID", targetId); + + return sessionSettings; + } + + private SessionSettings createInitiatorSettings(String senderId, String targetId, int port) { + HashMap defaults = createDefaults(port); + + SessionID sessionID = new SessionID(FixVersions.BEGINSTRING_FIX44, senderId, targetId); + + SessionSettings sessionSettings = new SessionSettings(); + sessionSettings.set(defaults); + sessionSettings.setString(sessionID, "BeginString", FixVersions.BEGINSTRING_FIX44); + sessionSettings.setString(sessionID, "DataDictionary", "FIX44.xml"); + sessionSettings.setString(sessionID, "SenderCompID", senderId); + sessionSettings.setString(sessionID, "TargetCompID", targetId); + + return sessionSettings; + } + + private static HashMap createDefaults(int port) { + HashMap defaults = new HashMap<>(); + defaults.put(SessionFactory.SETTING_CONNECTION_TYPE, "initiator"); + defaults.put(Initiator.SETTING_SOCKET_CONNECT_PROTOCOL, ProtocolFactory.getTypeString(ProtocolFactory.SOCKET)); + defaults.put(Initiator.SETTING_SOCKET_CONNECT_HOST, "localhost"); + defaults.put(Initiator.SETTING_SOCKET_CONNECT_PORT, Integer.toString(port)); + defaults.put(Initiator.SETTING_RECONNECT_INTERVAL, "2"); + defaults.put(Session.SETTING_START_TIME, "00:00:00"); + defaults.put(Session.SETTING_END_TIME, "00:00:00"); + defaults.put(Session.SETTING_HEARTBTINT, "30"); + return defaults; + } +} diff --git a/quickfixj-core/src/test/java/quickfix/mina/ssl/SSLSupportTest.java b/quickfixj-core/src/test/java/quickfix/mina/ssl/SSLSupportTest.java index 517d0842ad..89bc8552cd 100644 --- a/quickfixj-core/src/test/java/quickfix/mina/ssl/SSLSupportTest.java +++ b/quickfixj-core/src/test/java/quickfix/mina/ssl/SSLSupportTest.java @@ -1,14 +1,17 @@ package quickfix.mina.ssl; -import java.util.Arrays; - -import org.junit.Assert; import org.junit.Test; - import quickfix.FixVersions; import quickfix.SessionID; import quickfix.SessionSettings; +import java.util.Arrays; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + public class SSLSupportTest { @Test @@ -18,15 +21,18 @@ public void shouldLoadDefaultSslConfig() { SSLConfig sslConfig = SSLSupport.getSslConfig(sessionSettings, sessionID); - Assert.assertNull(sslConfig.getEnabledCipherSuites()); - Assert.assertNull(sslConfig.getEnabledProtocols()); - Assert.assertEquals("SunX509", sslConfig.getKeyManagerFactoryAlgorithm()); - Assert.assertEquals(SSLSupport.QUICKFIXJ_KEY_STORE, sslConfig.getKeyStoreName()); - Assert.assertTrue(Arrays.equals(SSLSupport.QUICKFIXJ_KEY_STORE_PWD.toCharArray(), sslConfig.getKeyStorePassword())); - Assert.assertEquals("JKS", sslConfig.getKeyStoreType()); - Assert.assertEquals("PKIX", sslConfig.getTrustManagerFactoryAlgorithm()); - Assert.assertNull(sslConfig.getTrustStoreName()); - Assert.assertNull(sslConfig.getTrustStorePassword()); - Assert.assertEquals("JKS", sslConfig.getTrustStoreType()); + assertNull(sslConfig.getEnabledCipherSuites()); + assertNull(sslConfig.getEnabledProtocols()); + assertEquals("SunX509", sslConfig.getKeyManagerFactoryAlgorithm()); + assertEquals(SSLSupport.QUICKFIXJ_KEY_STORE, sslConfig.getKeyStoreName()); + assertTrue(Arrays.equals(SSLSupport.QUICKFIXJ_KEY_STORE_PWD.toCharArray(), sslConfig.getKeyStorePassword())); + assertEquals("JKS", sslConfig.getKeyStoreType()); + assertEquals("PKIX", sslConfig.getTrustManagerFactoryAlgorithm()); + assertNull(sslConfig.getTrustStoreName()); + assertNull(sslConfig.getTrustStorePassword()); + assertEquals("JKS", sslConfig.getTrustStoreType()); + assertNull(sslConfig.getEndpointIdentificationAlgorithm()); + assertFalse(sslConfig.isUseSNI()); + assertNull(sslConfig.getSniHostName()); } } diff --git a/quickfixj-core/src/test/java/quickfix/test/util/SSLUtil.java b/quickfixj-core/src/test/java/quickfix/test/util/SSLUtil.java new file mode 100644 index 0000000000..bbc59537c6 --- /dev/null +++ b/quickfixj-core/src/test/java/quickfix/test/util/SSLUtil.java @@ -0,0 +1,203 @@ +package quickfix.test.util; + +import org.apache.mina.core.filterchain.IoFilterChain; +import org.apache.mina.core.session.AttributeKey; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.ssl.SslFilter; +import org.apache.mina.filter.ssl.SslHandler; +import quickfix.Session; +import quickfix.mina.IoSessionResponder; +import quickfix.mina.ssl.SSLSupport; + +import javax.net.ssl.ExtendedSSLSession; +import javax.net.ssl.SNIServerName; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; +import java.lang.reflect.Field; +import java.net.IDN; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.charset.StandardCharsets; +import java.security.Principal; +import java.security.cert.Certificate; +import java.util.List; + +/** + * A utility class for working with SSL/TLS sessions and retrieving SSL-related information from a {@link Session}. + * This class provides methods to find the underlying {@link SSLSession}, retrieve peer certificates, and get the peer principal etc. + */ +public final class SSLUtil { + + private static final String IO_SESSION_FIELD_NAME = "ioSession"; + private static final String SSL_ENGINE_FIELD_NAME = "mEngine"; + private static final AttributeKey SSL_HANDLER_ATTRIBUTE_KEY = new AttributeKey(SslHandler.class, "handler"); + private static final Field IO_SESSION_FIELD; + private static final Field SSL_ENGINE_FIELD; + private static final int SNI_HOST_NAME_TYPE = 0; + + static { + try { + IO_SESSION_FIELD = IoSessionResponder.class.getDeclaredField(IO_SESSION_FIELD_NAME); + IO_SESSION_FIELD.setAccessible(true); + } catch (NoSuchFieldException e) { + throw new RuntimeException("Unable to get field: " + IO_SESSION_FIELD_NAME, e); + } + + try { + SSL_ENGINE_FIELD = SslHandler.class.getDeclaredField(SSL_ENGINE_FIELD_NAME); + SSL_ENGINE_FIELD.setAccessible(true); + } catch (NoSuchFieldException e) { + throw new RuntimeException("Unable to get field: " + SSL_ENGINE_FIELD_NAME, e); + } + } + + private SSLUtil() { + } + + /** + * Retrieves the {@link SSLSession} associated with the given {@link Session}. + * + * @param session the session from which to retrieve the {@link SSLSession}. + * @return the {@link SSLSession} if found, or {@code null} if no SSL session is available. + */ + public static SSLSession findSSLSession(Session session) { + IoSession ioSession = findIoSession(session); + + if (ioSession == null) { + return null; + } + + IoFilterChain filterChain = ioSession.getFilterChain(); + SslFilter sslFilter = (SslFilter) filterChain.get(SSLSupport.FILTER_NAME); + + if (sslFilter == null) { + return null; + } + + return (SSLSession) ioSession.getAttribute(SslFilter.SSL_SECURED); + } + + /** + * Retrieves the {@link IoSession} associated with the given {@link Session}. + * + * @param session the session from which to retrieve the {@link IoSession}. + * @return the {@link IoSession} if found, or {@code null} if no underlying session is available. + */ + public static IoSession findIoSession(Session session) { + IoSessionResponder ioSessionResponder = (IoSessionResponder) session.getResponder(); + + if (ioSessionResponder == null) { + return null; + } + + try { + return (IoSession) IO_SESSION_FIELD.get(ioSessionResponder); + } catch (IllegalAccessException e) { + throw new RuntimeException("Failed to get IO session field", e); + } + } + + /** + * Retrieves the {@link SslHandler} associated with the given {@link Session}. + * This method first finds the corresponding {@link IoSession} for the provided session, + * then retrieves the {@link SslFilter} from the session's filter chain. + * If the filter is found, it returns the {@link SslHandler} stored as an attribute in the {@link IoSession}. + * + * @param session The session for which to retrieve the {@link SslHandler}. + * @return The {@link SslHandler} associated with the session, or {@code null} if either + * the {@link IoSession} or the {@link SslFilter} is not found. + */ + public static SslHandler getSSLHandler(Session session) { + IoSession ioSession = findIoSession(session); + + if (ioSession == null) { + return null; + } + + IoFilterChain filterChain = ioSession.getFilterChain(); + SslFilter sslFilter = (SslFilter) filterChain.get(SSLSupport.FILTER_NAME); + + if (sslFilter == null) { + return null; + } + + return (SslHandler) ioSession.getAttribute(SSL_HANDLER_ATTRIBUTE_KEY); + } + + /** + * Retrieves the {@link SSLEngine} associated with the given {@link Session}. + * This method first retrieves the {@link SslHandler} using {@link #getSSLHandler(Session)}, + * and then attempts to access the {@link SSLEngine} stored within the {@link SslHandler} + * using reflection. + * + * @param session The session for which to retrieve the {@link SSLEngine}. + * @return The {@link SSLEngine} associated with the session, or {@code null} if the + * {@link SslHandler} is not found. + */ + public static SSLEngine getSSLEngine(Session session) { + SslHandler sslHandler = getSSLHandler(session); + + if (sslHandler == null) { + return null; + } + + try { + return (SSLEngine) SSL_ENGINE_FIELD.get(sslHandler); + } catch (IllegalAccessException e) { + throw new RuntimeException("Failed to get SSL engine field", e); + } + } + + /** + * Retrieves the peer certificates from the given {@link SSLSession}. + * + * @param sslSession the SSL session from which to retrieve the peer certificates. + * @return an array of {@link Certificate} objects representing the peer certificates, or {@code null} if the peer is unverified. + */ + public static Certificate[] getPeerCertificates(SSLSession sslSession) { + try { + return sslSession.getPeerCertificates(); + } catch (SSLPeerUnverifiedException e) { + return null; + } + } + + /** + * Retrieves the peer principal from the given {@link SSLSession}. + * + * @param sslSession the SSL session from which to retrieve the peer principal. + * @return the {@link Principal} representing the peer, or {@code null} if the peer is unverified. + */ + public static Principal getPeerPrincipal(SSLSession sslSession) { + try { + return sslSession.getPeerPrincipal(); + } catch (SSLPeerUnverifiedException e) { + return null; + } + } + + /** + * Retrieves the first SNI Server Name that is SNI Host Name (type=0) provided in ClientHello. + * + * @param sslSession the SSL session from which to retrieve SNI Host Name + * @return the SNI Host Name SSL extensions value, or {@code null} if extension neither compatible nor available + */ + public static String getSniHostName(SSLSession sslSession) { + if (!(sslSession instanceof ExtendedSSLSession)) { + return null; + } + + ExtendedSSLSession extendedSSLSession = (ExtendedSSLSession) sslSession; + List requestedServerNames = extendedSSLSession.getRequestedServerNames(); + + for (SNIServerName serverName : requestedServerNames) { + if (serverName.getType() == SNI_HOST_NAME_TYPE) { + String asciiServerName = new String(serverName.getEncoded(), StandardCharsets.US_ASCII); + return IDN.toUnicode(asciiServerName, IDN.USE_STD3_ASCII_RULES); + } + } + + return null; + } +}
If provided, SNIHostName will be used as the server name. Otherwise, SocketConnectHost or SocketConnectHost<n> will be used.
Note: When this option is disabled, the JVM may still implicitly send the SSL server_name extension.
server_name