SSLEngine.wrap(...) returns NOT_HANDSHAKING even when the alert was not consumed yet in latest JDK12 release (possible regression).

Norman Maurer norman.maurer at googlemail.com
Wed Mar 13 12:56:50 UTC 2019


Here is the reproducer (just replace “test.p12” with some valid file that contains the keymaterial), which does not throw on JDK11 but throws on JDK12:

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.nio.ByteBuffer;
import java.security.KeyStore;
import java.security.cert.X509Certificate;

public class JDKSSLUnwrapReproducer {

    public static void main(String[] args) throws Exception {
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        keyStore.load(JDKSSLUnwrapReproducer.class.getResourceAsStream("test.p12"), "test".toCharArray());
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(keyStore, "test".toCharArray());
        SSLContext serverCtx = SSLContext.getInstance("TLS");
        serverCtx.init(kmf.getKeyManagers(), null, null);
        SSLEngine server = serverCtx.createSSLEngine();
        server.setUseClientMode(false);
        server.setEnabledProtocols(new String[] { "TLSv1.2" });


        SSLContext clientCtx = SSLContext.getInstance("TLS");
        clientCtx.init(null, new TrustManager[] {
                new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {
                        // NOOP
                    }

                    @Override
                    public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {
                        // NOOP
                    }

                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[0];
                    }
                }
        }, null);

        SSLEngine client = clientCtx.createSSLEngine();
        client.setUseClientMode(true);
        client.setEnabledProtocols(new String[] { "TLSv1.2" });

        ByteBuffer plainClientOut = ByteBuffer.allocate(client.getSession().getApplicationBufferSize());
        ByteBuffer plainServerOut = ByteBuffer.allocate(server.getSession().getApplicationBufferSize());

        ByteBuffer encryptedClientToServer = ByteBuffer.allocate(client.getSession().getPacketBufferSize());
        ByteBuffer encryptedServerToClient = ByteBuffer.allocate(server.getSession().getPacketBufferSize());
        ByteBuffer empty = ByteBuffer.allocate(0);

        handshake(client, server);

        // This will produce a close_notify
        client.closeOutbound();

        // Something still pending in the outbound buffer.
        assertFalse(client.isOutboundDone());
        assertFalse(client.isInboundDone());

        // Now wrap and so drain the outbound buffer.
        SSLEngineResult result = client.wrap(empty, encryptedClientToServer);
        encryptedClientToServer.flip();

        assertEquals(SSLEngineResult.Status.CLOSED, result.getStatus());
        // Need an UNWRAP to read the response of the close_notify
        //
        // This is NOT_HANDSHAKING for JDK 12+ !!!!
        assertEquals(SSLEngineResult.HandshakeStatus.NEED_UNWRAP, result.getHandshakeStatus());

        int produced = result.bytesProduced();
        int consumed = result.bytesConsumed();
        int closeNotifyLen = produced;

        assertTrue(produced > 0);
        assertEquals(0, consumed);
        assertEquals(produced, encryptedClientToServer.remaining());
        // Outbound buffer should be drained now.
        assertTrue(client.isOutboundDone());
        assertFalse(client.isInboundDone());

        assertFalse(server.isOutboundDone());
        assertFalse(server.isInboundDone());
        result = server.unwrap(encryptedClientToServer, plainServerOut);
        plainServerOut.flip();

        assertEquals(SSLEngineResult.Status.CLOSED, result.getStatus());
        // Need a WRAP to respond to the close_notify
        assertEquals(SSLEngineResult.HandshakeStatus.NEED_WRAP, result.getHandshakeStatus());

        produced = result.bytesProduced();
        consumed = result.bytesConsumed();
        assertEquals(closeNotifyLen, consumed);
        assertEquals(0, produced);
        // Should have consumed the complete close_notify
        assertEquals(0, encryptedClientToServer.remaining());
        assertEquals(0, plainServerOut.remaining());

        assertFalse(server.isOutboundDone());
        assertTrue(server.isInboundDone());

        result = server.wrap(empty, encryptedServerToClient);
        encryptedServerToClient.flip();

        assertEquals(SSLEngineResult.Status.CLOSED, result.getStatus());
        // UNWRAP/WRAP are not expected after this point
        assertEquals(SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, result.getHandshakeStatus());

        produced = result.bytesProduced();
        consumed = result.bytesConsumed();
        assertEquals(closeNotifyLen, produced);
        assertEquals(0, consumed);

        assertEquals(produced, encryptedServerToClient.remaining());
        assertTrue(server.isOutboundDone());
        assertTrue(server.isInboundDone());

        result = client.unwrap(encryptedServerToClient, plainClientOut);

        plainClientOut.flip();
        assertEquals(SSLEngineResult.Status.CLOSED, result.getStatus());
        // UNWRAP/WRAP are not expected after this point
        assertEquals(SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, result.getHandshakeStatus());

        produced = result.bytesProduced();
        consumed = result.bytesConsumed();
        assertEquals(closeNotifyLen, consumed);
        assertEquals(0, produced);
        assertEquals(0, encryptedServerToClient.remaining());

        assertTrue(client.isOutboundDone());
        assertTrue(client.isInboundDone());

        // Ensure that calling wrap or unwrap again will not produce a SSLException
        encryptedServerToClient.clear();
        plainServerOut.clear();

        result = server.wrap(plainServerOut, encryptedServerToClient);
        assertEngineRemainsClosed(result);

        encryptedClientToServer.clear();
        plainServerOut.clear();

        result = server.unwrap(encryptedClientToServer, plainServerOut);
        assertEngineRemainsClosed(result);

        encryptedClientToServer.clear();
        plainClientOut.clear();

        result = client.wrap(plainClientOut, encryptedClientToServer);
        assertEngineRemainsClosed(result);

        encryptedServerToClient.clear();
        plainClientOut.clear();

        result = client.unwrap(encryptedServerToClient, plainClientOut);
        assertEngineRemainsClosed(result);
    }


    private static void assertEngineRemainsClosed(SSLEngineResult result) {
        assertEquals(SSLEngineResult.Status.CLOSED, result.getStatus());
        assertEquals(SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, result.getHandshakeStatus());
        assertEquals(0, result.bytesConsumed());
        assertEquals(0, result.bytesProduced());
    }

    private static void handshake(SSLEngine clientEngine, SSLEngine serverEngine) throws SSLException {
        ByteBuffer cTOs = ByteBuffer.allocate(clientEngine.getSession().getPacketBufferSize());
        ByteBuffer sTOc = ByteBuffer.allocate(serverEngine.getSession().getPacketBufferSize());

        ByteBuffer serverAppReadBuffer = ByteBuffer.allocate(
                serverEngine.getSession().getApplicationBufferSize());
        ByteBuffer clientAppReadBuffer = ByteBuffer.allocate(
                clientEngine.getSession().getApplicationBufferSize());

        clientEngine.beginHandshake();
        serverEngine.beginHandshake();

        ByteBuffer empty = ByteBuffer.allocate(0);

        SSLEngineResult clientResult;
        SSLEngineResult serverResult;

        boolean clientHandshakeFinished = false;
        boolean serverHandshakeFinished = false;

        do {
            if (!clientHandshakeFinished) {
                clientResult = clientEngine.wrap(empty, cTOs);
                runDelegatedTasks(clientResult, clientEngine);

                if (isHandshakeFinished(clientResult)) {
                    clientHandshakeFinished = true;
                }
            }

            if (!serverHandshakeFinished) {
                serverResult = serverEngine.wrap(empty, sTOc);
                runDelegatedTasks(serverResult, serverEngine);

                if (isHandshakeFinished(serverResult)) {
                    serverHandshakeFinished = true;
                }
            }

            cTOs.flip();
            sTOc.flip();

            if (!clientHandshakeFinished) {
                clientResult = clientEngine.unwrap(sTOc, clientAppReadBuffer);

                runDelegatedTasks(clientResult, clientEngine);

                if (isHandshakeFinished(clientResult)) {
                    clientHandshakeFinished = true;
                }
            }

            if (!serverHandshakeFinished) {
                serverResult = serverEngine.unwrap(cTOs, serverAppReadBuffer);
                runDelegatedTasks(serverResult, serverEngine);

                if (isHandshakeFinished(serverResult)) {
                    serverHandshakeFinished = true;
                }
            }

            sTOc.compact();
            cTOs.compact();
        } while (!clientHandshakeFinished || !serverHandshakeFinished);
    }

    private static boolean isHandshakeFinished(SSLEngineResult result) {
        return result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.FINISHED;
    }

    private static void runDelegatedTasks(SSLEngineResult result, SSLEngine engine) {
        if (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_TASK) {
            for (;;) {
                Runnable task = engine.getDelegatedTask();
                if (task == null) {
                    break;
                }
                task.run();
            }
        }
    }


    private static void assertTrue(boolean result) {
        if (!result) {
            throw new AssertionError();
        }
    }

    private static void assertFalse(boolean result) {
        if (result) {
            throw new AssertionError();
        }
    }

    private static void assertEquals(Object o1, Object o2) {
        if (!o1.equals(o2)) {
            throw new AssertionError(o1 + " != " + o2);
        }
    }
}


> On 13. Mar 2019, at 10:04, Alan Bateman <Alan.Bateman at oracle.com> wrote:
> 
> On 13/03/2019 07:51, Norman Maurer wrote:
>> As it contains also keymaterial can I just tar it or will you be able to use your own keymaterial and fill in the missing pieces?
> If it's needed to demonstrate the issue then include but if it's just setup then it would be better to provide the instructions. I don't know file types are stripped by the mail servers but if you cc me then I can create an issue in JBS.
> 
> -Alan

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/security-dev/attachments/20190313/f3b1afa7/attachment.htm>


More information about the security-dev mailing list