blob: a53115b67c3c31bc33f5d8b022508525d27d9585 [file] [log] [blame]
package org.apache.mina.filter.ssl;
import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder;
import org.apache.mina.core.filterchain.IoFilterChain;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.FilterEvent;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.apache.mina.util.AvailablePortFinder;
import org.junit.Before;
import org.junit.Test;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManagerFactory;
import java.net.InetSocketAddress;
import java.security.KeyStore;
import java.security.Security;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Test SNI matching scenarios. (tests for DIRMINA-1122)
*
* <pre>
* emptykeystore.sslTest - empty keystore
* server-cn.keystore - keystore with single certificate chain (CN=mina)
* client-cn.truststore - keystore with trusted certificate
* server-san-ext.keystore - keystore with single certificate chain (CN=mina;SAN=*.bbb.ccc,xxx.yyy)
* client-san-ext.truststore - keystore with trusted certificate
* </pre>
*/
public class SslIdentificationAlgorithmTest {
private static final String KEY_MANAGER_FACTORY_ALGORITHM;
static {
String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm");
if (algorithm == null) {
algorithm = KeyManagerFactory.getDefaultAlgorithm();
}
KEY_MANAGER_FACTORY_ALGORITHM = algorithm;
}
private int port;
private CountDownLatch handshakeDone;
@Before
public void setUp() {
port = AvailablePortFinder.getNextAvailable(5555);
handshakeDone = new CountDownLatch(2);
}
@Test
public void shouldAuthenticateWhenServerCertificateCommonNameMatchesClientSNI() throws Exception {
SSLContext acceptorContext = createSSLContext("server-cn.keystore", "emptykeystore.sslTest");
SSLContext connectorContext = createSSLContext("emptykeystore.sslTest", "client-cn.truststore");
startAcceptor(acceptorContext);
startConnector(connectorContext, "mina");
assertTrue(handshakeDone.await(10, TimeUnit.SECONDS));
}
@Test
public void shouldFailAuthenticationWhenServerCertificateCommonNameDoesNotMatchClientSNI() throws Exception {
SSLContext acceptorContext = createSSLContext("server-cn.keystore", "emptykeystore.sslTest");
SSLContext connectorContext = createSSLContext("emptykeystore.sslTest", "client-cn.truststore");
startAcceptor(acceptorContext);
startConnector(connectorContext, "example.com");
assertFalse(handshakeDone.await(10, TimeUnit.SECONDS));
}
@Test
public void shouldFailAuthenticationWhenClientMissingSNIAndIdentificationAlgorithmProvided() throws Exception {
SSLContext acceptorContext = createSSLContext("server-cn.keystore", "emptykeystore.sslTest");
SSLContext connectorContext = createSSLContext("emptykeystore.sslTest", "client-cn.truststore");
startAcceptor(acceptorContext);
startConnector(connectorContext, null);
assertFalse(handshakeDone.await(10, TimeUnit.SECONDS));
}
/**
* Subject Alternative Name (SAN) scenarios
*
* @exception Exception If the test trhows an exception
*/
@Test
public void shouldAuthenticateWhenServerCertificateAlternativeNameMatchesClientSNIExactly() throws Exception {
SSLContext acceptorContext = createSSLContext("server-san-ext.keystore", "emptykeystore.sslTest");
SSLContext connectorContext = createSSLContext("emptykeystore.sslTest", "client-san-ext.truststore");
startAcceptor(acceptorContext);
startConnector(connectorContext, "xxx.yyy");
assertTrue(handshakeDone.await(10, TimeUnit.SECONDS));
}
@Test
public void shouldAuthenticateWhenServerCertificateAlternativeNameMatchesClientSNIViaWildcard() throws Exception {
SSLContext acceptorContext = createSSLContext("server-san-ext.keystore", "emptykeystore.sslTest");
SSLContext connectorContext = createSSLContext("emptykeystore.sslTest", "client-san-ext.truststore");
startAcceptor(acceptorContext);
startConnector(connectorContext, "aaa.bbb.ccc");
assertTrue(handshakeDone.await(10, TimeUnit.SECONDS));
}
@Test
public void shouldFailAuthenticationWhenServerCommonNameMatchesSNIAndSNINotInAlternativeName() throws Exception {
SSLContext acceptorContext = createSSLContext("server-san-ext.keystore", "emptykeystore.sslTest");
SSLContext connectorContext = createSSLContext("emptykeystore.sslTest", "client-san-ext.truststore");
startAcceptor(acceptorContext);
startConnector(connectorContext, "mina");
assertFalse(handshakeDone.await(10, TimeUnit.SECONDS));
}
@Test
public void shouldFailAuthenticationWhenMatchingAlternativeNameWildcardExactly() throws Exception {
SSLContext acceptorContext = createSSLContext("server-san-ext.keystore", "emptykeystore.sslTest");
SSLContext connectorContext = createSSLContext("emptykeystore.sslTest", "client-san-ext.truststore");
startAcceptor(acceptorContext);
startConnector(connectorContext, "*.bbb.ccc");
assertFalse(handshakeDone.await(10, TimeUnit.SECONDS));
}
@Test
public void shouldFailAuthenticationWhenMatchingAlternativeNameWithTooManyLabels() throws Exception {
SSLContext acceptorContext = createSSLContext("server-san-ext.keystore", "emptykeystore.sslTest");
SSLContext connectorContext = createSSLContext("emptykeystore.sslTest", "client-san-ext.truststore");
startAcceptor(acceptorContext);
startConnector(connectorContext, "mmm.nnn.bbb.ccc");
assertFalse(handshakeDone.await(10, TimeUnit.SECONDS));
}
private void startAcceptor(SSLContext sslContext) throws Exception {
NioSocketAcceptor acceptor = new NioSocketAcceptor();
acceptor.setReuseAddress(true);
SslFilter sslFilter = new SslFilter(sslContext);
sslFilter.setEnabledProtocols(new String[] {"TLSv1.2"});
DefaultIoFilterChainBuilder filters = acceptor.getFilterChain();
filters.addLast("ssl", sslFilter);
filters.addLast("text", new ProtocolCodecFilter(new TextLineCodecFactory()));
acceptor.setHandler(new IoHandlerAdapter() {
@Override
public void sessionOpened(IoSession session) {
session.write("acceptor write");
}
@Override
public void event(IoSession session, FilterEvent event) {
if (event == SslEvent.SECURED) {
handshakeDone.countDown();
}
}
});
acceptor.bind(new InetSocketAddress(port));
}
private void startConnector(SSLContext sslContext, final String sni) {
NioSocketConnector connector = new NioSocketConnector();
SslFilter sslFilter = new SslFilter(sslContext) {
@Override
public void onPreAdd(IoFilterChain parent, String name, NextFilter nextFilter) throws SSLException {
if (sni != null) {
IoSession session = parent.getSession();
session.setAttribute(SslFilter.PEER_ADDRESS, new InetSocketAddress(sni, port));
}
super.onPreAdd(parent, name, nextFilter);
}
};
sslFilter.setUseClientMode(true);
sslFilter.setEndpointIdentificationAlgorithm("HTTPS");
sslFilter.setEnabledProtocols(new String[] {"TLSv1.2"});
DefaultIoFilterChainBuilder filters = connector.getFilterChain();
filters.addLast("ssl", sslFilter);
filters.addLast("text", new ProtocolCodecFilter(new TextLineCodecFactory()));
connector.setHandler(new IoHandlerAdapter() {
@Override
public void sessionOpened(IoSession session) {
session.write("connector write");
}
@Override
public void event(IoSession session, FilterEvent event) {
if (event == SslEvent.SECURED) {
handshakeDone.countDown();
}
}
});
connector.connect(new InetSocketAddress("localhost", port));
}
private SSLContext createSSLContext(String keyStorePath, String trustStorePath) throws Exception {
char[] password = "password".toCharArray();
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(SslTest.class.getResourceAsStream(keyStorePath), password);
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KEY_MANAGER_FACTORY_ALGORITHM);
kmf.init(keyStore, password);
KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load(SslTest.class.getResourceAsStream(trustStorePath), password);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(KEY_MANAGER_FACTORY_ALGORITHM);
tmf.init(trustStore);
SSLContext ctx = SSLContext.getInstance("TLSv1.2");
ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
return ctx;
}
}