diff --git a/pom.xml b/pom.xml index 0767eeb978..805be3056d 100644 --- a/pom.xml +++ b/pom.xml @@ -101,7 +101,7 @@ 1.6.8 1.6.8 2.3.3 - 2.1.6 + 2.2.3 2.11.0 32.0.0-jre OrchestraFIXLatest.xml diff --git a/quickfixj-core/src/main/doc/usermanual/usage/configuration.html b/quickfixj-core/src/main/doc/usermanual/usage/configuration.html index 6708d0e9ca..8a661ef381 100644 --- a/quickfixj-core/src/main/doc/usermanual/usage/configuration.html +++ b/quickfixj-core/src/main/doc/usermanual/usage/configuration.html @@ -692,10 +692,12 @@

QuickFIX Settings

Java default cipher suites - UseSNI - Configures the SSL engine to use Server Name Indication. This option is only useful to initiators. - Y
N - N + EndpointIdentificationAlgorithm + Sets the endpoint identification algorithm. If the algorithm parameter is non-null, the endpoint identification/verification procedures must be handled during SSL/TLS handshaking. See + Endpoint Identification + Algorithm Names + + diff --git a/quickfixj-core/src/main/java/quickfix/mina/acceptor/AbstractSocketAcceptor.java b/quickfixj-core/src/main/java/quickfix/mina/acceptor/AbstractSocketAcceptor.java index 2b7cb2b359..93a10c98c9 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/acceptor/AbstractSocketAcceptor.java +++ b/quickfixj-core/src/main/java/quickfix/mina/acceptor/AbstractSocketAcceptor.java @@ -23,6 +23,7 @@ import org.apache.mina.core.buffer.SimpleBufferAllocator; import org.apache.mina.core.service.IoAcceptor; import org.apache.mina.filter.codec.ProtocolCodecFilter; +import org.apache.mina.filter.ssl.SslFilter; import quickfix.Acceptor; import quickfix.Application; import quickfix.ConfigError; @@ -45,7 +46,6 @@ import quickfix.mina.message.FIXProtocolCodecFactory; import quickfix.mina.ssl.SSLConfig; import quickfix.mina.ssl.SSLContextFactory; -import quickfix.mina.ssl.SSLFilter; import quickfix.mina.ssl.SSLSupport; import javax.net.ssl.SSLContext; @@ -132,10 +132,9 @@ private void installSSL(AcceptorSocketDescriptor descriptor, log.info("Installing SSL filter for {}", descriptor.getAddress()); SSLConfig sslConfig = descriptor.getSslConfig(); SSLContext sslContext = SSLContextFactory.getInstance(sslConfig); - SSLFilter sslFilter = new SSLFilter(sslContext); - sslFilter.setUseClientMode(false); + SslFilter sslFilter = new SslFilter(sslContext); sslFilter.setNeedClientAuth(sslConfig.isNeedClientAuth()); - sslFilter.setCipherSuites(sslConfig.getEnabledCipherSuites() != null ? sslConfig.getEnabledCipherSuites() + sslFilter.setEnabledCipherSuites(sslConfig.getEnabledCipherSuites() != null ? sslConfig.getEnabledCipherSuites() : SSLSupport.getDefaultCipherSuites(sslContext)); sslFilter.setEnabledProtocols(sslConfig.getEnabledProtocols() != null ? sslConfig.getEnabledProtocols() : SSLSupport.getSupportedProtocols(sslContext)); diff --git a/quickfixj-core/src/main/java/quickfix/mina/initiator/InitiatorProxyIoHandler.java b/quickfixj-core/src/main/java/quickfix/mina/initiator/InitiatorProxyIoHandler.java index 49bbdf620f..bdbc10f459 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/initiator/InitiatorProxyIoHandler.java +++ b/quickfixj-core/src/main/java/quickfix/mina/initiator/InitiatorProxyIoHandler.java @@ -22,16 +22,12 @@ import org.apache.mina.core.session.IoSession; import org.apache.mina.proxy.AbstractProxyIoHandler; -import quickfix.mina.ssl.SSLFilter; - class InitiatorProxyIoHandler extends AbstractProxyIoHandler { private final InitiatorIoHandler initiatorIoHandler; - private final SSLFilter sslFilter; - InitiatorProxyIoHandler(InitiatorIoHandler initiatorIoHandler, SSLFilter sslFilter) { + InitiatorProxyIoHandler(InitiatorIoHandler initiatorIoHandler) { super(); this.initiatorIoHandler = initiatorIoHandler; - this.sslFilter = sslFilter; } @Override @@ -60,9 +56,6 @@ public void exceptionCaught(IoSession ioSession, Throwable cause) throws Excepti } @Override - public void proxySessionOpened(IoSession ioSession) throws Exception { - if (this.sslFilter != null) { - this.sslFilter.initiateHandshake(ioSession); - } + public void proxySessionOpened(IoSession ioSession) { } } 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 e6c97eeeb8..b3826b091f 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/initiator/IoSessionInitiator.java +++ b/quickfixj-core/src/main/java/quickfix/mina/initiator/IoSessionInitiator.java @@ -24,6 +24,7 @@ import org.apache.mina.core.service.IoConnector; import org.apache.mina.core.session.IoSession; import org.apache.mina.filter.codec.ProtocolCodecFilter; +import org.apache.mina.filter.ssl.SslFilter; import org.apache.mina.proxy.ProxyConnector; import org.apache.mina.transport.socket.SocketConnector; import quickfix.ConfigError; @@ -40,7 +41,6 @@ import quickfix.mina.message.FIXProtocolCodecFactory; import quickfix.mina.ssl.SSLConfig; import quickfix.mina.ssl.SSLContextFactory; -import quickfix.mina.ssl.SSLFilter; import quickfix.mina.ssl.SSLSupport; import javax.net.ssl.SSLContext; @@ -153,9 +153,9 @@ private void setupIoConnector() throws ConfigError, GeneralSecurityException { boolean hasProxy = proxyType != null && proxyPort > 0 && socketAddresses[nextSocketAddressIndex] instanceof InetSocketAddress; - SSLFilter sslFilter = null; + SslFilter sslFilter = null; if (sslEnabled) { - sslFilter = installSslFilter(ioFilterChainBuilder, !hasProxy); + sslFilter = installSslFilter(ioFilterChainBuilder); } ioFilterChainBuilder.addLast(FIXProtocolCodecFactory.FILTER_NAME, new ProtocolCodecFilter(new FIXProtocolCodecFactory())); @@ -175,9 +175,7 @@ private void setupIoConnector() throws ConfigError, GeneralSecurityException { ); proxyConnector.setHandler(new InitiatorProxyIoHandler( - new InitiatorIoHandler(fixSession, sessionSettings, networkingOptions, eventHandlingStrategy), - sslFilter - )); + new InitiatorIoHandler(fixSession, sessionSettings, networkingOptions, eventHandlingStrategy))); newConnector = proxyConnector; } @@ -188,16 +186,15 @@ private void setupIoConnector() throws ConfigError, GeneralSecurityException { ioConnector = newConnector; } - private SSLFilter installSslFilter(CompositeIoFilterChainBuilder ioFilterChainBuilder, boolean autoStart) + private SslFilter installSslFilter(CompositeIoFilterChainBuilder ioFilterChainBuilder) throws GeneralSecurityException { final SSLContext sslContext = SSLContextFactory.getInstance(sslConfig); - final SSLFilter sslFilter = new SSLFilter(sslContext, autoStart); - sslFilter.setUseClientMode(true); - sslFilter.setCipherSuites(sslConfig.getEnabledCipherSuites() != null ? sslConfig.getEnabledCipherSuites() + final SslFilter sslFilter = new SslFilter(sslContext); + sslFilter.setEnabledCipherSuites(sslConfig.getEnabledCipherSuites() != null ? sslConfig.getEnabledCipherSuites() : SSLSupport.getDefaultCipherSuites(sslContext)); sslFilter.setEnabledProtocols(sslConfig.getEnabledProtocols() != null ? sslConfig.getEnabledProtocols() : SSLSupport.getSupportedProtocols(sslContext)); - sslFilter.setUseSNI(sslConfig.isUseSNI()); + sslFilter.setEndpointIdentificationAlgorithm(sslConfig.getEndpointIdentificationAlgorithm()); ioFilterChainBuilder.addLast(SSLSupport.FILTER_NAME, sslFilter); return sslFilter; } 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 667750f341..4d45fe9f8c 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/ssl/SSLConfig.java +++ b/quickfixj-core/src/main/java/quickfix/mina/ssl/SSLConfig.java @@ -20,6 +20,7 @@ package quickfix.mina.ssl; import java.util.Arrays; +import java.util.Objects; /** * Groups together SSL related configuration. @@ -36,58 +37,7 @@ public class SSLConfig { private String[] enabledProtocols; private String[] enabledCipherSuites; private boolean needClientAuth; - private boolean useSNI; - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - SSLConfig other = (SSLConfig) obj; - if (!Arrays.equals(enabledCipherSuites, other.enabledCipherSuites)) - return false; - if (!Arrays.equals(enabledProtocols, other.enabledProtocols)) - return false; - if (keyManagerFactoryAlgorithm == null) { - if (other.keyManagerFactoryAlgorithm != null) - return false; - } else if (!keyManagerFactoryAlgorithm.equals(other.keyManagerFactoryAlgorithm)) - return false; - if (keyStoreName == null) { - if (other.keyStoreName != null) - return false; - } else if (!keyStoreName.equals(other.keyStoreName)) - return false; - if (!Arrays.equals(keyStorePassword, other.keyStorePassword)) - return false; - if (keyStoreType == null) { - if (other.keyStoreType != null) - return false; - } else if (!keyStoreType.equals(other.keyStoreType)) - return false; - if (needClientAuth != other.needClientAuth) - return false; - if (trustManagerFactoryAlgorithm == null) { - if (other.trustManagerFactoryAlgorithm != null) - return false; - } else if (!trustManagerFactoryAlgorithm.equals(other.trustManagerFactoryAlgorithm)) - return false; - if (trustStoreName == null) { - if (other.trustStoreName != null) - return false; - } else if (!trustStoreName.equals(other.trustStoreName)) - return false; - if (!Arrays.equals(trustStorePassword, other.trustStorePassword)) - return false; - if(useSNI != other.useSNI) - return false; - if (trustStoreType == null) { - return other.trustStoreType == null; - } else return trustStoreType.equals(other.trustStoreType); - } + private String endpointIdentificationAlgorithm; public String[] getEnabledCipherSuites() { return enabledCipherSuites; @@ -129,31 +79,12 @@ public String getTrustStoreType() { return trustStoreType; } - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + Arrays.hashCode(enabledCipherSuites); - result = prime * result + Arrays.hashCode(enabledProtocols); - result = prime * result + ((keyManagerFactoryAlgorithm == null) ? 0 : keyManagerFactoryAlgorithm.hashCode()); - result = prime * result + ((keyStoreName == null) ? 0 : keyStoreName.hashCode()); - result = prime * result + Arrays.hashCode(keyStorePassword); - result = prime * result + ((keyStoreType == null) ? 0 : keyStoreType.hashCode()); - result = prime * result + (needClientAuth ? 1231 : 1237); - result = prime * result - + ((trustManagerFactoryAlgorithm == null) ? 0 : trustManagerFactoryAlgorithm.hashCode()); - result = prime * result + ((trustStoreName == null) ? 0 : trustStoreName.hashCode()); - result = prime * result + Arrays.hashCode(trustStorePassword); - result = prime * result + ((trustStoreType == null) ? 0 : trustStoreType.hashCode()); - return result; - } - public boolean isNeedClientAuth() { return needClientAuth; } - public boolean isUseSNI() { - return useSNI; + public String getEndpointIdentificationAlgorithm() { + return endpointIdentificationAlgorithm; } public void setEnabledCipherSuites(String[] enabledCipherSuites) { @@ -184,8 +115,8 @@ public void setNeedClientAuth(boolean needClientAuth) { this.needClientAuth = needClientAuth; } - public void setUseSNI(boolean useSNI) { - this.useSNI = useSNI; + public void setEndpointIdentificationAlgorithm(String endpointIdentificationAlgorithm) { + this.endpointIdentificationAlgorithm = endpointIdentificationAlgorithm; } public void setTrustManagerFactoryAlgorithm(String trustManagerFactoryAlgorithm) { @@ -203,4 +134,33 @@ public void setTrustStorePassword(char[] trustStorePassword) { public void setTrustStoreType(String trustStoreType) { this.trustStoreType = trustStoreType; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SSLConfig sslConfig = (SSLConfig) o; + return needClientAuth == sslConfig.needClientAuth && + Objects.equals(keyStoreName, sslConfig.keyStoreName) && + Arrays.equals(keyStorePassword, sslConfig.keyStorePassword) && + Objects.equals(keyManagerFactoryAlgorithm, sslConfig.keyManagerFactoryAlgorithm) && + Objects.equals(keyStoreType, sslConfig.keyStoreType) && + Objects.equals(trustStoreName, sslConfig.trustStoreName) && + Arrays.equals(trustStorePassword, sslConfig.trustStorePassword) && + Objects.equals(trustManagerFactoryAlgorithm, sslConfig.trustManagerFactoryAlgorithm) && + Objects.equals(trustStoreType, sslConfig.trustStoreType) && + Arrays.equals(enabledProtocols, sslConfig.enabledProtocols) && + Arrays.equals(enabledCipherSuites, sslConfig.enabledCipherSuites) && + Objects.equals(endpointIdentificationAlgorithm, sslConfig.endpointIdentificationAlgorithm); + } + + @Override + public int hashCode() { + int result = Objects.hash(keyStoreName, keyManagerFactoryAlgorithm, keyStoreType, trustStoreName, trustManagerFactoryAlgorithm, trustStoreType, needClientAuth, endpointIdentificationAlgorithm); + result = 31 * result + Arrays.hashCode(keyStorePassword); + result = 31 * result + Arrays.hashCode(trustStorePassword); + result = 31 * result + Arrays.hashCode(enabledProtocols); + result = 31 * result + Arrays.hashCode(enabledCipherSuites); + return result; + } } diff --git a/quickfixj-core/src/main/java/quickfix/mina/ssl/SSLFilter.java b/quickfixj-core/src/main/java/quickfix/mina/ssl/SSLFilter.java deleted file mode 100644 index b9c036f9d2..0000000000 --- a/quickfixj-core/src/main/java/quickfix/mina/ssl/SSLFilter.java +++ /dev/null @@ -1,88 +0,0 @@ -/******************************************************************************* - * 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 java.net.InetSocketAddress; -import java.net.SocketAddress; -import javax.net.ssl.SSLContext; - -import javax.net.ssl.SSLException; -import org.apache.mina.core.filterchain.IoFilterChain; -import org.apache.mina.core.session.IoSession; -import org.apache.mina.filter.ssl.SslFilter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * An extended SSL filter based on MINA {@link SslFilter} that applies - * some adaptations. - */ -public class SSLFilter extends SslFilter { - - private final Logger log = LoggerFactory.getLogger(getClass()); - private boolean useSNI; - - public SSLFilter(SSLContext sslContext, boolean autoStart) { - super(sslContext, autoStart); - } - - public SSLFilter(SSLContext sslContext) { - super(sslContext); - } - - /** - * Called from {@link SslFilter#onPreAdd} every time a new - * session is created which makes it impossible to override enabled cipher - * suites configuration. - */ - @Override - public void setEnabledCipherSuites(String[] cipherSuites) { - } - - public void setCipherSuites(String[] cipherSuites) { - super.setEnabledCipherSuites(cipherSuites); - } - - /** - * Called before filter is added into the chain. - * We activate Server Name Indication if it is enabled in the session config. - */ - @Override - public void onPreAdd(IoFilterChain parent, String name, NextFilter nextFilter) - throws SSLException { - - if (useSNI) { - IoSession session = parent.getSession(); - SocketAddress remoteAddress = session.getRemoteAddress(); - - if (remoteAddress instanceof InetSocketAddress) { - // activate the SNI support in the JSSE SSLEngine - log.info("Activating TLS SNI support for peer address: {}", remoteAddress); - session.setAttribute(PEER_ADDRESS, remoteAddress); - } - } - - super.onPreAdd(parent, name, nextFilter); - } - - public void setUseSNI(boolean useSNI) { - this.useSNI = useSNI; - } -} 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 d821f2f64f..52550ad813 100644 --- a/quickfixj-core/src/main/java/quickfix/mina/ssl/SSLSupport.java +++ b/quickfixj-core/src/main/java/quickfix/mina/ssl/SSLSupport.java @@ -39,7 +39,7 @@ public class SSLSupport { public static final String SETTING_TRUST_MANAGER_FACTORY_ALGORITHM = "TrustManagerFactoryAlgorithm"; public static final String SETTING_TRUST_STORE_TYPE = "TrustStoreType"; public static final String SETTING_NEED_CLIENT_AUTH = "NeedClientAuth"; - public static final String SETTING_USE_SNI = "UseSNI"; + public static final String SETTING_ENDPOINT_IDENTIFICATION_ALGORITHM = "EndpointIdentificationAlgorithm"; public static final String SETTING_ENABLED_PROTOCOLS = "EnabledProtocols"; public static final String SETTING_CIPHER_SUITES = "CipherSuites"; static final String DEFAULT_STORE_TYPE = "JKS"; @@ -111,7 +111,7 @@ public static SSLConfig getSslConfig(SessionSettings sessionSettings, SessionID sslConfig.setEnabledCipherSuites(getEnabledCipherSuites(sessionSettings, sessionID)); sslConfig.setEnabledProtocols(getEnabledProtocols(sessionSettings, sessionID)); sslConfig.setNeedClientAuth(isNeedClientAuth(sessionSettings, sessionID)); - sslConfig.setUseSNI(isUseSNI(sessionSettings, sessionID)); + sslConfig.setEndpointIdentificationAlgorithm(getEndpointIdentificationAlgorithm(sessionSettings, sessionID)); return sslConfig; } @@ -150,7 +150,7 @@ public static boolean isNeedClientAuth(SessionSettings sessionSettings, SessionI return "Y".equals(getString(sessionSettings, sessionID, SETTING_NEED_CLIENT_AUTH, "N")); } - public static boolean isUseSNI(SessionSettings sessionSettings, SessionID sessionID) { - return "Y".equals(getString(sessionSettings, sessionID, SETTING_USE_SNI, "N")); + public static String getEndpointIdentificationAlgorithm(SessionSettings sessionSettings, SessionID sessionID) { + return getString(sessionSettings, sessionID, SETTING_ENDPOINT_IDENTIFICATION_ALGORITHM, 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 053dbb2018..6d31b9293f 100644 --- a/quickfixj-core/src/test/java/quickfix/mina/ssl/SSLCertificateTest.java +++ b/quickfixj-core/src/test/java/quickfix/mina/ssl/SSLCertificateTest.java @@ -22,6 +22,8 @@ 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.Assert; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,6 +44,7 @@ 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; @@ -51,10 +54,14 @@ import java.util.HashMap; 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 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 @@ -102,6 +109,111 @@ public void shouldAuthenticateServerCertificate() throws Exception { } } + /** + * 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(); @@ -446,19 +558,21 @@ static abstract class TestConnector { 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) - throws Exception { + public void exceptionCaught(NextFilter nextFilter, IoSession session, Throwable cause) { LOGGER.info("exceptionCaught", cause); + exception.set(cause); exceptionThrownLatch.countDown(); nextFilter.exceptionCaught(session, cause); } @@ -476,12 +590,12 @@ private SSLSession findSSLSession(Session session) throws Exception { return null; IoFilterChain filterChain = ioSession.getFilterChain(); - SSLFilter sslFilter = (SSLFilter) filterChain.get(SSLSupport.FILTER_NAME); + SslFilter sslFilter = (SslFilter) filterChain.get(SSLSupport.FILTER_NAME); if (sslFilter == null) return null; - return sslFilter.getSslSession(ioSession); + return (SSLSession) ioSession.getAttribute(SslFilter.SSL_SECURED); } private Session findSession(SessionID sessionID) { @@ -541,14 +655,14 @@ public void assertNotAuthenticated(SessionID sessionID) throws Exception { } } - public void assertLoggedOn(SessionID sessionID) throws InterruptedException { + 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) throws InterruptedException { + public void assertNotLoggedOn(SessionID sessionID) { Session session = findSession(sessionID); if (session.isLoggedOn()) @@ -556,11 +670,25 @@ public void assertNotLoggedOn(SessionID sessionID) throws InterruptedException { } 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 { @@ -711,8 +839,14 @@ private SessionSettings createAcceptorSettings(String keyStoreName, boolean need } private SessionSettings createInitiatorSettings(String keyStoreName, String trustStoreName, String cipherSuites, - String protocols, String senderId, String targetId, String port, String keyStoreType, - String trustStoreType) { + 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("ConnectionType", "initiator"); defaults.put("SocketConnectProtocol", ProtocolFactory.getTypeString(ProtocolFactory.SOCKET)); @@ -748,6 +882,10 @@ private SessionSettings createInitiatorSettings(String keyStoreName, String trus 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(); diff --git a/quickfixj-core/src/test/resources/single-session/client-bad-cn.truststore b/quickfixj-core/src/test/resources/single-session/client-bad-cn.truststore new file mode 100644 index 0000000000..a55af59bc5 Binary files /dev/null and b/quickfixj-core/src/test/resources/single-session/client-bad-cn.truststore differ diff --git a/quickfixj-core/src/test/resources/single-session/client-cn.truststore b/quickfixj-core/src/test/resources/single-session/client-cn.truststore new file mode 100644 index 0000000000..526fdae991 Binary files /dev/null and b/quickfixj-core/src/test/resources/single-session/client-cn.truststore differ diff --git a/quickfixj-core/src/test/resources/single-session/client-sni.truststore b/quickfixj-core/src/test/resources/single-session/client-sni.truststore new file mode 100644 index 0000000000..88a78c2335 Binary files /dev/null and b/quickfixj-core/src/test/resources/single-session/client-sni.truststore differ diff --git a/quickfixj-core/src/test/resources/single-session/server-bad-cn.keystore b/quickfixj-core/src/test/resources/single-session/server-bad-cn.keystore new file mode 100644 index 0000000000..910b8ce3b9 Binary files /dev/null and b/quickfixj-core/src/test/resources/single-session/server-bad-cn.keystore differ diff --git a/quickfixj-core/src/test/resources/single-session/server-cn.keystore b/quickfixj-core/src/test/resources/single-session/server-cn.keystore new file mode 100644 index 0000000000..700e10a313 Binary files /dev/null and b/quickfixj-core/src/test/resources/single-session/server-cn.keystore differ diff --git a/quickfixj-core/src/test/resources/single-session/server-sni.keystore b/quickfixj-core/src/test/resources/single-session/server-sni.keystore new file mode 100644 index 0000000000..1497555316 Binary files /dev/null and b/quickfixj-core/src/test/resources/single-session/server-sni.keystore differ