You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by el...@apache.org on 2023/06/01 07:34:26 UTC
[mina] branch 2.0.X updated: Pushed patch from DIRMINA-1122
This is an automated email from the ASF dual-hosted git repository.
elecharny pushed a commit to branch 2.0.X
in repository https://gitbox.apache.org/repos/asf/mina.git
The following commit(s) were added to refs/heads/2.0.X by this push:
new bd015c21d Pushed patch from DIRMINA-1122
bd015c21d is described below
commit bd015c21d50c71e3de37d5c7a0a53b0c7230f27f
Author: emmanuel lecharny <el...@apache.org>
AuthorDate: Thu Jun 1 09:34:16 2023 +0200
Pushed patch from DIRMINA-1122
---
.../java/org/apache/mina/filter/ssl/SslFilter.java | 21 ++
.../org/apache/mina/filter/ssl/SslHandler.java | 9 +
.../filter/ssl/SslIdentificationAlgorithmTest.java | 255 +++++++++++++++++++++
.../apache/mina/filter/ssl/client-cn.truststore | Bin 0 -> 951 bytes
.../mina/filter/ssl/client-san-ext.truststore | Bin 0 -> 986 bytes
.../org/apache/mina/filter/ssl/server-cn.keystore | Bin 0 -> 2240 bytes
.../apache/mina/filter/ssl/server-san-ext.keystore | Bin 0 -> 2276 bytes
7 files changed, 285 insertions(+)
diff --git a/mina-core/src/main/java/org/apache/mina/filter/ssl/SslFilter.java b/mina-core/src/main/java/org/apache/mina/filter/ssl/SslFilter.java
index dde0e6416..17a1eeb9a 100644
--- a/mina-core/src/main/java/org/apache/mina/filter/ssl/SslFilter.java
+++ b/mina-core/src/main/java/org/apache/mina/filter/ssl/SslFilter.java
@@ -171,6 +171,8 @@ public class SslFilter extends IoFilterAdapter {
private boolean wantClientAuth;
+ private String identificationAlgorithm;
+
private String[] enabledCipherSuites;
private String[] enabledProtocols;
@@ -401,7 +403,26 @@ public class SslFilter extends IoFilterAdapter {
public void setWantClientAuth(boolean wantClientAuth) {
this.wantClientAuth = wantClientAuth;
}
+
+ /**
+ * @return the endpoint identification algorithm to be used when {@link SSLEngine}
+ * is initialized. <tt>null</tt> means 'use {@link SSLEngine}'s default.'
+ */
+ public String getEndpointIdentificationAlgorithm() {
+ return identificationAlgorithm;
+ }
+
+ /**
+ * Sets the endpoint identification algorithm to be used when {@link SSLEngine}
+ * is initialized.
+ *
+ * @param identificationAlgorithm <tt>null</tt> means 'use {@link SSLEngine}'s default.'
+ */
+ public void setEndpointIdentificationAlgorithm(String identificationAlgorithm) {
+ this.identificationAlgorithm = identificationAlgorithm;
+ }
+
/**
* @return the list of cipher suites to be enabled when {@link SSLEngine}
* is initialized. <tt>null</tt> means 'use {@link SSLEngine}'s default.'
diff --git a/mina-core/src/main/java/org/apache/mina/filter/ssl/SslHandler.java b/mina-core/src/main/java/org/apache/mina/filter/ssl/SslHandler.java
index 04640debb..3066086b8 100644
--- a/mina-core/src/main/java/org/apache/mina/filter/ssl/SslHandler.java
+++ b/mina-core/src/main/java/org/apache/mina/filter/ssl/SslHandler.java
@@ -30,6 +30,7 @@ import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLEngineResult.Status;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLParameters;
import org.apache.mina.core.RuntimeIoException;
import org.apache.mina.core.buffer.IoBuffer;
@@ -177,6 +178,14 @@ class SslHandler {
sslEngine.setEnabledProtocols(sslFilter.getEnabledProtocols());
}
+ // Set the endpoint identification algorithm
+ if (sslFilter.getEndpointIdentificationAlgorithm() != null) {
+ SSLParameters sslParameters = sslEngine.getSSLParameters();
+ sslParameters.setEndpointIdentificationAlgorithm(sslFilter.getEndpointIdentificationAlgorithm());
+ sslEngine.setSSLParameters(sslParameters);
+ }
+
+
// TODO : we may not need to call this method...
// However, if we don't call it here, the tests are failing. Why?
handshakeStatus = sslEngine.getHandshakeStatus();
diff --git a/mina-core/src/test/java/org/apache/mina/filter/ssl/SslIdentificationAlgorithmTest.java b/mina-core/src/test/java/org/apache/mina/filter/ssl/SslIdentificationAlgorithmTest.java
new file mode 100644
index 000000000..20270e6fe
--- /dev/null
+++ b/mina-core/src/test/java/org/apache/mina/filter/ssl/SslIdentificationAlgorithmTest.java
@@ -0,0 +1,255 @@
+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.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
+ */
+ @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 sessionCreated(IoSession session) throws Exception {
+ // Add the SSL notification in the session's attribute liste
+ session.setAttribute(SslFilter.USE_NOTIFICATION, Boolean.TRUE);
+ }
+
+ @Override
+ public void sessionOpened(IoSession session) {
+ session.write("acceptor write");
+ }
+
+ @Override
+ public void messageReceived(IoSession session, Object message) throws Exception {
+ // Check if the 'fake' session secured message notification has been received
+ if (message == SslFilter.SESSION_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 sessionCreated(IoSession session) throws Exception {
+ // Add the SSL notification in the session's attribute liste
+ session.setAttribute(SslFilter.USE_NOTIFICATION, Boolean.TRUE);
+ }
+
+ @Override
+ public void sessionOpened(IoSession session) {
+ session.write("connector write");
+ }
+
+ @Override
+ public void messageReceived(IoSession session, Object message) throws Exception {
+ // Check if the 'fake' session secured message notification has been received
+ if (message == SslFilter.SESSION_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;
+ }
+}
diff --git a/mina-core/src/test/resources/org/apache/mina/filter/ssl/client-cn.truststore b/mina-core/src/test/resources/org/apache/mina/filter/ssl/client-cn.truststore
new file mode 100644
index 000000000..b9d3be86c
Binary files /dev/null and b/mina-core/src/test/resources/org/apache/mina/filter/ssl/client-cn.truststore differ
diff --git a/mina-core/src/test/resources/org/apache/mina/filter/ssl/client-san-ext.truststore b/mina-core/src/test/resources/org/apache/mina/filter/ssl/client-san-ext.truststore
new file mode 100644
index 000000000..d6495dc1c
Binary files /dev/null and b/mina-core/src/test/resources/org/apache/mina/filter/ssl/client-san-ext.truststore differ
diff --git a/mina-core/src/test/resources/org/apache/mina/filter/ssl/server-cn.keystore b/mina-core/src/test/resources/org/apache/mina/filter/ssl/server-cn.keystore
new file mode 100644
index 000000000..ffb935eaa
Binary files /dev/null and b/mina-core/src/test/resources/org/apache/mina/filter/ssl/server-cn.keystore differ
diff --git a/mina-core/src/test/resources/org/apache/mina/filter/ssl/server-san-ext.keystore b/mina-core/src/test/resources/org/apache/mina/filter/ssl/server-san-ext.keystore
new file mode 100644
index 000000000..600ef3a7a
Binary files /dev/null and b/mina-core/src/test/resources/org/apache/mina/filter/ssl/server-san-ext.keystore differ