From 4e32bc622c1f73c2ab6a4ef4b4bbd92e381a3439 Mon Sep 17 00:00:00 2001
From: zhangyipeng  <zhangyipeng7@huawei.com>
Date: Mon, 15 Jan 2024 11:11:41 +0800
Subject: [PATCH] [Backport]8273553: sun.security.ssl.SSLEngineImpl.closeInbound also has
 similar error of JDK-8253368
---
 .../classes/sun/security/ssl/SSLEngineImpl.java    |  24 +-
 .../classes/sun/security/ssl/SSLSocketImpl.java    |   7 +-
 .../SSLSocketSSLEngineCloseInbound.java            | 491 +++++++++++++++++++++
 3 files changed, 508 insertions(+), 14 deletions(-)
 create mode 100644 jdk/test/sun/security/ssl/SSLSocketImpl/SSLSocketSSLEngineCloseInbound.java

diff --git a/jdk/src/share/classes/sun/security/ssl/SSLEngineImpl.java b/jdk/src/share/classes/sun/security/ssl/SSLEngineImpl.java
index 05ffb8a00..a9cc989f2 100644
--- a/jdk/src/share/classes/sun/security/ssl/SSLEngineImpl.java
+++ b/jdk/src/share/classes/sun/security/ssl/SSLEngineImpl.java
@@ -47,7 +47,7 @@ import javax.net.ssl.SSLProtocolException;
 import javax.net.ssl.SSLSession;
 
 /**
- * Implementation of an non-blocking SSLEngine.
+ * Implementation of a non-blocking SSLEngine.
  *
  * @author Brad Wetmore
  */
@@ -230,7 +230,7 @@ final class SSLEngineImpl extends SSLEngine implements SSLTransport {
             if (ciphertext == null && !conContext.isNegotiated &&
                     conContext.isInboundClosed() &&
                     hsStatus == HandshakeStatus.NEED_WRAP) {
-                // Even the outboud is open, no futher data could be wrapped as:
+                // Even the outbound is open, no further data could be wrapped as:
                 //     1. the outbound is empty
                 //     2. no negotiated connection
                 //     3. the inbound has closed, cannot complete the handshake
@@ -631,17 +631,19 @@ final class SSLEngineImpl extends SSLEngine implements SSLTransport {
             SSLLogger.finest("Closing inbound of SSLEngine");
         }
 
-        // Is it ready to close inbound?
-        //
-        // No need to throw exception if the initial handshake is not started.
-        if (!conContext.isInputCloseNotified &&
-            (conContext.isNegotiated || conContext.handshakeContext != null)) {
 
-            throw conContext.fatal(Alert.INTERNAL_ERROR,
-                    "closing inbound before receiving peer's close_notify");
+        try {
+            // Is it ready to close inbound?
+            //
+            // No need to throw exception if the initial handshake is not started.
+            if (!conContext.isInputCloseNotified &&
+                (conContext.isNegotiated || conContext.handshakeContext != null)) {
+                throw new SSLException(
+                        "closing inbound before receiving peer's close_notify");
+            }
+        } finally {
+            conContext.closeInbound();
         }
-
-        conContext.closeInbound();
     }
 
     @Override
diff --git a/jdk/src/share/classes/sun/security/ssl/SSLSocketImpl.java b/jdk/src/share/classes/sun/security/ssl/SSLSocketImpl.java
index 69c96f226..7e8b131bb 100644
--- a/jdk/src/share/classes/sun/security/ssl/SSLSocketImpl.java
+++ b/jdk/src/share/classes/sun/security/ssl/SSLSocketImpl.java
@@ -742,9 +742,10 @@ public final class SSLSocketImpl
         // No need to throw exception if the initial handshake is not started.
         try {
             if (checkCloseNotify && !conContext.isInputCloseNotified &&
-                (conContext.isNegotiated || conContext.handshakeContext != null)) {
-            throw new SSLException(
-                    "closing inbound before receiving peer's close_notify");
+                    (conContext.isNegotiated ||
+                            conContext.handshakeContext != null)) {
+                throw new SSLException(
+                        "closing inbound before receiving peer's close_notify");
             }
         } finally {
             conContext.closeInbound();
diff --git a/jdk/test/sun/security/ssl/SSLSocketImpl/SSLSocketSSLEngineCloseInbound.java b/jdk/test/sun/security/ssl/SSLSocketImpl/SSLSocketSSLEngineCloseInbound.java
new file mode 100644
index 000000000..abf1571ca
--- /dev/null
+++ b/jdk/test/sun/security/ssl/SSLSocketImpl/SSLSocketSSLEngineCloseInbound.java
@@ -0,0 +1,491 @@
+/*
+ * Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+//
+// SunJSSE does not support dynamic system properties, no way to re-use
+// system properties in samevm/agentvm mode.
+//
+
+/*
+ * @test
+ * @bug 8273553 8253368
+ * @summary sun.security.ssl.SSLEngineImpl.closeInbound also has similar error
+ *          of JDK-8253368
+ * @run main/othervm SSLSocketSSLEngineCloseInbound TLSv1.3
+ * @run main/othervm SSLSocketSSLEngineCloseInbound TLSv1.2
+ * @run main/othervm SSLSocketSSLEngineCloseInbound TLSv1.1
+ * @run main/othervm SSLSocketSSLEngineCloseInbound TLSv1
+ * @run main/othervm SSLSocketSSLEngineCloseInbound TLS
+ */
+
+/**
+ * A SSLSocket/SSLEngine interop test case.  This is not the way to
+ * code SSLEngine-based servers, but works for what we need to do here,
+ * which is to make sure that SSLEngine/SSLSockets can talk to each other.
+ * SSLEngines can use direct or indirect buffers, and different code
+ * is used to get at the buffer contents internally, so we test that here.
+ *
+ * The test creates one SSLSocket (client) and one SSLEngine (server).
+ * The SSLSocket talks to a raw ServerSocket, and the server code
+ * does the translation between byte [] and ByteBuffers that the SSLEngine
+ * can use.  The "transport" layer consists of a Socket Input/OutputStream
+ * and two byte buffers for the SSLEngines:  think of them
+ * as directly connected pipes.
+ *
+ * Again, this is a *very* simple example: real code will be much more
+ * involved.  For example, different threading and I/O models could be
+ * used, transport mechanisms could close unexpectedly, and so on.
+ *
+ * When this application runs, notice that several messages
+ * (wrap/unwrap) pass before any application data is consumed or
+ * produced.  (For more information, please see the SSL/TLS
+ * specifications.)  There may several steps for a successful handshake,
+ * so it's typical to see the following series of operations:
+ *
+ *      client          server          message
+ *      ======          ======          =======
+ *      write()         ...             ClientHello
+ *      ...             unwrap()        ClientHello
+ *      ...             wrap()          ServerHello/Certificate
+ *      read()          ...             ServerHello/Certificate
+ *      write()         ...             ClientKeyExchange
+ *      write()         ...             ChangeCipherSpec
+ *      write()         ...             Finished
+ *      ...             unwrap()        ClientKeyExchange
+ *      ...             unwrap()        ChangeCipherSpec
+ *      ...             unwrap()        Finished
+ *      ...             wrap()          ChangeCipherSpec
+ *      ...             wrap()          Finished
+ *      read()          ...             ChangeCipherSpec
+ *      read()          ...             Finished
+ */
+import javax.net.ssl.*;
+import javax.net.ssl.SSLEngineResult.*;
+import java.io.*;
+import java.net.*;
+import java.security.*;
+import java.nio.*;
+
+public class SSLSocketSSLEngineCloseInbound {
+
+    /*
+     * Enables logging of the SSL/TLS operations.
+     */
+    private static final boolean logging = true;
+
+    /*
+     * Enables the JSSE system debugging system property:
+     *
+     *     -Djavax.net.debug=all
+     *
+     * This gives a lot of low-level information about operations underway,
+     * including specific handshake messages, and might be best examined
+     * after gaining some familiarity with this application.
+     */
+    private static final boolean debug = false;
+    private final SSLContext sslc;
+    private SSLEngine serverEngine;     // server-side SSLEngine
+    private SSLSocket clientSocket;
+
+    private final byte[] serverMsg =
+        "Hi there Client, I'm a Server.".getBytes();
+    private final byte[] clientMsg =
+        "Hello Server, I'm a Client! Pleased to meet you!".getBytes();
+
+    private ByteBuffer serverOut;       // write side of serverEngine
+    private ByteBuffer serverIn;        // read side of serverEngine
+
+    private volatile Exception clientException;
+    private volatile Exception serverException;
+
+    /*
+     * For data transport, this example uses local ByteBuffers.
+     */
+    private ByteBuffer cTOs;            // "reliable" transport client->server
+    private ByteBuffer sTOc;            // "reliable" transport server->client
+
+    /*
+     * The following is to set up the keystores/trust material.
+     */
+    private static final String pathToStores = "../../../../javax/net/ssl/etc";
+    private static final String keyStoreFile = "keystore";
+    private static final String trustStoreFile = "truststore";
+    private static final String keyFilename =
+            System.getProperty("test.src", ".") + "/" + pathToStores
+            + "/" + keyStoreFile;
+    private static final String trustFilename =
+            System.getProperty("test.src", ".") + "/" + pathToStores
+            + "/" + trustStoreFile;
+
+    /*
+     * Main entry point for this test.
+     */
+    public static void main(String[] args) throws Exception {
+        String protocol = args[0];
+
+        // reset security properties to make sure that the algorithms
+        // and keys used in this test are not disabled.
+        Security.setProperty("jdk.tls.disabledAlgorithms", "");
+        Security.setProperty("jdk.certpath.disabledAlgorithms", "");
+
+        if (debug) {
+            System.setProperty("javax.net.debug", "all");
+        }
+
+        /*
+         * Run the tests with direct and indirect buffers.
+         */
+        SSLSocketSSLEngineCloseInbound test =
+            new SSLSocketSSLEngineCloseInbound(protocol);
+        log("-------------------------------------");
+        log("Testing " + protocol + " for direct buffers ...");
+        test.runTest(true);
+
+        log("---------------------------------------");
+        log("Testing " + protocol + " for indirect buffers ...");
+        test.runTest(false);
+
+        log("Test Passed.");
+    }
+
+    /*
+     * Create an initialized SSLContext to use for these tests.
+     */
+    public SSLSocketSSLEngineCloseInbound(String protocol) throws Exception {
+
+        KeyStore ks = KeyStore.getInstance("JKS");
+        KeyStore ts = KeyStore.getInstance("JKS");
+
+        char[] passphrase = "passphrase".toCharArray();
+
+        try (FileInputStream keyFile = new FileInputStream(keyFilename);
+                FileInputStream trustFile = new FileInputStream(trustFilename)) {
+            ks.load(keyFile, passphrase);
+            ts.load(trustFile, passphrase);
+        }
+
+        KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
+        kmf.init(ks, passphrase);
+
+        TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
+        tmf.init(ts);
+
+        SSLContext sslCtx = SSLContext.getInstance(protocol);
+
+        sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+
+        sslc = sslCtx;
+    }
+
+    /*
+     * Run the test.
+     *
+     * Sit in a tight loop, with the server engine calling wrap/unwrap
+     * regardless of whether data is available or not.  We do this until
+     * we get the application data.  Then we shutdown and go to the next one.
+     *
+     * The main loop handles all the I/O phases of the SSLEngine's
+     * lifetime:
+     *
+     *     initial handshaking
+     *     application data transfer
+     *     engine closing
+     *
+     * One could easily separate these phases into separate
+     * sections of code.
+     */
+    private void runTest(boolean direct) throws Exception {
+        clientSocket = null;
+
+        // generates the server-side Socket
+        try (ServerSocket serverSocket = new ServerSocket()) {
+            serverSocket.setReuseAddress(false);
+            serverSocket.bind(null);
+            int port = serverSocket.getLocalPort();
+            log("Port: " + port);
+            Thread thread = createClientThread(port);
+
+            createSSLEngine();
+            createBuffers(direct);
+
+            // server-side socket that will read
+            try (Socket socket = serverSocket.accept()) {
+                socket.setSoTimeout(500);
+
+                InputStream is = socket.getInputStream();
+                OutputStream os = socket.getOutputStream();
+
+                SSLEngineResult serverResult;   // results from last operation
+
+                /*
+                 * Examining the SSLEngineResults could be much more involved,
+                 * and may alter the overall flow of the application.
+                 *
+                 * For example, if we received a BUFFER_OVERFLOW when trying
+                 * to write to the output pipe, we could reallocate a larger
+                 * pipe, but instead we wait for the peer to drain it.
+                 */
+                byte[] inbound = new byte[8192];
+                byte[] outbound = new byte[8192];
+
+                while (!isEngineClosed(serverEngine)) {
+                    int len;
+
+                    // Inbound data
+                    log("================");
+
+                    // Try reading Client side, even if it's already closed.
+                    try {
+                        len = is.read(inbound);
+                        if (len > 0) {
+                            cTOs.put(inbound, 0, len);
+                        }
+                    } catch (IOException e) {
+                        /*
+                         * swallow IO/SocketTimeoutExceptions.  We'll do
+                         * the testing/exit after the unwraps.
+                         */
+                    }
+
+                    cTOs.flip();
+
+                    serverResult = serverEngine.unwrap(cTOs, serverIn);
+                    log("server unwrap: ", serverResult);
+                    runDelegatedTasks(serverResult, serverEngine);
+                    cTOs.compact();
+
+                    // Outbound data
+                    log("----");
+
+                    // After we've received  our app bytes, close input side
+                    // and see what happens.  Exit the test at the end.
+                    if (serverIn.position() != 0) {
+                        try {
+                            serverEngine.closeInbound();
+                            throw new Exception(
+                                    "No error shutting down client's input");
+                        } catch (SSLException e) {
+                            System.out.println(
+                                    "Server caught the right Exception");
+                        }
+
+                        if (serverEngine.getSession().isValid()) {
+                            System.out.println("Server session is still valid");
+                        } else {
+                            throw new Exception("Server session is not valid");
+                        }
+
+                        return;
+                    }
+
+                    serverResult = serverEngine.wrap(serverOut, sTOc);
+                    log("server wrap: ", serverResult);
+                    runDelegatedTasks(serverResult, serverEngine);
+
+                    sTOc.flip();
+
+                    if ((len = sTOc.remaining()) != 0) {
+                        sTOc.get(outbound, 0, len);
+                        os.write(outbound, 0, len);
+                        // Give the other side a chance to process
+                    }
+
+                    sTOc.compact();
+                }
+            } catch (Exception e) {
+                serverException = e;
+            } finally {
+                // Wait for the client to join up with us.
+                if (thread != null) {
+                    thread.join();
+                }
+            }
+        } finally {
+            if (serverException != null) {
+                if (clientException != null) {
+                    serverException.initCause(clientException);
+                }
+                throw serverException;
+            }
+            if (clientException != null) {
+                if (serverException != null) {
+                    clientException.initCause(serverException);
+                }
+                throw clientException;
+            }
+        }
+    }
+
+    /*
+     * Create a client thread which does simple SSLSocket operations.
+     * We'll write and read one data packet.
+     */
+    private Thread createClientThread(final int port) {
+
+        Thread t = new Thread("ClientThread") {
+
+            @Override
+            public void run() {
+                // client-side socket
+                try (SSLSocket sslSocket = (SSLSocket)sslc.getSocketFactory().
+                            createSocket("localhost", port)) {
+                    clientSocket = sslSocket;
+
+                    OutputStream os = sslSocket.getOutputStream();
+
+                    // write(byte[]) goes in one shot.
+                    os.write(clientMsg);
+                    os.flush();
+
+                    try {
+                        sslSocket.shutdownInput();
+                        throw new Exception(
+                                "No error shutting down client's input");
+                    } catch (SSLException e) {
+                        System.out.println("Client caught the right Exception");
+                    }
+
+                    if (sslSocket.getSession().isValid()) {
+                        System.out.println("Client session is still valid");
+                    } else {
+                        throw new Exception("Client's session is not valid");
+                    }
+
+                    // Give server a chance to read before we shutdown via
+                    // the try-with-resources block.
+                    Thread.sleep(2000);
+                } catch (Exception e) {
+                    clientException = e;
+                }
+            }
+        };
+        t.start();
+        return t;
+    }
+
+    /*
+     * Using the SSLContext created during object creation,
+     * create/configure the SSLEngines we'll use for this test.
+     */
+    private void createSSLEngine() {
+        /*
+         * Configure the serverEngine to act as a server in the SSL/TLS
+         * handshake.
+         */
+        serverEngine = sslc.createSSLEngine();
+        serverEngine.setUseClientMode(false);
+        serverEngine.getNeedClientAuth();
+    }
+
+    /*
+     * Create and size the buffers appropriately.
+     */
+    private void createBuffers(boolean direct) {
+
+        SSLSession session = serverEngine.getSession();
+        int appBufferMax = session.getApplicationBufferSize();
+        int netBufferMax = session.getPacketBufferSize();
+
+        /*
+         * We'll make the input buffers a bit bigger than the max needed
+         * size, so that unwrap()s following a successful data transfer
+         * won't generate BUFFER_OVERFLOWS.
+         *
+         * We'll use a mix of direct and indirect ByteBuffers for
+         * tutorial purposes only.  In reality, only use direct
+         * ByteBuffers when they give a clear performance enhancement.
+         */
+        if (direct) {
+            serverIn = ByteBuffer.allocateDirect(appBufferMax + 50);
+            cTOs = ByteBuffer.allocateDirect(netBufferMax);
+            sTOc = ByteBuffer.allocateDirect(netBufferMax);
+        } else {
+            serverIn = ByteBuffer.allocate(appBufferMax + 50);
+            cTOs = ByteBuffer.allocate(netBufferMax);
+            sTOc = ByteBuffer.allocate(netBufferMax);
+        }
+
+        serverOut = ByteBuffer.wrap(serverMsg);
+    }
+
+    /*
+     * If the result indicates that we have outstanding tasks to do,
+     * go ahead and run them in this thread.
+     */
+    private static void runDelegatedTasks(SSLEngineResult result,
+            SSLEngine engine) throws Exception {
+
+        if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
+            Runnable runnable;
+            while ((runnable = engine.getDelegatedTask()) != null) {
+                log("\trunning delegated task...");
+                runnable.run();
+            }
+            HandshakeStatus hsStatus = engine.getHandshakeStatus();
+            if (hsStatus == HandshakeStatus.NEED_TASK) {
+                throw new Exception(
+                        "handshake shouldn't need additional tasks");
+            }
+            log("\tnew HandshakeStatus: " + hsStatus);
+        }
+    }
+
+    private static boolean isEngineClosed(SSLEngine engine) {
+        return (engine.isOutboundDone() && engine.isInboundDone());
+    }
+
+    /*
+     * Logging code
+     */
+    private static boolean resultOnce = true;
+
+    private static void log(String str, SSLEngineResult result) {
+        if (!logging) {
+            return;
+        }
+        if (resultOnce) {
+            resultOnce = false;
+            log("The format of the SSLEngineResult is: \n"
+                    + "\t\"getStatus() / getHandshakeStatus()\" +\n"
+                    + "\t\"bytesConsumed() / bytesProduced()\"\n");
+        }
+        HandshakeStatus hsStatus = result.getHandshakeStatus();
+        log(str
+                + result.getStatus() + "/" + hsStatus + ", "
+                + result.bytesConsumed() + "/" + result.bytesProduced()
+                + " bytes");
+        if (hsStatus == HandshakeStatus.FINISHED) {
+            log("\t...ready for application data");
+        }
+    }
+
+    private static void log(String str) {
+        if (logging) {
+            if (debug) {
+                System.err.println(str);
+            } else {
+                System.out.println(str);
+            }
+        }
+    }
+}
-- 
2.12.3

