When a mina 2.1.X server (presumably also 2.0.X) is used with the BouncyCastle JSSE provider ("BCJSSE" - I am a BC dev), then it may get into an infinite loop if a connected client requests renegotiation (by sending a new ClientHello) and BCJSSE (default behaviour) rejects that by responding with a no_renegotiation alert.

The infinite loop happens in the 'SslHandler.handshake' method (via 'renegotiateIfNeeded'). The (renegotiation) ClientHello is unwrapped and the server SSLEngine then signals NEED_WRAP because it has an alert to send. 'renegotiateIfNeeded' then calls through to 'handshake' and the alert is wrapped, with the SSLEngine returning NOT_HANDSHAKING. This ought to be an appropriate time to exit the switch loop, but it appears this loop requires getting a FINISHED status, which will never happen of course since no new handshake was initiated.

Simply returning from 'handshake' when NOT_HANDSHAKING is reached seems to cause other issues, so presumably the handling of NOT_HANDSHAKING in the same way as NEED_WRAP is a workaround best left as is. Therefore I propose a more targeted fix that returns once a wrap call returns NOT_HANDSHAKING and without any progress (no bytes consumed or produced). The exit condition could be made even more specific by requiring the previous status to also have been NOT_HANDSHAKING, if that is preferable.

The attached patch includes a reproducer unit test (in SslTest) and the proposed fix in SslHandler.handshake() method.

Regards,
Pete Dettman
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 aaae15066..2a3390a0f 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
@@ -654,6 +654,17 @@ class SslHandler {
                 outNetBuffer.flip();
                 handshakeStatus = result.getHandshakeStatus();
                 writeNetBuffer(nextFilter);
+
+                /*
+                 * Avoid an infinite loop if we are NOT_HANDSHAKING and wrap 
made no progress.
+                 */
+                if (HandshakeStatus.NOT_HANDSHAKING == handshakeStatus &&
+                    result.bytesConsumed() == 0 &&
+                    result.bytesProduced() == 0)
+                {
+                    return;
+                }
+
                 break;
 
             default:
diff --git a/mina-core/src/test/java/org/apache/mina/filter/ssl/SslTest.java 
b/mina-core/src/test/java/org/apache/mina/filter/ssl/SslTest.java
index e61bad654..c4616d24d 100644
--- a/mina-core/src/test/java/org/apache/mina/filter/ssl/SslTest.java
+++ b/mina-core/src/test/java/org/apache/mina/filter/ssl/SslTest.java
@@ -31,6 +31,7 @@ import java.security.Security;
 
 import javax.net.ssl.KeyManagerFactory;
 import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
 import javax.net.ssl.SSLSocketFactory;
 import javax.net.ssl.TrustManagerFactory;
 
@@ -41,6 +42,8 @@ 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.util.AvailablePortFinder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
 import org.junit.Test;
 
 /**
@@ -116,6 +119,24 @@ public class SslTest {
         acceptor.bind(new InetSocketAddress(port));
     }
     
+    private static void startServerBCJSSE() throws Exception {
+        acceptor = new NioSocketAcceptor();
+
+        acceptor.setReuseAddress(true);
+        DefaultIoFilterChainBuilder filters = acceptor.getFilterChain();
+
+        // Inject the SSL filter
+        SslFilter sslFilter = new SslFilter(createSSLContextBCJSSE());
+        filters.addLast("sslFilter", sslFilter);
+        sslFilter.setNeedClientAuth(true);
+
+        // Inject the TestLine codec filter
+        filters.addLast("text", new ProtocolCodecFilter(new 
TextLineCodecFactory()));
+
+        acceptor.setHandler(new TestHandler());
+        acceptor.bind(new InetSocketAddress(port));
+    }
+
     private static void stopServer() {
         acceptor.dispose();
     }
@@ -135,6 +156,29 @@ public class SslTest {
         connectAndSend();
     }
 
+    /**
+     * Starts a client which will connect using SSL and then attempt 
renegotiation
+     */
+    private static void startClientReneg() throws Exception {
+        address = InetAddress.getByName("localhost");
+
+        SSLContext context = createSSLContext();
+        factory = context.getSocketFactory();
+
+        connectAndRenegotiate();
+    }
+
+    private static void connectAndRenegotiate() throws Exception {
+        Socket parent = new Socket(address, port);
+        SSLSocket socket = (SSLSocket)factory.createSocket(parent, 
address.getCanonicalHostName(), port, false);
+
+        socket.setEnabledProtocols(new String[]{ "TLSv1.2" });
+        socket.startHandshake();
+
+        // Attempt renegotiation on the connection
+        socket.startHandshake();
+    }
+
     private static void connectAndSend() throws Exception {
         Socket parent = new Socket(address, port);
         Socket socket = factory.createSocket(parent, 
address.getCanonicalHostName(), port, false);
@@ -174,6 +218,28 @@ public class SslTest {
         return ctx;
     }
 
+    private static SSLContext createSSLContextBCJSSE() throws IOException, 
GeneralSecurityException {
+        char[] passphrase = "password".toCharArray();
+
+        SSLContext ctx = SSLContext.getInstance("TLS", "BCJSSE");
+//        KeyManagerFactory kmf = 
KeyManagerFactory.getInstance(KEY_MANAGER_FACTORY_ALGORITHM);
+//        TrustManagerFactory tmf = 
TrustManagerFactory.getInstance(KEY_MANAGER_FACTORY_ALGORITHM);
+        KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX", 
"BCJSSE");
+        TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX", 
"BCJSSE");
+
+        KeyStore ks = KeyStore.getInstance("JKS");
+        KeyStore ts = KeyStore.getInstance("JKS");
+
+        ks.load(SslTest.class.getResourceAsStream("keystore.sslTest"), 
passphrase);
+        ts.load(SslTest.class.getResourceAsStream("truststore.sslTest"), 
passphrase);
+
+        kmf.init(ks, passphrase);
+        tmf.init(ts);
+        ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+
+        return ctx;
+    }
+
     @Test
     public void testSSL() throws Exception {
         try {
@@ -200,6 +266,48 @@ public class SslTest {
     }
     
     
+    @Test
+    public void testSSLClientRenegRejected() throws Exception {
+        boolean missingBC = Security.getProvider("BC") == null;
+        boolean missingBCJSSE = Security.getProvider("BCJSSE") == null;
+
+        if (missingBC) {
+            Security.addProvider(new BouncyCastleProvider());
+        }
+        if (missingBCJSSE) {
+            Security.addProvider(new BouncyCastleJsseProvider());
+        }
+
+        try {
+            startServerBCJSSE();
+
+            Thread t = new Thread() {
+                public void run() {
+                    try {
+                        startClientReneg();
+                    } catch (Exception e) {
+                        clientError = e;
+                    }
+                }
+            };
+            t.start();
+            t.join();
+
+            if (clientError != null) {
+                throw clientError;
+            }
+        } finally {
+            stopServer();
+
+            if (missingBC) {
+                Security.removeProvider("BC");
+            }
+            if (missingBCJSSE) {
+                Security.removeProvider("BCJSSE");
+            }
+        }
+    }
+
     @Test
     public void unsecureClientTryToConnectoToSecureServer() throws Exception {
         try {
diff --git a/pom.xml b/pom.xml
index 81154972c..89ecca016 100644
--- a/pom.xml
+++ b/pom.xml
@@ -370,6 +370,24 @@
       <artifactId>junit</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+        <groupId>org.bouncycastle</groupId>
+        <artifactId>bcprov-debug-jdk18on</artifactId>
+        <version>1.77</version>
+        <scope>test</scope>
+    </dependency>
+    <dependency>
+        <groupId>org.bouncycastle</groupId>
+        <artifactId>bctls-debug-jdk18on</artifactId>
+        <version>1.77</version>
+        <scope>test</scope>
+    </dependency>
+    <dependency>
+        <groupId>org.bouncycastle</groupId>
+        <artifactId>bcutil-debug-jdk18on</artifactId>
+        <version>1.77</version>
+        <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <profiles>
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@mina.apache.org
For additional commands, e-mail: dev-h...@mina.apache.org

Reply via email to