Repository: netty/netty
Issue: Race in SslHandler which can lead to failing handshake and also other bad side-effects

Here is the related stack:

UNKNOWN SERVER CERTIFICATE: CN=securechat.example.netty.gleamynode.net, OU=Contributors, O=The Netty Project, L=Seongnam-si, ST=Kyunggi-do, C=KR
javax.net.ssl.SSLException: Algorithm missing:  
  at com.sun.net.ssl.internal.ssl.SSLEngineImpl.changeReadCiphers(SSLEngineImpl.java:554)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.readRecord(SSLEngineImpl.java:1051)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.readNetRecord(SSLEngineImpl.java:845)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:721)
    at javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:607)
    at org.jboss.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1127)
    at org.jboss.netty.handler.ssl.SslHandler.decode(SslHandler.java:825)
    at org.jboss.netty.handler.codec.frame.FrameDecoder.callDecode(FrameDecoder.java:425)
    at org.jboss.netty.handler.codec.frame.FrameDecoder.messageReceived(FrameDecoder.java:303)
    at org.jboss.netty.channel.SimpleChannelUpstreamHandler.handleUpstream(SimpleChannelUpstreamHandler.java:70)
    at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(DefaultChannelPipeline.java:560)
    at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(DefaultChannelPipeline.java:555)
    at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:268)
    at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:255)
    at org.jboss.netty.channel.socket.oio.OioWorker.process(OioWorker.java:71)
    at org.jboss.netty.channel.socket.oio.AbstractOioWorker.run(AbstractOioWorker.java:73)
    at org.jboss.netty.channel.socket.oio.OioWorker.run(OioWorker.java:51)
    at org.jboss.netty.util.ThreadRenamingRunnable.run(ThreadRenamingRunnable.java:108)
    at org.jboss.netty.util.internal.DeadLockProofWorker$1.run(DeadLockProofWorker.java:42)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
    at java.lang.Thread.run(Thread.java:680)
Caused by: java.security.NoSuchAlgorithmException: Could not create cipher RC4/128
    at com.sun.net.ssl.internal.ssl.CipherBox.<init>(CipherBox.java:99)
    at com.sun.net.ssl.internal.ssl.CipherBox.newCipherBox(CipherBox.java:119)
    at com.sun.net.ssl.internal.ssl.CipherSuite$BulkCipher.newCipher(CipherSuite.java:369)
    at com.sun.net.ssl.internal.ssl.Handshaker.newReadCipher(Handshaker.java:410)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.changeReadCiphers(SSLEngineImpl.java:550)
    ... 21 more
Caused by: java.security.InvalidKeyException: No installed provider supports this key: (null)
    at javax.crypto.Cipher.a(DashoA13_..)
    at javax.crypto.Cipher.init(DashoA13_..)
    at javax.crypto.Cipher.init(DashoA13_..)
    at com.sun.net.ssl.internal.ssl.CipherBox.<init>(CipherBox.java:88)
    ... 25 more
javax.net.ssl.SSLException: Algorithm missing:  
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.changeReadCiphers(SSLEngineImpl.java:554)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.readRecord(SSLEngineImpl.java:1051)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.readNetRecord(SSLEngineImpl.java:845)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:721)
    at javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:607)
    at org.jboss.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1127)
    at org.jboss.netty.handler.ssl.SslHandler.decode(SslHandler.java:825)
    at org.jboss.netty.handler.codec.frame.FrameDecoder.callDecode(FrameDecoder.java:425)
    at org.jboss.netty.handler.codec.frame.FrameDecoder.messageReceived(FrameDecoder.java:303)
    at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:268)
    at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:255)
    at org.jboss.netty.channel.socket.oio.OioWorker.process(OioWorker.java:71)
    at org.jboss.netty.channel.socket.oio.AbstractOioWorker.run(AbstractOioWorker.java:73)
    at org.jboss.netty.channel.socket.oio.OioWorker.run(OioWorker.java:51)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
    at java.lang.Thread.run(Thread.java:680)
Caused by: java.security.NoSuchAlgorithmException: Could not create cipher RC4/128
    at com.sun.net.ssl.internal.ssl.CipherBox.<init>(CipherBox.java:99)
    at com.sun.net.ssl.internal.ssl.CipherBox.newCipherBox(CipherBox.java:119)
    at com.sun.net.ssl.internal.ssl.CipherSuite$BulkCipher.newCipher(CipherSuite.java:369)
    at com.sun.net.ssl.internal.ssl.Handshaker.newReadCipher(Handshaker.java:410)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.changeReadCiphers(SSLEngineImpl.java:550)
    ... 16 more
Caused by: java.security.InvalidKeyException: No installed provider supports this key: (null)
    at javax.crypto.Cipher.a(DashoA13_..)
    at javax.crypto.Cipher.init(DashoA13_..)
    at javax.crypto.Cipher.init(DashoA13_..)
    at com.sun.net.ssl.internal.ssl.CipherBox.<init>(CipherBox.java:88)
    ... 20 more
javax.net.ssl.SSLException: Received fatal alert: internal_error
    at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:190)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.fatal(SSLEngineImpl.java:1467)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.fatal(SSLEngineImpl.java:1435)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.recvAlert(SSLEngineImpl.java:1601)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.readRecord(SSLEngineImpl.java:1031)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.readNetRecord(SSLEngineImpl.java:845)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:721)
    at javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:607)
    at org.jboss.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1127)
    at org.jboss.netty.handler.ssl.SslHandler.decode(SslHandler.java:825)
    at org.jboss.netty.handler.codec.frame.FrameDecoder.callDecode(FrameDecoder.java:425)
    at org.jboss.netty.handler.codec.frame.FrameDecoder.messageReceived(FrameDecoder.java:303)
    at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:268)
    at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:255)
    at org.jboss.netty.channel.socket.oio.OioWorker.process(OioWorker.java:71)
    at org.jboss.netty.channel.socket.oio.AbstractOioWorker.run(AbstractOioWorker.java:73)
    at org.jboss.netty.channel.socket.oio.OioWorker.run(OioWorker.java:51)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
    at java.lang.Thread.run(Thread.java:680)
java.nio.channels.ClosedChannelException
    at org.jboss.netty.handler.ssl.SslHandler.channelClosed(SslHandler.java:1472)
    at org.jboss.netty.channel.Channels.fireChannelClosed(Channels.java:468)
    at org.jboss.netty.channel.socket.oio.AbstractOioWorker.close(AbstractOioWorker.java:229)
    at org.jboss.netty.channel.socket.oio.AbstractOioWorker.run(AbstractOioWorker.java:98)
    at org.jboss.netty.channel.socket.oio.OioWorker.run(OioWorker.java:51)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
    at java.lang.Thread.run(Thread.java:680)
17.12.2012 09:11:26 org.jboss.netty.handler.ssl.SslHandshakeTest
SCHWERWIEGEND: Handshake failed
javax.net.ssl.SSLException: Received fatal alert: internal_error
    at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:190)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.fatal(SSLEngineImpl.java:1467)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.fatal(SSLEngineImpl.java:1435)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.recvAlert(SSLEngineImpl.java:1601)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.readRecord(SSLEngineImpl.java:1031)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.readNetRecord(SSLEngineImpl.java:845)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:721)
    at javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:607)
    at org.jboss.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1127)
    at org.jboss.netty.handler.ssl.SslHandler.decode(SslHandler.java:825)
    at org.jboss.netty.handler.codec.frame.FrameDecoder.callDecode(FrameDecoder.java:425)
    at org.jboss.netty.handler.codec.frame.FrameDecoder.messageReceived(FrameDecoder.java:303)
    at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:268)
    at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:255)
    at org.jboss.netty.channel.socket.oio.OioWorker.process(OioWorker.java:71)
    at org.jboss.netty.channel.socket.oio.AbstractOioWorker.run(AbstractOioWorker.java:73)
    at org.jboss.netty.channel.socket.oio.OioWorker.run(OioWorker.java:51)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
    at java.lang.Thread.run(Thread.java:680)
[WARNING] 
java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.codehaus.mojo.exec.ExecJavaMojo$1.run(ExecJavaMojo.java:297)
    at java.lang.Thread.run(Thread.java:680)
Caused by: javax.net.ssl.SSLException: Received fatal alert: internal_error
    at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:190)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.fatal(SSLEngineImpl.java:1467)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.fatal(SSLEngineImpl.java:1435)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.recvAlert(SSLEngineImpl.java:1601)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.readRecord(SSLEngineImpl.java:1031)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.readNetRecord(SSLEngineImpl.java:845)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:721)
    at javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:607)
    at org.jboss.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1127)
    at org.jboss.netty.handler.ssl.SslHandler.decode(SslHandler.java:825)
    at org.jboss.netty.handler.codec.frame.FrameDecoder.callDecode(FrameDecoder.java:425)
    at org.jboss.netty.handler.codec.frame.FrameDecoder.messageReceived(FrameDecoder.java:303)
    at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:268)
    at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:255)
    at org.jboss.netty.channel.socket.oio.OioWorker.process(OioWorker.java:71)
    at org.jboss.netty.channel.socket.oio.AbstractOioWorker.run(AbstractOioWorker.java:73)
    at org.jboss.netty.channel.socket.oio.OioWorker.run(OioWorker.java:51)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
    ... 1 more


Issue comments:
This was first discovered by a hornetq user. For more details:

https://community.jboss.org/thread/214134?start=15&tstart=0

Pull request #830 was merged in to fix it...

The same issue was observed in 3.6.6 Release. Stacktrace is following:
 Exception caught in channel [id: 0x363a30c8, /192.168.1.69:55489 => /192.168.1.32:5012]. Closing channel.
javax.net.ssl.SSLException: Algorithm missing:  
        at com.sun.net.ssl.internal.ssl.SSLEngineImpl.changeReadCiphers(SSLEngineImpl.java:554) ~[na:1.6]
        at com.sun.net.ssl.internal.ssl.SSLEngineImpl.readRecord(SSLEngineImpl.java:1051) ~[na:1.6]
        at com.sun.net.ssl.internal.ssl.SSLEngineImpl.readNetRecord(SSLEngineImpl.java:845) ~[na:1.6]
        at com.sun.net.ssl.internal.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:721) ~[na:1.6]
        at javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:607) ~[na:1.6]
        at org.jboss.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1225) ~[netty-3.6.6.Final.jar:na]
        at org.jboss.netty.handler.ssl.SslHandler.decode(SslHandler.java:913) ~[netty-3.6.6.Final.jar:na]
        at org.jboss.netty.handler.codec.frame.FrameDecoder.callDecode(FrameDecoder.java:425) ~[netty-3.6.6.Final.jar:na]
        at org.jboss.netty.handler.codec.frame.FrameDecoder.messageReceived(FrameDecoder.java:303) ~[netty-3.6.6.Final.jar:na]
        at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:268) ~[netty-3.6.6.Final.jar:na]
        at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:255) ~[netty-3.6.6.Final.jar:na]
        at org.jboss.netty.channel.socket.nio.NioWorker.read(NioWorker.java:88) ~[netty-3.6.6.Final.jar:na]
        at org.jboss.netty.channel.socket.nio.AbstractNioWorker.process(AbstractNioWorker.java:109) ~[netty-3.6.6.Final.jar:na]
        at org.jboss.netty.channel.socket.nio.AbstractNioSelector.run(AbstractNioSelector.java:312) ~[netty-3.6.6.Final.jar:na]
        at org.jboss.netty.channel.socket.nio.AbstractNioWorker.run(AbstractNioWorker.java:90) ~[netty-3.6.6.Final.jar:na]
        at org.jboss.netty.channel.socket.nio.NioWorker.run(NioWorker.java:178) ~[netty-3.6.6.Final.jar:na]
        at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886) [na:1.6.0_32]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908) [na:1.6.0_32]
        at java.lang.Thread.run(Thread.java:662) [na:1.6.0_32]
Caused by: java.security.NoSuchAlgorithmException: Could not create cipher RC4/128
        at com.sun.net.ssl.internal.ssl.CipherBox.<init>(CipherBox.java:99) ~[na:1.6]
        at com.sun.net.ssl.internal.ssl.CipherBox.newCipherBox(CipherBox.java:119) ~[na:1.6]
        at com.sun.net.ssl.internal.ssl.CipherSuite$BulkCipher.newCipher(CipherSuite.java:369) ~[na:1.6]
        at com.sun.net.ssl.internal.ssl.Handshaker.newReadCipher(Handshaker.java:410) ~[na:1.6]
        at com.sun.net.ssl.internal.ssl.SSLEngineImpl.changeReadCiphers(SSLEngineImpl.java:550) ~[na:1.6]
        ... 18 common frames omitted
Caused by: java.security.InvalidKeyException: No installed provider supports this key: (null)
        at javax.crypto.Cipher.a(DashoA13_..) ~[na:na]
        at javax.crypto.Cipher.init(DashoA13_..) ~[na:na]
        at javax.crypto.Cipher.init(DashoA13*..) ~[na:na]
        at com.sun.net.ssl.internal.ssl.CipherBox.<init>(CipherBox.java:88) ~[na:1.6]
        ... 22 common frames omitted

Should I provide some info?


Pull request review comments:

Patch that resolved the issue:
diff --git a/src/main/java/org/jboss/netty/handler/ssl/SslHandler.java b/src/main/java/org/jboss/netty/handler/ssl/SslHandler.java
index de66e562a71..2e7e494a726 100644
--- a/src/main/java/org/jboss/netty/handler/ssl/SslHandler.java
+++ b/src/main/java/org/jboss/netty/handler/ssl/SslHandler.java
@@ -54,7 +54,6 @@
 import java.util.regex.Pattern;
 
 import static org.jboss.netty.channel.Channels.*;
-import static org.jboss.netty.channel.Channels.fireExceptionCaught;
 
 /**
  * Adds <a href="http://en.wikipedia.org/wiki/Transport_Layer_Security">SSL
@@ -870,76 +869,76 @@ private void wrap(ChannelHandlerContext context, Channel channel)
                                         channel.getRemoteAddress()));
                         offered = true;
                     } else {
-                        SSLEngineResult result = null;
-                        try {
-                            synchronized (handshakeLock) {
+                        synchronized (handshakeLock) {
+                            SSLEngineResult result = null;
+                            try {
                                 result = engine.wrap(outAppBuf, outNetBuf);
-                            }
-                        } finally {
-                            if (!outAppBuf.hasRemaining()) {
-                                pendingUnencryptedWrites.remove();
-                            }
-                        }
-
-                        if (result.bytesProduced() > 0) {
-                            outNetBuf.flip();
-                            int remaining = outNetBuf.remaining();
-                            msg = ctx.getChannel().getConfig().getBufferFactory().getBuffer(remaining);
-
-                            // Transfer the bytes to the new ChannelBuffer using some safe method that will also
-                            // work with "non" heap buffers
-                            //
-                            // See https://github.com/netty/netty/issues/329
-                            msg.writeBytes(outNetBuf);
-                            outNetBuf.clear();
-
-                            ChannelFuture future;
-                            if (pendingWrite.outAppBuf.hasRemaining()) {
-                                // pendingWrite's future shouldn't be notified if
-                                // only partial data is written.
-                                future = succeededFuture(channel);
-                            } else {
-                                future = pendingWrite.future;
+                            } finally {
+                                if (!outAppBuf.hasRemaining()) {
+                                    pendingUnencryptedWrites.remove();
+                                }
                             }
 
-                            MessageEvent encryptedWrite = new DownstreamMessageEvent(
-                                    channel, future, msg, channel.getRemoteAddress());
-                            offerEncryptedWriteRequest(encryptedWrite);
-                            offered = true;
-                        } else if (result.getStatus() == Status.CLOSED) {
-                            // SSLEngine has been closed already.
-                            // Any further write attempts should be denied.
-                            success = false;
-                            break;
-                        } else {
-                            final HandshakeStatus handshakeStatus = result.getHandshakeStatus();
-                            handleRenegotiation(handshakeStatus);
-                            switch (handshakeStatus) {
-                            case NEED_WRAP:
-                                if (outAppBuf.hasRemaining()) {
-                                    break;
+                            if (result.bytesProduced() > 0) {
+                                outNetBuf.flip();
+                                int remaining = outNetBuf.remaining();
+                                msg = ctx.getChannel().getConfig().getBufferFactory().getBuffer(remaining);
+
+                                // Transfer the bytes to the new ChannelBuffer using some safe method that will also
+                                // work with "non" heap buffers
+                                //
+                                // See https://github.com/netty/netty/issues/329
+                                msg.writeBytes(outNetBuf);
+                                outNetBuf.clear();
+
+                                ChannelFuture future;
+                                if (pendingWrite.outAppBuf.hasRemaining()) {
+                                    // pendingWrite's future shouldn't be notified if
+                                    // only partial data is written.
+                                    future = succeededFuture(channel);
                                 } else {
-                                    break loop;
+                                    future = pendingWrite.future;
                                 }
-                            case NEED_UNWRAP:
-                                needsUnwrap = true;
-                                break loop;
-                            case NEED_TASK:
-                                runDelegatedTasks();
+
+                                MessageEvent encryptedWrite = new DownstreamMessageEvent(
+                                        channel, future, msg, channel.getRemoteAddress());
+                                offerEncryptedWriteRequest(encryptedWrite);
+                                offered = true;
+                            } else if (result.getStatus() == Status.CLOSED) {
+                                // SSLEngine has been closed already.
+                                // Any further write attempts should be denied.
+                                success = false;
                                 break;
-                            case FINISHED:
-                            case NOT_HANDSHAKING:
-                                if (handshakeStatus == HandshakeStatus.FINISHED) {
-                                    setHandshakeSuccess(channel);
-                                }
-                                if (result.getStatus() == Status.CLOSED) {
-                                    success = false;
+                            } else {
+                                final HandshakeStatus handshakeStatus = result.getHandshakeStatus();
+                                handleRenegotiation(handshakeStatus);
+                                switch (handshakeStatus) {
+                                case NEED_WRAP:
+                                    if (outAppBuf.hasRemaining()) {
+                                        break;
+                                    } else {
+                                        break loop;
+                                    }
+                                case NEED_UNWRAP:
+                                    needsUnwrap = true;
+                                    break loop;
+                                case NEED_TASK:
+                                    runDelegatedTasks();
+                                    break;
+                                case FINISHED:
+                                case NOT_HANDSHAKING:
+                                    if (handshakeStatus == HandshakeStatus.FINISHED) {
+                                        setHandshakeSuccess(channel);
+                                    }
+                                    if (result.getStatus() == Status.CLOSED) {
+                                        success = false;
+                                    }
+                                    break loop;
+                                default:
+                                    throw new IllegalStateException(
+                                            "Unknown handshake status: " +
+                                            handshakeStatus);
                                 }
-                                break loop;
-                            default:
-                                throw new IllegalStateException(
-                                        "Unknown handshake status: " +
-                                        handshakeStatus);
                             }
                         }
                     }
@@ -1125,56 +1124,55 @@ private ChannelBuffer unwrap(
 
                 synchronized (handshakeLock) {
                     result = engine.unwrap(inNetBuf, outAppBuf);
-                }
 
-                // notify about the CLOSED state of the SSLEngine. See #137
-                if (result.getStatus() == Status.CLOSED) {
-                    sslEngineCloseFuture.setClosed();
-                }
+                    // notify about the CLOSED state of the SSLEngine. See #137
+                    if (result.getStatus() == Status.CLOSED) {
+                        sslEngineCloseFuture.setClosed();
+                    }
 
-                final HandshakeStatus handshakeStatus = result.getHandshakeStatus();
-                handleRenegotiation(handshakeStatus);
-                switch (handshakeStatus) {
-                case NEED_UNWRAP:
-                    if (inNetBuf.hasRemaining() && !engine.isInboundDone()) {
+                    final HandshakeStatus handshakeStatus = result.getHandshakeStatus();
+                    handleRenegotiation(handshakeStatus);
+                    switch (handshakeStatus) {
+                    case NEED_UNWRAP:
+                        if (inNetBuf.hasRemaining() && !engine.isInboundDone()) {
+                            break;
+                        } else {
+                            break loop;
+                        }
+                    case NEED_WRAP:
+                        wrapNonAppData(ctx, channel);
                         break;
-                    } else {
+                    case NEED_TASK:
+                        runDelegatedTasks();
+                        break;
+                    case FINISHED:
+                        setHandshakeSuccess(channel);
+                        needsWrap = true;
                         break loop;
+                    case NOT_HANDSHAKING:
+                        needsWrap = true;
+                        break loop;
+                    default:
+                        throw new IllegalStateException(
+                                "Unknown handshake status: " + handshakeStatus);
                     }
-                case NEED_WRAP:
-                    wrapNonAppData(ctx, channel);
-                    break;
-                case NEED_TASK:
-                    runDelegatedTasks();
-                    break;
-                case FINISHED:
-                    setHandshakeSuccess(channel);
-                    needsWrap = true;
-                    break loop;
-                case NOT_HANDSHAKING:
-                    needsWrap = true;
-                    break loop;
-                default:
-                    throw new IllegalStateException(
-                            "Unknown handshake status: " + handshakeStatus);
                 }
-            }
 
-            if (needsWrap) {
-                // wrap() acquires pendingUnencryptedWrites first and then
-                // handshakeLock.  If handshakeLock is already hold by the
-                // current thread, calling wrap() will lead to a dead lock
-                // i.e. pendingUnencryptedWrites -> handshakeLock vs.
-                //      handshakeLock -> pendingUnencryptedLock -> handshakeLock
-                //
-                // There is also a same issue between pendingEncryptedWrites
-                // and pendingUnencryptedWrites.
-                if (!Thread.holdsLock(handshakeLock) &&
-                    !pendingEncryptedWritesLock.isHeldByCurrentThread()) {
-                    wrap(ctx, channel);
+                if (needsWrap) {
+                    // wrap() acquires pendingUnencryptedWrites first and then
+                    // handshakeLock.  If handshakeLock is already hold by the
+                    // current thread, calling wrap() will lead to a dead lock
+                    // i.e. pendingUnencryptedWrites -> handshakeLock vs.
+                    //      handshakeLock -> pendingUnencryptedLock -> handshakeLock
+                    //
+                    // There is also a same issue between pendingEncryptedWrites
+                    // and pendingUnencryptedWrites.
+                    if (!Thread.holdsLock(handshakeLock) &&
+                        !pendingEncryptedWritesLock.isHeldByCurrentThread()) {
+                        wrap(ctx, channel);
+                    }
                 }
             }
-
             outAppBuf.flip();
 
             if (outAppBuf.hasRemaining()) {


Test patch:
diff --git a/src/test/java/org/jboss/netty/handler/ssl/SslHandshakeRaceTester.java b/src/test/java/org/jboss/netty/handler/ssl/SslHandshakeRaceTester.java
new file mode 100644
index 00000000000..fdf99d897e6
--- /dev/null
+++ b/src/test/java/org/jboss/netty/handler/ssl/SslHandshakeRaceTester.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jboss.netty.handler.ssl;
+
+import org.jboss.netty.bootstrap.ClientBootstrap;
+import org.jboss.netty.bootstrap.ServerBootstrap;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelFuture;
+import org.jboss.netty.channel.ChannelFutureListener;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelPipeline;
+import org.jboss.netty.channel.ChannelPipelineFactory;
+import org.jboss.netty.channel.ChannelStateEvent;
+import org.jboss.netty.channel.Channels;
+import org.jboss.netty.channel.ExceptionEvent;
+import org.jboss.netty.channel.MessageEvent;
+import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
+import org.jboss.netty.channel.socket.ClientSocketChannelFactory;
+import org.jboss.netty.channel.socket.ServerSocketChannelFactory;
+import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
+import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
+import org.jboss.netty.channel.socket.oio.OioServerSocketChannelFactory;
+import org.jboss.netty.channel.socket.oio.OioClientSocketChannelFactory;
+import org.jboss.netty.example.securechat.SecureChatSslContextFactory;
+
+import javax.net.ssl.SSLEngine;
+import java.net.InetSocketAddress;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class SslHandshakeRaceTester {
+
+    private static final Random random = new Random();
+    static final byte[] data = new byte[1048576];
+    private int count;
+
+    static {
+        random.nextBytes(data);
+    }
+
+    public void run(int rounds, boolean nio) throws Throwable {
+        ClientSocketChannelFactory clientFactory;
+        if (nio) {
+            clientFactory = new NioClientSocketChannelFactory();
+        } else {
+            clientFactory = new OioClientSocketChannelFactory();
+        }
+        ClientBootstrap cb = new ClientBootstrap(clientFactory);
+        cb.setPipelineFactory(new ChannelPipelineFactory() {
+            public ChannelPipeline getPipeline() throws Exception {
+                ChannelPipeline cp = Channels.pipeline();
+
+                SSLEngine cse = SecureChatSslContextFactory.getClientContext().createSSLEngine();
+                cse.setUseClientMode(true);
+                cp.addFirst("ssl", new SslHandler(cse));
+
+                cp.addLast("handler", new TestHandler());
+                return cp;
+            }
+        });
+
+        ServerSocketChannelFactory serverFactory;
+        if (nio) {
+            serverFactory = new NioServerSocketChannelFactory();
+        } else {
+            serverFactory = new OioServerSocketChannelFactory();
+        }
+        ServerBootstrap sb = new ServerBootstrap(serverFactory);
+
+        sb.setPipelineFactory(new ChannelPipelineFactory() {
+            public ChannelPipeline getPipeline() throws Exception {
+                ChannelPipeline cp = Channels.pipeline();
+
+                cp.addFirst("counter", new SimpleChannelUpstreamHandler() {
+                    @Override
+                    public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
+                        ctx.getPipeline().get(SslHandler.class).handshake().addListener(new ChannelFutureListener() {
+                            public void operationComplete(ChannelFuture future) throws Exception {
+                                if (!future.isSuccess()) {
+                                    future.getCause().printStackTrace();
+                                    future.getChannel().close();
+                                }
+                            }
+                        });
+
+                        ++count;
+                        System.out.println("Connection #" + count);
+                    }
+                });
+
+                SSLEngine sse = SecureChatSslContextFactory.getServerContext().createSSLEngine();
+                sse.setUseClientMode(false);
+                cp.addFirst("ssl", new SslHandler(sse));
+
+                cp.addLast("handler",  new TestHandler());
+                return cp;
+            }
+        });
+
+        Channel sc = sb.bind(new InetSocketAddress(0));
+        int port = ((InetSocketAddress) sc.getLocalAddress()).getPort();
+
+        for (int i = 0; i < rounds; i++) {
+            connectAndSend(cb, port);
+        }
+
+        cb.shutdown();
+        cb.releaseExternalResources();
+        sc.close().awaitUninterruptibly();
+        sb.shutdown();
+        sb.releaseExternalResources();
+    }
+
+    private static void connectAndSend(ClientBootstrap cb, int port) throws Throwable {
+        ChannelFuture ccf = cb.connect(new InetSocketAddress("127.0.0.1", port));
+        ccf.awaitUninterruptibly();
+        if (!ccf.isSuccess()) {
+            ccf.getCause().printStackTrace();
+            throw ccf.getCause();
+        }
+        TestHandler ch = ccf.getChannel().getPipeline().get(TestHandler.class);
+
+        Channel cc = ccf.getChannel();
+        ChannelFuture hf = cc.getPipeline().get(SslHandler.class).handshake();
+        hf.awaitUninterruptibly();
+        if (!hf.isSuccess()) {
+            hf.getCause().printStackTrace();
+            ch.channel.close();
+            throw hf.getCause();
+        }
+
+        for (int i = 0; i < data.length;) {
+            int length = Math.min(random.nextInt(1024 * 64), data.length - i);
+            ChannelFuture future = cc.write(ChannelBuffers.wrappedBuffer(data, i, length));
+            i += length;
+            if (i >= data.length) {
+                future.awaitUninterruptibly();
+            }
+        }
+
+        ch.channel.close().awaitUninterruptibly();
+
+        if (ch.exception.get() != null) {
+            throw ch.exception.get();
+        }
+    }
+
+    private static class TestHandler extends SimpleChannelUpstreamHandler {
+        volatile Channel channel;
+        final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
+
+        @Override
+        public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e)
+                throws Exception {
+            channel = e.getChannel();
+        }
+
+        @Override
+        public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
+                throws Exception {
+        }
+
+        @Override
+        public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
+                throws Exception {
+            e.getCause().printStackTrace();
+
+            exception.compareAndSet(null, e.getCause());
+            e.getChannel().close();
+        }
+    }
+
+    public static void main(String[] args) throws Throwable {
+        int count = 20000;
+        boolean nio = false;
+        if (args.length == 2) {
+            count = Integer.parseInt(args[0]);
+            nio = Boolean.parseBoolean(args[1]);
+        }
+        SslHandshakeRaceTester test = new SslHandshakeRaceTester();
+        test.run(count, nio);
+    }
+}