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/04/30 03:53:11 UTC

[mina] branch 2.1.X updated: DIRMINA-1122 - endpoint identification algorithm support

This is an automated email from the ASF dual-hosted git repository.

elecharny pushed a commit to branch 2.1.X
in repository https://gitbox.apache.org/repos/asf/mina.git


The following commit(s) were added to refs/heads/2.1.X by this push:
     new 2d113927f DIRMINA-1122 - endpoint identification algorithm support
     new 81edb257b Merge pull request #26 from the-thing/DIRMINA-1122/endoint-identification-support
2d113927f is described below

commit 2d113927f7b46eaed351b8127604df8c66ff833b
Author: Marcin <ma...@gmail.com>
AuthorDate: Mon Apr 20 14:15:00 2020 +0100

    DIRMINA-1122 - endpoint identification algorithm support
---
 .../java/org/apache/mina/filter/ssl/SslFilter.java |  20 ++
 .../org/apache/mina/filter/ssl/SslHandler.java     |  10 +-
 .../filter/ssl/SslIdentificationAlgorithmTest.java | 244 +++++++++++++++++++++
 .../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, 272 insertions(+), 2 deletions(-)

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 45d124ef4..61de659dd 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
@@ -156,6 +156,8 @@ public class SslFilter extends IoFilterAdapter {
 
     private boolean wantClientAuth;
 
+    private String identificationAlgorithm;
+
     private String[] enabledCipherSuites;
 
     private String[] enabledProtocols;
@@ -385,6 +387,24 @@ public class SslFilter extends IoFilterAdapter {
         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 71ace8238..86eb4c4ff 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
@@ -23,8 +23,6 @@ import java.net.InetSocketAddress;
 import java.nio.ByteBuffer;
 import java.util.Queue;
 import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.locks.ReentrantLock;
 
 import javax.net.ssl.SSLEngine;
 import javax.net.ssl.SSLEngineResult;
@@ -32,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;
@@ -179,6 +178,13 @@ 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..e97a50e86
--- /dev/null
+++ b/mina-core/src/test/java/org/apache/mina/filter/ssl/SslIdentificationAlgorithmTest.java
@@ -0,0 +1,244 @@
+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
+     */
+    @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"});
+
+        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"});
+
+        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("TLS");
+        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