8207009: TLS 1.3 half-close and synchronization issues
authorxuelei
Tue, 14 Aug 2018 18:16:47 -0700
changeset 51407 910f7b56592f
parent 51406 f4b4dfac45b1
child 51408 59269a19f108
8207009: TLS 1.3 half-close and synchronization issues Reviewed-by: jnimeh, mullan, wetmore
src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java
src/java.base/share/classes/javax/net/ssl/SSLEngine.java
src/java.base/share/classes/javax/net/ssl/SSLSocket.java
src/java.base/share/classes/sun/security/ssl/Alert.java
src/java.base/share/classes/sun/security/ssl/BaseSSLSocketImpl.java
src/java.base/share/classes/sun/security/ssl/CipherSuite.java
src/java.base/share/classes/sun/security/ssl/DTLSOutputRecord.java
src/java.base/share/classes/sun/security/ssl/HandshakeContext.java
src/java.base/share/classes/sun/security/ssl/HandshakeOutStream.java
src/java.base/share/classes/sun/security/ssl/KeyUpdate.java
src/java.base/share/classes/sun/security/ssl/OutputRecord.java
src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java
src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java
src/java.base/share/classes/sun/security/ssl/SSLEngineInputRecord.java
src/java.base/share/classes/sun/security/ssl/SSLEngineOutputRecord.java
src/java.base/share/classes/sun/security/ssl/SSLHandshake.java
src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java
src/java.base/share/classes/sun/security/ssl/SSLSocketInputRecord.java
src/java.base/share/classes/sun/security/ssl/SSLSocketOutputRecord.java
src/java.base/share/classes/sun/security/ssl/TransportContext.java
test/jdk/java/net/httpclient/CookieHeaderTest.java
test/jdk/java/net/httpclient/EncodedCharsInURI.java
test/jdk/java/net/httpclient/ServerCloseTest.java
test/jdk/javax/net/ssl/ALPN/SSLEngineAlpnTest.java
test/jdk/javax/net/ssl/SSLEngine/Arrays.java
test/jdk/javax/net/ssl/SSLEngine/ExtendedKeyEngine.java
test/jdk/javax/net/ssl/TLSCommon/SSLEngineTestCase.java
test/jdk/javax/net/ssl/TLSv12/TLSEnginesClosureTest.java
test/jdk/sun/security/ssl/AppInputStream/ReadBlocksClose.java
test/jdk/sun/security/ssl/SSLEngineImpl/CloseStart.java
test/jdk/sun/security/ssl/SSLEngineImpl/SSLEngineDeadlock.java
test/jdk/sun/security/ssl/SSLEngineImpl/SSLEngineKeyLimit.java
test/jdk/sun/security/ssl/SSLEngineImpl/TLS13BeginHandshake.java
test/jdk/sun/security/ssl/SSLSocketImpl/AsyncSSLSocketClose.java
test/jdk/sun/security/ssl/SSLSocketImpl/SSLSocketKeyLimit.java
test/jdk/sun/security/ssl/SSLSocketImpl/ServerRenegoWithTwoVersions.java
--- a/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java	Tue Aug 14 18:16:47 2018 -0700
@@ -118,7 +118,8 @@
         final String BLOCK_PADS = "NOPADDING|PKCS5PADDING|ISO10126PADDING";
 
         AccessController.doPrivileged(
-            new java.security.PrivilegedAction<>() {
+            new java.security.PrivilegedAction<Object>() {
+                @Override
                 public Object run() {
 
                     /*
--- a/src/java.base/share/classes/javax/net/ssl/SSLEngine.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/src/java.base/share/classes/javax/net/ssl/SSLEngine.java	Tue Aug 14 18:16:47 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2018, 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
@@ -145,7 +145,7 @@
  *     application messages are encrypted and integrity protected,
  *     and inbound messages reverse the process.
  *
- *     <li>  Rehandshaking - Either side may request a renegotiation of
+ *     <li> Rehandshaking - Either side may request a renegotiation of
  *     the session at any time during the Application Data phase.  New
  *     handshaking data can be intermixed among the application data.
  *     Before starting the rehandshake phase, the application may
@@ -156,12 +156,20 @@
  *     configuration settings will not be used until the next
  *     handshake.
  *
- *     <li>  Closure - When the connection is no longer needed, the
- *     application should close the {@code SSLEngine} and should
- *     send/receive any remaining messages to the peer before
- *     closing the underlying transport mechanism.  Once an engine is
- *     closed, it is not reusable:  a new {@code SSLEngine} must
- *     be created.
+ *     <li> Closure - When the connection is no longer needed, the client
+ *     and the server applications should each close both sides of their
+ *     respective connections.  For {@code SSLEngine} objects, an
+ *     application should call {@link SSLEngine#closeOutbound()} and
+ *     send any remaining messages to the peer.  Likewise, an application
+ *     should receive any remaining messages from the peer before calling
+ *     {@link SSLEngine#closeInbound()}.  The underlying transport mechanism
+ *     can then be closed after both sides of the {@code SSLEngine} have
+ *     been closed.  If the connection is not closed in an orderly manner
+ *     (for example {@link SSLEngine#closeInbound()} is called before the
+ *     peer's write closure notification has been received), exceptions
+ *     will be raised to indicate that an error has occurred.  Once an
+ *     engine is closed, it is not reusable: a new {@code SSLEngine}
+ *     must be created.
  * </OL>
  * An {@code SSLEngine} is created by calling {@link
  * SSLContext#createSSLEngine()} from an initialized
--- a/src/java.base/share/classes/javax/net/ssl/SSLSocket.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/src/java.base/share/classes/javax/net/ssl/SSLSocket.java	Tue Aug 14 18:16:47 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2018, 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
@@ -130,6 +130,21 @@
  * socket can not switch between client and server modes, even when
  * performing renegotiations.
  *
+ * @apiNote
+ * When the connection is no longer needed, the client and server
+ * applications should each close both sides of their respective connection.
+ * For {@code SSLSocket} objects, for example, an application can call
+ * {@link Socket#shutdownOutput()} or {@link java.io.OutputStream#close()}
+ * for output strean close and call {@link Socket#shutdownInput()} or
+ * {@link java.io.InputStream#close()} for input stream close.  Note that
+ * in some cases, closing the input stream may depend on the peer's output
+ * stream being closed first.  If the connection is not closed in an orderly
+ * manner (for example {@link Socket#shutdownInput()} is called before the
+ * peer's write closure notification has been received), exceptions may
+ * be raised to indicate that an error has occurred.  Once an
+ * {@code SSLSocket} is closed, it is not reusable: a new {@code SSLSocket}
+ * must be created.
+ *
  * @see java.net.Socket
  * @see SSLServerSocket
  * @see SSLSocketFactory
--- a/src/java.base/share/classes/sun/security/ssl/Alert.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/src/java.base/share/classes/sun/security/ssl/Alert.java	Tue Aug 14 18:16:47 2018 -0700
@@ -235,13 +235,22 @@
             Level level = Level.valueOf(am.level);
             Alert alert = Alert.valueOf(am.id);
             if (alert == Alert.CLOSE_NOTIFY) {
-                if (tc.handshakeContext != null) {
+                tc.isInputCloseNotified = true;
+                tc.closeInbound();
+
+                if (tc.peerUserCanceled) {
+                    tc.closeOutbound();
+                } else if (tc.handshakeContext != null) {
                     tc.fatal(Alert.UNEXPECTED_MESSAGE,
                             "Received close_notify during handshake");
                 }
-
-                tc.isInputCloseNotified = true;
-                tc.closeInbound();
+            } else if (alert == Alert.USER_CANCELED) {
+                if (level == Level.WARNING) {
+                    tc.peerUserCanceled = true;
+                } else {
+                    tc.fatal(alert,
+                            "Received fatal close_notify alert", true, null);
+                }
             } else if ((level == Level.WARNING) && (alert != null)) {
                 // Terminate the connection if an alert with a level of warning
                 // is received during handshaking, except the no_certificate
--- a/src/java.base/share/classes/sun/security/ssl/BaseSSLSocketImpl.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/src/java.base/share/classes/sun/security/ssl/BaseSSLSocketImpl.java	Tue Aug 14 18:16:47 2018 -0700
@@ -204,30 +204,35 @@
     //
 
     /**
-     * The semantics of shutdownInput is not supported in TLS 1.0
-     * spec. Thus when the method is called on an SSL socket, an
-     * UnsupportedOperationException will be thrown.
+     * Places the input stream for this socket at "end of stream".  Any data
+     * sent to the input stream side of the socket is acknowledged and then
+     * silently discarded.
      *
-     * @throws UnsupportedOperationException
+     * @see java.net.Socket#shutdownInput
      */
     @Override
-    public final void shutdownInput() throws IOException {
-        throw new UnsupportedOperationException("The method shutdownInput()" +
-                   " is not supported in SSLSocket");
+    public void shutdownInput() throws IOException {
+        if (self == this) {
+            super.shutdownInput();
+        } else {
+            self.shutdownInput();
+        }
     }
 
     /**
-     * The semantics of shutdownOutput is not supported in TLS 1.0
-     * spec. Thus when the method is called on an SSL socket, an
-     * UnsupportedOperationException will be thrown.
+     * Disables the output stream for this socket.  For a TCP socket, any
+     * previously written data will be sent followed by TCP's normal
+     * connection termination sequence.
      *
-     * @throws UnsupportedOperationException
+     * @see java.net.Socket#shutdownOutput
      */
     @Override
-    public final void shutdownOutput() throws IOException {
-        throw new UnsupportedOperationException("The method shutdownOutput()" +
-                   " is not supported in SSLSocket");
-
+    public void shutdownOutput() throws IOException {
+        if (self == this) {
+            super.shutdownOutput();
+        } else {
+            self.shutdownOutput();
+        }
     }
 
     /**
@@ -235,7 +240,7 @@
      * @see java.net.Socket#isInputShutdown
      */
     @Override
-    public final boolean isInputShutdown() {
+    public boolean isInputShutdown() {
         if (self == this) {
             return super.isInputShutdown();
         } else {
@@ -248,7 +253,7 @@
      * @see java.net.Socket#isOutputShutdown
      */
     @Override
-    public final boolean isOutputShutdown() {
+    public boolean isOutputShutdown() {
         if (self == this) {
             return super.isOutputShutdown();
         } else {
@@ -618,7 +623,7 @@
     }
 
     @Override
-    public synchronized void close() throws IOException {
+    public void close() throws IOException {
         if (self == this) {
             super.close();
         } else {
--- a/src/java.base/share/classes/sun/security/ssl/CipherSuite.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/src/java.base/share/classes/sun/security/ssl/CipherSuite.java	Tue Aug 14 18:16:47 2018 -0700
@@ -805,7 +805,7 @@
         this.id = id;
         this.isDefaultEnabled = isDefaultEnabled;
         this.name = name;
-        if (aliases.isEmpty()) {
+        if (!aliases.isEmpty()) {
             this.aliases = Arrays.asList(aliases.split(","));
         } else {
             this.aliases = Collections.emptyList();
--- a/src/java.base/share/classes/sun/security/ssl/DTLSOutputRecord.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/src/java.base/share/classes/sun/security/ssl/DTLSOutputRecord.java	Tue Aug 14 18:16:47 2018 -0700
@@ -44,7 +44,7 @@
     Authenticator       prevWriteAuthenticator;
     SSLWriteCipher      prevWriteCipher;
 
-    private final LinkedList<RecordMemo> alertMemos = new LinkedList<>();
+    private volatile boolean isCloseWaiting = false;
 
     DTLSOutputRecord(HandshakeHash handshakeHash) {
         super(handshakeHash, SSLWriteCipher.nullDTlsWriteCipher());
@@ -58,6 +58,21 @@
     }
 
     @Override
+    public synchronized void close() throws IOException {
+        if (!isClosed) {
+            if (fragmenter != null && fragmenter.hasAlert()) {
+                isCloseWaiting = true;
+            } else {
+                super.close();
+            }
+        }
+    }
+
+    boolean isClosed() {
+        return isClosed || isCloseWaiting;
+    }
+
+    @Override
     void initHandshaker() {
         // clean up
         fragmenter = null;
@@ -71,6 +86,14 @@
     @Override
     void changeWriteCiphers(SSLWriteCipher writeCipher,
             boolean useChangeCipherSpec) throws IOException {
+        if (isClosed()) {
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                SSLLogger.warning("outbound has closed, ignore outbound " +
+                    "change_cipher_spec message");
+            }
+            return;
+        }
+
         if (useChangeCipherSpec) {
             encodeChangeCipherSpec();
         }
@@ -91,23 +114,31 @@
 
     @Override
     void encodeAlert(byte level, byte description) throws IOException {
-        RecordMemo memo = new RecordMemo();
+        if (isClosed()) {
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                SSLLogger.warning("outbound has closed, ignore outbound " +
+                    "alert message: " + Alert.nameOf(description));
+            }
+            return;
+        }
 
-        memo.contentType = ContentType.ALERT.id;
-        memo.majorVersion = protocolVersion.major;
-        memo.minorVersion = protocolVersion.minor;
-        memo.encodeEpoch = writeEpoch;
-        memo.encodeCipher = writeCipher;
+        if (fragmenter == null) {
+           fragmenter = new DTLSFragmenter();
+        }
 
-        memo.fragment = new byte[2];
-        memo.fragment[0] = level;
-        memo.fragment[1] = description;
-
-        alertMemos.add(memo);
+        fragmenter.queueUpAlert(level, description);
     }
 
     @Override
     void encodeChangeCipherSpec() throws IOException {
+        if (isClosed()) {
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                SSLLogger.warning("outbound has closed, ignore outbound " +
+                    "change_cipher_spec message");
+            }
+            return;
+        }
+
         if (fragmenter == null) {
            fragmenter = new DTLSFragmenter();
         }
@@ -117,6 +148,15 @@
     @Override
     void encodeHandshake(byte[] source,
             int offset, int length) throws IOException {
+        if (isClosed()) {
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                SSLLogger.warning("outbound has closed, ignore outbound " +
+                        "handshake message",
+                        ByteBuffer.wrap(source, offset, length));
+            }
+            return;
+        }
+
         if (firstMessage) {
             firstMessage = false;
         }
@@ -132,6 +172,23 @@
     Ciphertext encode(
         ByteBuffer[] srcs, int srcsOffset, int srcsLength,
         ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws IOException {
+
+        if (isClosed) {
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                SSLLogger.warning("outbound has closed, ignore outbound " +
+                    "application data or cached messages");
+            }
+
+            return null;
+        } else if (isCloseWaiting) {
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                SSLLogger.warning("outbound has closed, ignore outbound " +
+                    "application data");
+            }
+
+            srcs = null;    // use no application data.
+        }
+
         return encode(srcs, srcsOffset, srcsLength, dsts[0]);
     }
 
@@ -237,48 +294,6 @@
 
     private Ciphertext acquireCiphertext(
             ByteBuffer destination) throws IOException {
-        if (alertMemos != null && !alertMemos.isEmpty()) {
-            RecordMemo memo = alertMemos.pop();
-
-            int dstPos = destination.position();
-            int dstLim = destination.limit();
-            int dstContent = dstPos + headerSize +
-                                writeCipher.getExplicitNonceSize();
-            destination.position(dstContent);
-
-            destination.put(memo.fragment);
-
-            destination.limit(destination.position());
-            destination.position(dstContent);
-
-            if (SSLLogger.isOn && SSLLogger.isOn("record")) {
-                SSLLogger.fine(
-                        "WRITE: " + protocolVersion + " " +
-                        ContentType.ALERT.name +
-                        ", length = " + destination.remaining());
-            }
-
-            // Encrypt the fragment and wrap up a record.
-            long recordSN = encrypt(memo.encodeCipher,
-                    ContentType.ALERT.id,
-                    destination, dstPos, dstLim, headerSize,
-                    ProtocolVersion.valueOf(memo.majorVersion,
-                            memo.minorVersion));
-
-            if (SSLLogger.isOn && SSLLogger.isOn("packet")) {
-                ByteBuffer temporary = destination.duplicate();
-                temporary.limit(temporary.position());
-                temporary.position(dstPos);
-                SSLLogger.fine("Raw write", temporary);
-            }
-
-            // remain the limit unchanged
-            destination.limit(dstLim);
-
-            return new Ciphertext(ContentType.ALERT.id,
-                    SSLHandshake.NOT_APPLICABLE.id, recordSN);
-        }
-
         if (fragmenter != null) {
             return fragmenter.acquireCiphertext(destination);
         }
@@ -288,16 +303,14 @@
 
     @Override
     boolean isEmpty() {
-        return ((fragmenter == null) || fragmenter.isEmpty()) &&
-               ((alertMemos == null) || alertMemos.isEmpty());
+        return (fragmenter == null) || fragmenter.isEmpty();
     }
 
     @Override
     void launchRetransmission() {
         // Note: Please don't retransmit if there are handshake messages
         // or alerts waiting in the queue.
-        if (((alertMemos == null) || alertMemos.isEmpty()) &&
-                (fragmenter != null) && fragmenter.isRetransmittable()) {
+        if ((fragmenter != null) && fragmenter.isRetransmittable()) {
             fragmenter.setRetransmission();
         }
     }
@@ -338,29 +351,6 @@
         // size is bigger than 256 bytes.
         private int retransmits = 2;            // attemps of retransmits
 
-        void queueUpChangeCipherSpec() {
-
-            // Cleanup if a new flight starts.
-            if (flightIsReady) {
-                handshakeMemos.clear();
-                acquireIndex = 0;
-                flightIsReady = false;
-            }
-
-            RecordMemo memo = new RecordMemo();
-
-            memo.contentType = ContentType.CHANGE_CIPHER_SPEC.id;
-            memo.majorVersion = protocolVersion.major;
-            memo.minorVersion = protocolVersion.minor;
-            memo.encodeEpoch = writeEpoch;
-            memo.encodeCipher = writeCipher;
-
-            memo.fragment = new byte[1];
-            memo.fragment[0] = 1;
-
-            handshakeMemos.add(memo);
-        }
-
         void queueUpHandshake(byte[] buf,
                 int offset, int length) throws IOException {
 
@@ -401,6 +391,45 @@
             }
         }
 
+        void queueUpChangeCipherSpec() {
+
+            // Cleanup if a new flight starts.
+            if (flightIsReady) {
+                handshakeMemos.clear();
+                acquireIndex = 0;
+                flightIsReady = false;
+            }
+
+            RecordMemo memo = new RecordMemo();
+
+            memo.contentType = ContentType.CHANGE_CIPHER_SPEC.id;
+            memo.majorVersion = protocolVersion.major;
+            memo.minorVersion = protocolVersion.minor;
+            memo.encodeEpoch = writeEpoch;
+            memo.encodeCipher = writeCipher;
+
+            memo.fragment = new byte[1];
+            memo.fragment[0] = 1;
+
+            handshakeMemos.add(memo);
+        }
+
+        void queueUpAlert(byte level, byte description) throws IOException {
+            RecordMemo memo = new RecordMemo();
+
+            memo.contentType = ContentType.ALERT.id;
+            memo.majorVersion = protocolVersion.major;
+            memo.minorVersion = protocolVersion.minor;
+            memo.encodeEpoch = writeEpoch;
+            memo.encodeCipher = writeCipher;
+
+            memo.fragment = new byte[2];
+            memo.fragment[0] = level;
+            memo.fragment[1] = description;
+
+            handshakeMemos.add(memo);
+        }
+
         Ciphertext acquireCiphertext(ByteBuffer dstBuf) throws IOException {
             if (isEmpty()) {
                 if (isRetransmittable()) {
@@ -500,8 +529,13 @@
                 return new Ciphertext(hsMemo.contentType,
                         hsMemo.handshakeType, recordSN);
             } else {
+                if (isCloseWaiting &&
+                        memo.contentType == ContentType.ALERT.id) {
+                    close();
+                }
+
                 acquireIndex++;
-                return new Ciphertext(ContentType.CHANGE_CIPHER_SPEC.id,
+                return new Ciphertext(memo.contentType,
                         SSLHandshake.NOT_APPLICABLE.id, recordSN);
             }
         }
@@ -552,6 +586,16 @@
             return false;
         }
 
+        boolean hasAlert() {
+            for (RecordMemo memo : handshakeMemos) {
+                if (memo.contentType == ContentType.ALERT.id) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
         boolean isRetransmittable() {
             return (flightIsReady && !handshakeMemos.isEmpty() &&
                                 (acquireIndex >= handshakeMemos.size()));
--- a/src/java.base/share/classes/sun/security/ssl/HandshakeContext.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/src/java.base/share/classes/sun/security/ssl/HandshakeContext.java	Tue Aug 14 18:16:47 2018 -0700
@@ -47,7 +47,6 @@
 import sun.security.ssl.SupportedGroupsExtension.NamedGroupType;
 import static sun.security.ssl.SupportedGroupsExtension.NamedGroupType.*;
 import sun.security.ssl.SupportedGroupsExtension.SupportedGroups;
-import sun.security.ssl.PskKeyExchangeModesExtension.PskKeyExchangeMode;
 
 abstract class HandshakeContext implements ConnectionContext {
     // System properties
--- a/src/java.base/share/classes/sun/security/ssl/HandshakeOutStream.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/src/java.base/share/classes/sun/security/ssl/HandshakeOutStream.java	Tue Aug 14 18:16:47 2018 -0700
@@ -27,6 +27,7 @@
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.nio.ByteBuffer;
 
 /**
  * Output stream for handshake data.  This is used only internally
@@ -57,7 +58,14 @@
         }
 
         if (outputRecord != null) {
-            outputRecord.encodeHandshake(buf, 0, count);
+            if (!outputRecord.isClosed()) {
+                outputRecord.encodeHandshake(buf, 0, count);
+            } else {
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                    SSLLogger.warning("outbound has closed, ignore outbound " +
+                        "handshake messages", ByteBuffer.wrap(buf, 0, count));
+                }
+            }
 
             // reset the byte array output stream
             reset();
--- a/src/java.base/share/classes/sun/security/ssl/KeyUpdate.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/src/java.base/share/classes/sun/security/ssl/KeyUpdate.java	Tue Aug 14 18:16:47 2018 -0700
@@ -223,8 +223,8 @@
                         Authenticator.valueOf(hc.conContext.protocolVersion),
                         hc.conContext.protocolVersion, key, ivSpec,
                         hc.sslContext.getSecureRandom());
+                rc.baseSecret = nplus1;
                 hc.conContext.inputRecord.changeReadCiphers(rc);
-                hc.conContext.inputRecord.readCipher.baseSecret = nplus1;
                 if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
                     SSLLogger.fine("KeyUpdate: read key updated");
                 }
@@ -303,13 +303,12 @@
                 return null;
             }
 
-            // Output the handshake message.
-            km.write(hc.handshakeOutput);
-            hc.handshakeOutput.flush();
-
-            // change write cipher
-            hc.conContext.outputRecord.changeWriteCiphers(wc, false);
-            hc.conContext.outputRecord.writeCipher.baseSecret = nplus1;
+            // Output the handshake message and change the write cipher.
+            //
+            // The KeyUpdate handshake message SHALL be delivered in the
+            // changeWriteCiphers() implementation.
+            wc.baseSecret = nplus1;
+            hc.conContext.outputRecord.changeWriteCiphers(wc, km.status.id);
             if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
                 SSLLogger.fine("KeyUpdate: write key updated");
             }
--- a/src/java.base/share/classes/sun/security/ssl/OutputRecord.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/src/java.base/share/classes/sun/security/ssl/OutputRecord.java	Tue Aug 14 18:16:47 2018 -0700
@@ -66,7 +66,7 @@
     int                         fragmentSize;
 
     // closed or not?
-    boolean                     isClosed;
+    volatile boolean            isClosed;
 
     /*
      * Mappings from V3 cipher suite encodings to their pure V2 equivalents.
@@ -76,6 +76,8 @@
         {-1, -1, -1, 0x02, 0x01, -1, 0x04, 0x05, -1, 0x06, 0x07};
     private static final int[] V3toV2CipherMap3 =
         {-1, -1, -1, 0x80, 0x80, -1, 0x80, 0x80, -1, 0x40, 0xC0};
+    private static final byte[] HANDSHAKE_MESSAGE_KEY_UPDATE =
+        {SSLHandshake.KEY_UPDATE.id, 0x00, 0x00, 0x01, 0x00};
 
     OutputRecord(HandshakeHash handshakeHash, SSLWriteCipher writeCipher) {
         this.writeCipher = writeCipher;
@@ -87,7 +89,7 @@
         // Please set packetSize and protocolVersion in the implementation.
     }
 
-    void setVersion(ProtocolVersion protocolVersion) {
+    synchronized void setVersion(ProtocolVersion protocolVersion) {
         this.protocolVersion = protocolVersion;
     }
 
@@ -106,7 +108,7 @@
         return false;
     }
 
-    boolean seqNumIsHuge() {
+    synchronized boolean seqNumIsHuge() {
         return (writeCipher.authenticator != null) &&
                         writeCipher.authenticator.seqNumIsHuge();
     }
@@ -145,8 +147,17 @@
         throw new UnsupportedOperationException();
     }
 
-    void changeWriteCiphers(SSLWriteCipher writeCipher,
+    // Change write ciphers, may use change_cipher_spec record.
+    synchronized void changeWriteCiphers(SSLWriteCipher writeCipher,
             boolean useChangeCipherSpec) throws IOException {
+        if (isClosed()) {
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                SSLLogger.warning("outbound has closed, ignore outbound " +
+                    "change_cipher_spec message");
+            }
+            return;
+        }
+
         if (useChangeCipherSpec) {
             encodeChangeCipherSpec();
         }
@@ -165,15 +176,39 @@
         this.isFirstAppOutputRecord = true;
     }
 
-    void changePacketSize(int packetSize) {
+    // Change write ciphers using key_update handshake message.
+    synchronized void changeWriteCiphers(SSLWriteCipher writeCipher,
+            byte keyUpdateRequest) throws IOException {
+        if (isClosed()) {
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                SSLLogger.warning("outbound has closed, ignore outbound " +
+                    "key_update handshake message");
+            }
+            return;
+        }
+
+        // encode the handshake message, KeyUpdate
+        byte[] hm = HANDSHAKE_MESSAGE_KEY_UPDATE.clone();
+        hm[hm.length - 1] = keyUpdateRequest;
+        encodeHandshake(hm, 0, hm.length);
+        flush();
+
+        // Dispose of any intermediate state in the underlying cipher.
+        writeCipher.dispose();
+
+        this.writeCipher = writeCipher;
+        this.isFirstAppOutputRecord = true;
+    }
+
+    synchronized void changePacketSize(int packetSize) {
         this.packetSize = packetSize;
     }
 
-    void changeFragmentSize(int fragmentSize) {
+    synchronized void changeFragmentSize(int fragmentSize) {
         this.fragmentSize = fragmentSize;
     }
 
-    int getMaxPacketSize() {
+    synchronized int getMaxPacketSize() {
         return packetSize;
     }
 
@@ -194,13 +229,15 @@
 
     @Override
     public synchronized void close() throws IOException {
-        if (!isClosed) {
-            isClosed = true;
-            writeCipher.dispose();
+        if (isClosed) {
+            return;
         }
+
+        isClosed = true;
+        writeCipher.dispose();
     }
 
-    synchronized boolean isClosed() {
+    boolean isClosed() {
         return isClosed;
     }
 
@@ -241,7 +278,7 @@
         }
     }
 
-    static long d13Encrypt(
+    private static long d13Encrypt(
             SSLWriteCipher encCipher, byte contentType, ByteBuffer destination,
             int headerOffset, int dstLim, int headerSize,
             ProtocolVersion protocolVersion) {
@@ -282,7 +319,7 @@
         return Authenticator.toLong(sequenceNumber);
     }
 
-    static long t13Encrypt(
+    private static long t13Encrypt(
             SSLWriteCipher encCipher, byte contentType, ByteBuffer destination,
             int headerOffset, int dstLim, int headerSize,
             ProtocolVersion protocolVersion) {
@@ -321,7 +358,7 @@
         return Authenticator.toLong(sequenceNumber);
     }
 
-    static long t10Encrypt(
+    private static long t10Encrypt(
             SSLWriteCipher encCipher, byte contentType, ByteBuffer destination,
             int headerOffset, int dstLim, int headerSize,
             ProtocolVersion protocolVersion) {
@@ -362,7 +399,7 @@
         private static final byte[] zeros = new byte[16];
     }
 
-    long t13Encrypt(
+    private long t13Encrypt(
             SSLWriteCipher encCipher, byte contentType, int headerSize) {
         if (!encCipher.isNullCipher()) {
             // inner plaintext
@@ -375,9 +412,10 @@
         int contentLen = count - position;
 
         // ensure the capacity
-        int packetSize = encCipher.calculatePacketSize(contentLen, headerSize);
-        if (packetSize > buf.length) {
-            byte[] newBuf = new byte[packetSize];
+        int requiredPacketSize =
+                encCipher.calculatePacketSize(contentLen, headerSize);
+        if (requiredPacketSize > buf.length) {
+            byte[] newBuf = new byte[requiredPacketSize];
             System.arraycopy(buf, 0, newBuf, 0, count);
             buf = newBuf;
         }
@@ -406,16 +444,17 @@
         return Authenticator.toLong(sequenceNumber);
     }
 
-    long t10Encrypt(
+    private long t10Encrypt(
             SSLWriteCipher encCipher, byte contentType, int headerSize) {
         byte[] sequenceNumber = encCipher.authenticator.sequenceNumber();
         int position = headerSize + writeCipher.getExplicitNonceSize();
         int contentLen = count - position;
 
         // ensure the capacity
-        int packetSize = encCipher.calculatePacketSize(contentLen, headerSize);
-        if (packetSize > buf.length) {
-            byte[] newBuf = new byte[packetSize];
+        int requiredPacketSize =
+                encCipher.calculatePacketSize(contentLen, headerSize);
+        if (requiredPacketSize > buf.length) {
+            byte[] newBuf = new byte[requiredPacketSize];
             System.arraycopy(buf, 0, newBuf, 0, count);
             buf = newBuf;
         }
--- a/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java	Tue Aug 14 18:16:47 2018 -0700
@@ -96,6 +96,10 @@
     static final boolean useCompatibilityMode = Utilities.getBooleanProperty(
             "jdk.tls.client.useCompatibilityMode", true);
 
+    // Respond a close_notify alert if receiving close_notify alert.
+    static final boolean acknowledgeCloseNotify  = Utilities.getBooleanProperty(
+            "jdk.tls.acknowledgeCloseNotify", false);
+
 // TODO: Please remove after TLS 1.3 draft interop testing
 // delete me
 static int tls13VN;
--- a/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java	Tue Aug 14 18:16:47 2018 -0700
@@ -155,6 +155,7 @@
         ByteBuffer[] srcs, int srcsOffset, int srcsLength,
         ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws IOException {
 
+        // May need to deliver cached records.
         if (isOutboundDone()) {
             return new SSLEngineResult(
                     Status.CLOSED, getHandshakeStatus(), 0, 0);
@@ -162,8 +163,9 @@
 
         HandshakeContext hc = conContext.handshakeContext;
         HandshakeStatus hsStatus = null;
-        if (!conContext.isNegotiated &&
-                !conContext.isClosed() && !conContext.isBroken) {
+        if (!conContext.isNegotiated && !conContext.isBroken &&
+                !conContext.isInboundClosed() &&
+                !conContext.isOutboundClosed()) {
             conContext.kickstart();
 
             hsStatus = getHandshakeStatus();
@@ -315,7 +317,8 @@
         }
 
         // Is the sequence number is nearly overflow?
-        if (conContext.outputRecord.seqNumIsHuge()) {
+        if (conContext.outputRecord.seqNumIsHuge() ||
+                conContext.outputRecord.writeCipher.atKeyLimit()) {
             hsStatus = tryKeyUpdate(hsStatus);
         }
 
@@ -343,25 +346,29 @@
     }
 
     /**
-     * Try renegotiation or key update for sequence number wrap.
+     * Try key update for sequence number wrap or key usage limit.
      *
      * Note that in order to maintain the handshake status properly, we check
-     * the sequence number after the last record reading/writing process.  As
-     * we request renegotiation or close the connection for wrapped sequence
+     * the sequence number and key usage limit after the last record
+     * reading/writing process.
+     *
+     * As we request renegotiation or close the connection for wrapped sequence
      * number when there is enough sequence number space left to handle a few
      * more records, so the sequence number of the last record cannot be
      * wrapped.
      */
     private HandshakeStatus tryKeyUpdate(
             HandshakeStatus currentHandshakeStatus) throws IOException {
-        // Don't bother to kickstart the renegotiation or key update when the
-        // local is asking for it.
+        // Don't bother to kickstart if handshaking is in progress, or if the
+        // connection is not duplex-open.
         if ((conContext.handshakeContext == null) &&
-                !conContext.isClosed() && !conContext.isBroken) {
+                !conContext.isOutboundClosed() &&
+                !conContext.isInboundClosed() &&
+                !conContext.isBroken) {
             if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
-                SSLLogger.finest("key update to wrap sequence number");
+                SSLLogger.finest("trigger key update");
             }
-            conContext.keyUpdate();
+            beginHandshake();
             return conContext.getHandshakeStatus();
         }
 
@@ -471,8 +478,9 @@
         }
 
         HandshakeStatus hsStatus = null;
-        if (!conContext.isNegotiated &&
-                !conContext.isClosed() && !conContext.isBroken) {
+        if (!conContext.isNegotiated && !conContext.isBroken &&
+                !conContext.isInboundClosed() &&
+                !conContext.isOutboundClosed()) {
             conContext.kickstart();
 
             /*
@@ -677,7 +685,8 @@
             }
 
             // Is the sequence number is nearly overflow?
-            if (conContext.inputRecord.seqNumIsHuge()) {
+            if (conContext.inputRecord.seqNumIsHuge() ||
+                    conContext.inputRecord.readCipher.atKeyLimit()) {
                 pt.handshakeStatus =
                         tryKeyUpdate(pt.handshakeStatus);
             }
@@ -700,16 +709,42 @@
 
     @Override
     public synchronized void closeInbound() throws SSLException {
+        if (isInboundDone()) {
+            return;
+        }
+
+        if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+            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)) {
+
+            conContext.fatal(Alert.INTERNAL_ERROR,
+                    "closing inbound before receiving peer's close_notify");
+        }
+
         conContext.closeInbound();
     }
 
     @Override
     public synchronized boolean isInboundDone() {
-        return conContext.isInboundDone();
+        return conContext.isInboundClosed();
     }
 
     @Override
     public synchronized void closeOutbound() {
+        if (conContext.isOutboundClosed()) {
+            return;
+        }
+
+        if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+            SSLLogger.finest("Closing outbound of SSLEngine");
+        }
+
         conContext.closeOutbound();
     }
 
--- a/src/java.base/share/classes/sun/security/ssl/SSLEngineInputRecord.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/src/java.base/share/classes/sun/security/ssl/SSLEngineInputRecord.java	Tue Aug 14 18:16:47 2018 -0700
@@ -34,8 +34,6 @@
 import javax.net.ssl.SSLHandshakeException;
 import javax.net.ssl.SSLProtocolException;
 import sun.security.ssl.SSLCipher.SSLReadCipher;
-import sun.security.ssl.KeyUpdate.KeyUpdateMessage;
-import sun.security.ssl.KeyUpdate.KeyUpdateRequest;
 
 /**
  * {@code InputRecord} implementation for {@code SSLEngine}.
@@ -300,7 +298,7 @@
                     handshakeBuffer.put(handshakeFrag);
                     handshakeBuffer.rewind();
                     break;
-                } if (remaining == handshakeMessageLen) {
+                } else if (remaining == handshakeMessageLen) {
                     if (handshakeHash.isHashable(handshakeType)) {
                         handshakeHash.receive(handshakeFrag);
                     }
@@ -333,20 +331,6 @@
             return plaintexts.toArray(new Plaintext[0]);
         }
 
-        // KeyLimit check during application data.
-        // atKeyLimit() inactive when limits not checked, tc set when limits
-        // are active.
-
-        if (readCipher.atKeyLimit()) {
-            if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
-                SSLLogger.fine("KeyUpdate: triggered, read side.");
-            }
-
-            PostHandshakeContext p = new PostHandshakeContext(tc);
-            KeyUpdate.handshakeProducer.produce(p,
-                    new KeyUpdateMessage(p, KeyUpdateRequest.REQUESTED));
-        }
-
         return new Plaintext[] {
             new Plaintext(contentType,
                 majorVersion, minorVersion, -1, -1L, fragment)
--- a/src/java.base/share/classes/sun/security/ssl/SSLEngineOutputRecord.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/src/java.base/share/classes/sun/security/ssl/SSLEngineOutputRecord.java	Tue Aug 14 18:16:47 2018 -0700
@@ -31,8 +31,6 @@
 import javax.net.ssl.SSLHandshakeException;
 
 import sun.security.ssl.SSLCipher.SSLWriteCipher;
-import sun.security.ssl.KeyUpdate.KeyUpdateMessage;
-import sun.security.ssl.KeyUpdate.KeyUpdateRequest;
 
 /**
  * {@code OutputRecord} implementation for {@code SSLEngine}.
@@ -43,7 +41,7 @@
     private boolean isTalkingToV2 = false;      // SSLv2Hello
     private ByteBuffer v2ClientHello = null;    // SSLv2Hello
 
-    private boolean isCloseWaiting = false;
+    private volatile boolean isCloseWaiting = false;
 
     SSLEngineOutputRecord(HandshakeHash handshakeHash) {
         super(handshakeHash, SSLWriteCipher.nullTlsWriteCipher());
@@ -63,8 +61,20 @@
         }
     }
 
+    boolean isClosed() {
+        return isClosed || isCloseWaiting;
+    }
+
     @Override
     void encodeAlert(byte level, byte description) throws IOException {
+        if (isClosed()) {
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                SSLLogger.warning("outbound has closed, ignore outbound " +
+                    "alert message: " + Alert.nameOf(description));
+            }
+            return;
+        }
+
         if (fragmenter == null) {
            fragmenter = new HandshakeFragment();
         }
@@ -75,6 +85,14 @@
     @Override
     void encodeHandshake(byte[] source,
             int offset, int length) throws IOException {
+        if (isClosed()) {
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                SSLLogger.warning("outbound has closed, ignore outbound " +
+                        "handshake message",
+                        ByteBuffer.wrap(source, offset, length));
+            }
+            return;
+        }
 
         if (fragmenter == null) {
            fragmenter = new HandshakeFragment();
@@ -114,6 +132,14 @@
 
     @Override
     void encodeChangeCipherSpec() throws IOException {
+        if (isClosed()) {
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                SSLLogger.warning("outbound has closed, ignore outbound " +
+                    "change_cipher_spec message");
+            }
+            return;
+        }
+
         if (fragmenter == null) {
            fragmenter = new HandshakeFragment();
         }
@@ -129,6 +155,23 @@
     Ciphertext encode(
         ByteBuffer[] srcs, int srcsOffset, int srcsLength,
         ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws IOException {
+
+        if (isClosed) {
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                SSLLogger.warning("outbound has closed, ignore outbound " +
+                    "application data or cached messages");
+            }
+
+            return null;
+        } else if (isCloseWaiting) {
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                SSLLogger.warning("outbound has closed, ignore outbound " +
+                    "application data");
+            }
+
+            srcs = null;    // use no application data.
+        }
+
         return encode(srcs, srcsOffset, srcsLength, dsts[0]);
     }
 
@@ -248,25 +291,14 @@
             if (isFirstAppOutputRecord) {
                 isFirstAppOutputRecord = false;
             }
-
-            // atKeyLimit() inactive when limits not checked, tc set when limits
-            // are active.
-            if (writeCipher.atKeyLimit()) {
-                if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
-                    SSLLogger.fine("KeyUpdate: triggered, write side.");
-                }
-
-                PostHandshakeContext p = new PostHandshakeContext(tc);
-                KeyUpdate.handshakeProducer.produce(p,
-                    new KeyUpdateMessage(p, KeyUpdateRequest.REQUESTED));
-            }
         }
 
         return new Ciphertext(ContentType.APPLICATION_DATA.id,
                 SSLHandshake.NOT_APPLICABLE.id, recordSN);
     }
 
-    private Ciphertext acquireCiphertext(ByteBuffer destination) throws IOException {
+    private Ciphertext acquireCiphertext(
+            ByteBuffer destination) throws IOException {
         if (isTalkingToV2) {              // SSLv2Hello
             // We don't support SSLv2.  Send an SSLv2 error message
             // so that the connection can be closed gracefully.
@@ -517,7 +549,7 @@
 
         boolean hasAlert() {
             for (RecordMemo memo : handshakeMemos) {
-                if (memo.contentType ==  ContentType.ALERT.id) {
+                if (memo.contentType == ContentType.ALERT.id) {
                     return true;
                 }
             }
--- a/src/java.base/share/classes/sun/security/ssl/SSLHandshake.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/src/java.base/share/classes/sun/security/ssl/SSLHandshake.java	Tue Aug 14 18:16:47 2018 -0700
@@ -366,6 +366,7 @@
     SSLHandshake(byte id, String name,
         Map.Entry<SSLConsumer, ProtocolVersion[]>[] handshakeConsumers,
         Map.Entry<HandshakeProducer, ProtocolVersion[]>[] handshakeProducers) {
+
         this(id, name, handshakeConsumers, handshakeProducers,
                 (Map.Entry<HandshakeAbsence, ProtocolVersion[]>[])(
                         new Map.Entry[0]));
@@ -375,6 +376,7 @@
         Map.Entry<SSLConsumer, ProtocolVersion[]>[] handshakeConsumers,
         Map.Entry<HandshakeProducer, ProtocolVersion[]>[] handshakeProducers,
         Map.Entry<HandshakeAbsence, ProtocolVersion[]>[] handshakeAbsence) {
+
         this.id = id;
         this.name = name;
         this.handshakeConsumers = handshakeConsumers;
@@ -404,7 +406,12 @@
         ProtocolVersion protocolVersion;
         if ((hc.negotiatedProtocol == null) ||
                 (hc.negotiatedProtocol == ProtocolVersion.NONE)) {
-            protocolVersion = hc.maximumActiveProtocol;
+            if (hc.conContext.isNegotiated &&
+                    hc.conContext.protocolVersion != ProtocolVersion.NONE) {
+                protocolVersion = hc.conContext.protocolVersion;
+            } else {
+                protocolVersion = hc.maximumActiveProtocol;
+            }
         } else {
             protocolVersion = hc.negotiatedProtocol;
         }
@@ -444,7 +451,12 @@
         ProtocolVersion protocolVersion;
         if ((hc.negotiatedProtocol == null) ||
                 (hc.negotiatedProtocol == ProtocolVersion.NONE)) {
-            protocolVersion = hc.maximumActiveProtocol;
+            if (hc.conContext.isNegotiated &&
+                    hc.conContext.protocolVersion != ProtocolVersion.NONE) {
+                protocolVersion = hc.conContext.protocolVersion;
+            } else {
+                protocolVersion = hc.maximumActiveProtocol;
+            }
         } else {
             protocolVersion = hc.negotiatedProtocol;
         }
--- a/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java	Tue Aug 14 18:16:47 2018 -0700
@@ -82,7 +82,7 @@
     private String                  peerHost;
     private boolean                 autoClose;
     private boolean                 isConnected = false;
-    private boolean                 tlsIsClosed = false;
+    private volatile boolean        tlsIsClosed = false;
 
     /*
      * Is the local name service trustworthy?
@@ -325,7 +325,7 @@
     }
 
     @Override
-    public synchronized SSLSession getSession() {
+    public SSLSession getSession() {
         try {
             // start handshaking, if failed, the connection will be closed.
             ensureNegotiated();
@@ -343,7 +343,11 @@
     @Override
     public synchronized SSLSession getHandshakeSession() {
         if (conContext.handshakeContext != null) {
-            return conContext.handshakeContext.handshakeSession;
+            synchronized (this) {
+                if (conContext.handshakeContext != null) {
+                    return conContext.handshakeContext.handshakeSession;
+                }
+            }
         }
 
         return null;
@@ -370,23 +374,39 @@
     }
 
     @Override
-    public synchronized void startHandshake() throws IOException {
-        checkWrite();
-        try {
-            conContext.kickstart();
+    public void startHandshake() throws IOException {
+        if (!isConnected) {
+            throw new SocketException("Socket is not connected");
+        }
+
+        if (conContext.isBroken || conContext.isInboundClosed() ||
+                conContext.isOutboundClosed()) {
+            throw new SocketException("Socket has been closed or broken");
+        }
+
+        synchronized (conContext) {     // handshake lock
+            // double check the context status
+            if (conContext.isBroken || conContext.isInboundClosed() ||
+                    conContext.isOutboundClosed()) {
+                throw new SocketException("Socket has been closed or broken");
+            }
 
-            // All initial handshaking goes through this operation until we
-            // have a valid SSL connection.
-            //
-            // Handle handshake messages only, need no application data.
-            if (!conContext.isNegotiated) {
-                readRecord();
+            try {
+                conContext.kickstart();
+
+                // All initial handshaking goes through this operation until we
+                // have a valid SSL connection.
+                //
+                // Handle handshake messages only, need no application data.
+                if (!conContext.isNegotiated) {
+                    readHandshakeRecord();
+                }
+            } catch (IOException ioe) {
+                conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                    "Couldn't kickstart handshaking", ioe);
+            } catch (Exception oe) {    // including RuntimeException
+                handleException(oe);
             }
-        } catch (IOException ioe) {
-            conContext.fatal(Alert.HANDSHAKE_FAILURE,
-                "Couldn't kickstart handshaking", ioe);
-        } catch (Exception oe) {    // including RuntimeException
-            handleException(oe);
         }
     }
 
@@ -437,44 +457,264 @@
     }
 
     @Override
-    public synchronized boolean isClosed() {
-        return tlsIsClosed && conContext.isClosed();
+    public boolean isClosed() {
+        return tlsIsClosed;
     }
 
+    // Please don't synchronized this method.  Otherwise, the read and close
+    // locks may be deadlocked.
     @Override
-    public synchronized void close() throws IOException {
+    public void close() throws IOException {
+        if (tlsIsClosed) {
+            return;
+        }
+
+        if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+            SSLLogger.fine("duplex close of SSLSocket");
+        }
+
         try {
-            conContext.close();
+            // shutdown output bound, which may have been closed previously.
+            if (!isOutputShutdown()) {
+                duplexCloseOutput();
+            }
+
+            // shutdown input bound, which may have been closed previously.
+            if (!isInputShutdown()) {
+                duplexCloseInput();
+            }
+
+            if (!isClosed()) {
+                // close the connection directly
+                closeSocket(false);
+            }
         } catch (IOException ioe) {
             // ignore the exception
             if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
-                SSLLogger.warning("connection context closure failed", ioe);
+                SSLLogger.warning("SSLSocket duplex close failed", ioe);
             }
         } finally {
             tlsIsClosed = true;
         }
     }
 
+    /**
+     * Duplex close, start from closing outbound.
+     *
+     * For TLS 1.2 [RFC 5246], unless some other fatal alert has been
+     * transmitted, each party is required to send a close_notify alert
+     * before closing the write side of the connection.  The other party
+     * MUST respond with a close_notify alert of its own and close down
+     * the connection immediately, discarding any pending writes.  It is
+     * not required for the initiator of the close to wait for the responding
+     * close_notify alert before closing the read side of the connection.
+     *
+     * For TLS 1.3, Each party MUST send a close_notify alert before
+     * closing its write side of the connection, unless it has already sent
+     * some error alert.  This does not have any effect on its read side of
+     * the connection.  Both parties need not wait to receive a close_notify
+     * alert before closing their read side of the connection, though doing
+     * so would introduce the possibility of truncation.
+     *
+     * In order to support user initiated duplex-close for TLS 1.3 connections,
+     * the user_canceled alert is used together with the close_notify alert.
+     */
+    private void duplexCloseOutput() throws IOException {
+        boolean useUserCanceled = false;
+        boolean hasCloseReceipt = false;
+        if (conContext.isNegotiated) {
+            if (!conContext.protocolVersion.useTLS13PlusSpec()) {
+                hasCloseReceipt = true;
+            } else {
+                // Use a user_canceled alert for TLS 1.3 duplex close.
+                useUserCanceled = true;
+            }
+        } else if (conContext.handshakeContext != null) {   // initial handshake
+            // Use user_canceled alert regardless the protocol versions.
+            useUserCanceled = true;
+
+            // The protocol version may have been negotiated.
+            ProtocolVersion pv = conContext.handshakeContext.negotiatedProtocol;
+            if (pv == null || (!pv.useTLS13PlusSpec())) {
+                hasCloseReceipt = true;
+            }
+        }
+
+        // Need a lock here so that the user_canceled alert and the
+        // close_notify alert can be delivered together.
+        try {
+            synchronized (conContext.outputRecord) {
+                // send a user_canceled alert if needed.
+                if (useUserCanceled) {
+                    conContext.warning(Alert.USER_CANCELED);
+                }
+
+                // send a close_notify alert
+                conContext.warning(Alert.CLOSE_NOTIFY);
+            }
+        } finally {
+            if (!conContext.isOutboundClosed()) {
+                conContext.outputRecord.close();
+            }
+
+            if ((autoClose || !isLayered()) && !super.isOutputShutdown()) {
+                super.shutdownOutput();
+            }
+        }
+
+        if (!isInputShutdown()) {
+            bruteForceCloseInput(hasCloseReceipt);
+        }
+    }
+
+    /**
+     * Duplex close, start from closing inbound.
+     *
+     * This method should only be called when the outbound has been closed,
+     * but the inbound is still open.
+     */
+    private void duplexCloseInput() throws IOException {
+        boolean hasCloseReceipt = false;
+        if (conContext.isNegotiated &&
+                !conContext.protocolVersion.useTLS13PlusSpec()) {
+            hasCloseReceipt = true;
+        }   // No close receipt if handshake has no completed.
+
+        bruteForceCloseInput(hasCloseReceipt);
+    }
+
+    /**
+     * Brute force close the input bound.
+     *
+     * This method should only be called when the outbound has been closed,
+     * but the inbound is still open.
+     */
+    private void bruteForceCloseInput(
+            boolean hasCloseReceipt) throws IOException {
+        if (hasCloseReceipt) {
+            // It is not required for the initiator of the close to wait for
+            // the responding close_notify alert before closing the read side
+            // of the connection.  However, if the application protocol using
+            // TLS provides that any data may be carried over the underlying
+            // transport after the TLS connection is closed, the TLS
+            // implementation MUST receive a "close_notify" alert before
+            // indicating end-of-data to the application-layer.
+            try {
+                this.shutdown();
+            } finally {
+                if (!isInputShutdown()) {
+                    shutdownInput(false);
+                }
+            }
+        } else {
+            if (!conContext.isInboundClosed()) {
+                conContext.inputRecord.close();
+            }
+
+            if ((autoClose || !isLayered()) && !super.isInputShutdown()) {
+                super.shutdownInput();
+            }
+        }
+    }
+
+    // Please don't synchronized this method.  Otherwise, the read and close
+    // locks may be deadlocked.
+    @Override
+    public void shutdownInput() throws IOException {
+        shutdownInput(true);
+    }
+
+    // It is not required to check the close_notify receipt unless an
+    // application call shutdownInput() explicitly.
+    private void shutdownInput(
+            boolean checkCloseNotify) throws IOException {
+        if (isInputShutdown()) {
+            return;
+        }
+
+        if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+            SSLLogger.fine("close inbound of SSLSocket");
+        }
+
+        // Is it ready to close inbound?
+        //
+        // No need to throw exception if the initial handshake is not started.
+        if (checkCloseNotify && !conContext.isInputCloseNotified &&
+            (conContext.isNegotiated || conContext.handshakeContext != null)) {
+
+            conContext.fatal(Alert.INTERNAL_ERROR,
+                    "closing inbound before receiving peer's close_notify");
+        }
+
+        conContext.closeInbound();
+        if ((autoClose || !isLayered()) && !super.isInputShutdown()) {
+            super.shutdownInput();
+        }
+    }
+
+    @Override
+    public boolean isInputShutdown() {
+        return conContext.isInboundClosed() &&
+                ((autoClose || !isLayered()) ? super.isInputShutdown(): true);
+    }
+
+    // Please don't synchronized this method.  Otherwise, the read and close
+    // locks may be deadlocked.
+    @Override
+    public void shutdownOutput() throws IOException {
+        if (isOutputShutdown()) {
+            return;
+        }
+
+        if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+            SSLLogger.fine("close outbound of SSLSocket");
+        }
+        conContext.closeOutbound();
+
+        if ((autoClose || !isLayered()) && !super.isOutputShutdown()) {
+            super.shutdownOutput();
+        }
+    }
+
+    @Override
+    public boolean isOutputShutdown() {
+        return conContext.isOutboundClosed() &&
+                ((autoClose || !isLayered()) ? super.isOutputShutdown(): true);
+    }
+
     @Override
     public synchronized InputStream getInputStream() throws IOException {
-        if (isClosed() || conContext.isInboundDone()) {
-            throw new SocketException("Socket or inbound is closed");
+        if (isClosed()) {
+            throw new SocketException("Socket is closed");
         }
 
         if (!isConnected) {
             throw new SocketException("Socket is not connected");
         }
 
+        if (conContext.isInboundClosed() || isInputShutdown()) {
+            throw new SocketException("Socket input is already shutdown");
+        }
+
         return appInput;
     }
 
-    private synchronized void ensureNegotiated() throws IOException {
-        if (conContext.isNegotiated ||
-                conContext.isClosed() || conContext.isBroken) {
+    private void ensureNegotiated() throws IOException {
+        if (conContext.isNegotiated || conContext.isBroken ||
+                conContext.isInboundClosed() || conContext.isOutboundClosed()) {
             return;
         }
 
-        startHandshake();
+        synchronized (conContext) {     // handshake lock
+            // double check the context status
+            if (conContext.isNegotiated || conContext.isBroken ||
+                    conContext.isInboundClosed() ||
+                    conContext.isOutboundClosed()) {
+                return;
+            }
+
+            startHandshake();
+        }
     }
 
     /**
@@ -489,7 +729,7 @@
         private ByteBuffer buffer;
 
         // Is application data available in the stream?
-        private boolean appDataIsAvailable;
+        private volatile boolean appDataIsAvailable;
 
         AppInputStream() {
             this.appDataIsAvailable = false;
@@ -514,7 +754,7 @@
          * Read a single byte, returning -1 on non-fault EOF status.
          */
         @Override
-        public synchronized int read() throws IOException {
+        public int read() throws IOException {
             int n = read(oneByte, 0, 1);
             if (n <= 0) {   // EOF
                 return -1;
@@ -536,7 +776,7 @@
          * and returning "-1" on non-fault EOF status.
          */
         @Override
-        public synchronized int read(byte[] b, int off, int len)
+        public int read(byte[] b, int off, int len)
                 throws IOException {
             if (b == null) {
                 throw new NullPointerException("the target buffer is null");
@@ -553,75 +793,54 @@
             }
 
             // start handshaking if the connection has not been negotiated.
-            if (!conContext.isNegotiated &&
-                    !conContext.isClosed() && !conContext.isBroken) {
+            if (!conContext.isNegotiated && !conContext.isBroken &&
+                    !conContext.isInboundClosed() &&
+                    !conContext.isOutboundClosed()) {
                 ensureNegotiated();
             }
 
+            // Check if the Socket is invalid (error or closed).
+            if (!conContext.isNegotiated ||
+                    conContext.isBroken || conContext.isInboundClosed()) {
+                throw new SocketException("Connection or inbound has closed");
+            }
+
             // Read the available bytes at first.
-            int remains = available();
-            if (remains > 0) {
-                int howmany = Math.min(remains, len);
-                buffer.get(b, off, howmany);
-
-                return howmany;
-            }
+            //
+            // Note that the receiving and processing of post-handshake message
+            // are also synchronized with the read lock.
+            synchronized (this) {
+                int remains = available();
+                if (remains > 0) {
+                    int howmany = Math.min(remains, len);
+                    buffer.get(b, off, howmany);
 
-            appDataIsAvailable = false;
-            int volume = 0;
-            try {
-                /*
-                 * Read data if needed ... notice that the connection
-                 * guarantees that handshake, alert, and change cipher spec
-                 * data streams are handled as they arrive, so we never
-                 * see them here.
-                 */
-                while (volume == 0) {
-                    // Clear the buffer for a new record reading.
-                    buffer.clear();
+                    return howmany;
+                }
 
-                    // grow the buffer if needed
-                    int inLen = conContext.inputRecord.bytesInCompletePacket();
-                    if (inLen < 0) {    // EOF
-                        handleEOF(null);
-
-                        // if no exception thrown
+                appDataIsAvailable = false;
+                try {
+                    ByteBuffer bb = readApplicationRecord(buffer);
+                    if (bb == null) {   // EOF
                         return -1;
+                    } else {
+                        // The buffer may be reallocated for bigger capacity.
+                        buffer = bb;
                     }
 
-                    // Is this packet bigger than SSL/TLS normally allows?
-                    if (inLen > SSLRecord.maxLargeRecordSize) {
-                        throw new SSLProtocolException(
-                                "Illegal packet size: " + inLen);
-                    }
-
-                    if (inLen > buffer.remaining()) {
-                        buffer = ByteBuffer.allocate(inLen);
-                    }
+                    bb.flip();
+                    int volume = Math.min(len, bb.remaining());
+                    buffer.get(b, off, volume);
+                    appDataIsAvailable = true;
 
-                    volume = readRecord(buffer);
-                    buffer.flip();
-                    if (volume < 0) {   // EOF
-                        // treat like receiving a close_notify warning message.
-                        conContext.isInputCloseNotified = true;
-                        conContext.closeInbound();
-                        return -1;
-                    } else if (volume > 0) {
-                        appDataIsAvailable = true;
-                        break;
-                    }
+                    return volume;
+                } catch (Exception e) {   // including RuntimeException
+                    // shutdown and rethrow (wrapped) exception as appropriate
+                    handleException(e);
+
+                    // dummy for compiler
+                    return -1;
                 }
-
-                // file the destination buffer
-                int howmany = Math.min(len, volume);
-                buffer.get(b, off, howmany);
-                return howmany;
-            } catch (Exception e) {   // including RuntimeException
-                // shutdown and rethrow (wrapped) exception as appropriate
-                handleException(e);
-
-                // dummy for compiler
-                return -1;
             }
         }
 
@@ -658,20 +877,53 @@
                 SSLLogger.finest("Closing input stream");
             }
 
-            conContext.closeInbound();
+            try {
+                shutdownInput(false);
+            } catch (IOException ioe) {
+                // ignore the exception
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                    SSLLogger.warning("input stream close failed", ioe);
+                }
+            }
+        }
+
+        /**
+         * Return whether we have reached end-of-file.
+         *
+         * If the socket is not connected, has been shutdown because of an error
+         * or has been closed, throw an Exception.
+         */
+        private boolean checkEOF() throws IOException {
+            if (conContext.isInboundClosed()) {
+                return true;
+            } else if (conContext.isInputCloseNotified || conContext.isBroken) {
+                if (conContext.closeReason == null) {
+                    return true;
+                } else {
+                    throw new SSLException(
+                        "Connection has closed: " + conContext.closeReason,
+                        conContext.closeReason);
+                }
+            }
+
+            return false;
         }
     }
 
     @Override
     public synchronized OutputStream getOutputStream() throws IOException {
-        if (isClosed() || conContext.isOutboundDone()) {
-            throw new SocketException("Socket or outbound is closed");
+        if (isClosed()) {
+            throw new SocketException("Socket is closed");
         }
 
         if (!isConnected) {
             throw new SocketException("Socket is not connected");
         }
 
+        if (conContext.isOutboundDone() || isOutputShutdown()) {
+            throw new SocketException("Socket output is already shutdown");
+        }
+
         return appOutput;
     }
 
@@ -691,7 +943,7 @@
         }
 
         @Override
-        public synchronized void write(byte[] b,
+        public void write(byte[] b,
                 int off, int len) throws IOException {
             if (b == null) {
                 throw new NullPointerException("the source buffer is null");
@@ -700,25 +952,47 @@
                         "buffer length: " + b.length + ", offset; " + off +
                         ", bytes to read:" + len);
             } else if (len == 0) {
+                //
+                // Don't bother to really write empty records.  We went this
+                // far to drive the handshake machinery, for correctness; not
+                // writing empty records improves performance by cutting CPU
+                // time and network resource usage.  However, some protocol
+                // implementations are fragile and don't like to see empty
+                // records, so this also increases robustness.
+                //
                 return;
             }
 
-            // start handshaking if the connection has not been negotiated.
-            if (!conContext.isNegotiated &&
-                    !conContext.isClosed() && !conContext.isBroken) {
+            // Start handshaking if the connection has not been negotiated.
+            if (!conContext.isNegotiated && !conContext.isBroken &&
+                    !conContext.isInboundClosed() &&
+                    !conContext.isOutboundClosed()) {
                 ensureNegotiated();
             }
 
-            // check if the Socket is invalid (error or closed)
-            checkWrite();
+            // Check if the Socket is invalid (error or closed).
+            if (!conContext.isNegotiated ||
+                    conContext.isBroken || conContext.isOutboundClosed()) {
+                throw new SocketException("Connection or outbound has closed");
+            }
+
+            //
 
             // Delegate the writing to the underlying socket.
             try {
-                writeRecord(b, off, len);
-                checkWrite();
-            } catch (IOException ioe) {
-                // shutdown and rethrow (wrapped) exception as appropriate
-                handleException(ioe);
+                conContext.outputRecord.deliver(b, off, len);
+            } catch (SSLHandshakeException she) {
+                // may be record sequence number overflow
+                conContext.fatal(Alert.HANDSHAKE_FAILURE, she);
+            } catch (IOException e) {
+                conContext.fatal(Alert.UNEXPECTED_MESSAGE, e);
+            }
+
+            // Is the sequence number is nearly overflow, or has the key usage
+            // limit been reached?
+            if (conContext.outputRecord.seqNumIsHuge() ||
+                    conContext.outputRecord.writeCipher.atKeyLimit()) {
+                tryKeyUpdate();
             }
         }
 
@@ -728,7 +1002,14 @@
                 SSLLogger.finest("Closing output stream");
             }
 
-            conContext.closeOutbound();
+            try {
+                shutdownOutput();
+            } catch (IOException ioe) {
+                // ignore the exception
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                    SSLLogger.warning("output stream close failed", ioe);
+                }
+            }
         }
     }
 
@@ -773,39 +1054,11 @@
         return conContext.sslConfig.socketAPSelector;
     }
 
-    private synchronized void writeRecord(byte[] source,
-            int offset, int length) throws IOException {
-        if (conContext.isOutboundDone()) {
-            throw new SocketException("Socket or outbound closed");
-        }
-
-        //
-        // Don't bother to really write empty records.  We went this
-        // far to drive the handshake machinery, for correctness; not
-        // writing empty records improves performance by cutting CPU
-        // time and network resource usage.  However, some protocol
-        // implementations are fragile and don't like to see empty
-        // records, so this also increases robustness.
-        //
-        if (length > 0) {
-            try {
-                conContext.outputRecord.deliver(source, offset, length);
-            } catch (SSLHandshakeException she) {
-                // may be record sequence number overflow
-                conContext.fatal(Alert.HANDSHAKE_FAILURE, she);
-            } catch (IOException e) {
-                conContext.fatal(Alert.UNEXPECTED_MESSAGE, e);
-            }
-        }
-
-        // Is the sequence number is nearly overflow?
-        if (conContext.outputRecord.seqNumIsHuge()) {
-            tryKeyUpdate();
-        }
-    }
-
-    private synchronized int readRecord() throws IOException {
-        while (!conContext.isInboundDone()) {
+    /**
+     * Read the initial handshake records.
+     */
+    private int readHandshakeRecord() throws IOException {
+        while (!conContext.isInboundClosed()) {
             try {
                 Plaintext plainText = decode(null);
                 if ((plainText.contentType == ContentType.HANDSHAKE.id) &&
@@ -816,7 +1069,7 @@
                 throw ssle;
             } catch (IOException ioe) {
                 if (!(ioe instanceof SSLException)) {
-                    throw new SSLException("readRecord", ioe);
+                    throw new SSLException("readHandshakeRecord", ioe);
                 } else {
                     throw ioe;
                 }
@@ -826,8 +1079,20 @@
         return -1;
     }
 
-    private synchronized int readRecord(ByteBuffer buffer) throws IOException {
-        while (!conContext.isInboundDone()) {
+    /**
+     * Read application data record. Used by AppInputStream only, but defined
+     * here so as to use the socket level synchronization.
+     *
+     * Note that the connection guarantees that handshake, alert, and change
+     * cipher spec data streams are handled as they arrive, so we never see
+     * them here.
+     *
+     * Note: Please be careful about the synchronization, and don't use this
+     * method other than in the AppInputStream class!
+     */
+    private ByteBuffer readApplicationRecord(
+            ByteBuffer buffer) throws IOException {
+        while (!conContext.isInboundClosed()) {
             /*
              * clean the buffer and check if it is too small, e.g. because
              * the AppInputStream did not have the chance to see the
@@ -841,23 +1106,33 @@
                 handleEOF(null);
 
                 // if no exception thrown
-                return -1;
+                return null;
             }
 
-            if (buffer.remaining() < inLen) {
-                return 0;
+            // Is this packet bigger than SSL/TLS normally allows?
+            if (inLen > SSLRecord.maxLargeRecordSize) {
+                throw new SSLProtocolException(
+                        "Illegal packet size: " + inLen);
+            }
+
+            if (inLen > buffer.remaining()) {
+                buffer = ByteBuffer.allocate(inLen);
             }
 
             try {
-                Plaintext plainText = decode(buffer);
-                if (plainText.contentType == ContentType.APPLICATION_DATA.id) {
-                    return buffer.position();
+                Plaintext plainText;
+                synchronized (this) {
+                    plainText = decode(buffer);
+                }
+                if (plainText.contentType == ContentType.APPLICATION_DATA.id &&
+                        buffer.position() > 0) {
+                    return buffer;
                 }
             } catch (SSLException ssle) {
                 throw ssle;
             } catch (IOException ioe) {
                 if (!(ioe instanceof SSLException)) {
-                    throw new SSLException("readRecord", ioe);
+                    throw new SSLException("readApplicationRecord", ioe);
                 } else {
                     throw ioe;
                 }
@@ -867,7 +1142,7 @@
         //
         // couldn't read, due to some kind of error
         //
-        return -1;
+        return null;
     }
 
     private Plaintext decode(ByteBuffer destination) throws IOException {
@@ -887,7 +1162,8 @@
 
         // Is the sequence number is nearly overflow?
         if (plainText != Plaintext.PLAINTEXT_NULL &&
-                conContext.inputRecord.seqNumIsHuge()) {
+                (conContext.inputRecord.seqNumIsHuge() ||
+                conContext.inputRecord.readCipher.atKeyLimit())) {
             tryKeyUpdate();
         }
 
@@ -895,69 +1171,28 @@
     }
 
     /**
-     * Try renegotiation or key update for sequence number wrap.
+     * Try key update for sequence number wrap or key usage limit.
      *
      * Note that in order to maintain the handshake status properly, we check
-     * the sequence number after the last record reading/writing process.  As
-     * we request renegotiation or close the connection for wrapped sequence
+     * the sequence number and key usage limit after the last record
+     * reading/writing process.
+     *
+     * As we request renegotiation or close the connection for wrapped sequence
      * number when there is enough sequence number space left to handle a few
      * more records, so the sequence number of the last record cannot be
      * wrapped.
      */
     private void tryKeyUpdate() throws IOException {
-        // Don't bother to kickstart the renegotiation or key update when the
-        // local is asking for it.
+        // Don't bother to kickstart if handshaking is in progress, or if the
+        // connection is not duplex-open.
         if ((conContext.handshakeContext == null) &&
-                !conContext.isClosed() && !conContext.isBroken) {
+                !conContext.isOutboundClosed() &&
+                !conContext.isInboundClosed() &&
+                !conContext.isBroken) {
             if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
-                SSLLogger.finest("key update to wrap sequence number");
+                SSLLogger.finest("trigger key update");
             }
-            conContext.keyUpdate();
-        }
-    }
-
-    private void closeSocket(boolean selfInitiated) throws IOException {
-        if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
-            SSLLogger.fine("close the ssl connection " +
-                (selfInitiated ? "(initiative)" : "(passive)"));
-        }
-
-        if (autoClose || !isLayered()) {
-            super.close();
-        } else if (selfInitiated) {
-            // wait for close_notify alert to clear input stream.
-            waitForClose();
-        }
-    }
-
-   /**
-    * Wait for close_notify alert for a graceful closure.
-    *
-    * [RFC 5246] If the application protocol using TLS provides that any
-    * data may be carried over the underlying transport after the TLS
-    * connection is closed, the TLS implementation must receive the responding
-    * close_notify alert before indicating to the application layer that
-    * the TLS connection has ended.  If the application protocol will not
-    * transfer any additional data, but will only close the underlying
-    * transport connection, then the implementation MAY choose to close the
-    * transport without waiting for the responding close_notify.
-    */
-    private void waitForClose() throws IOException {
-        if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
-            SSLLogger.fine("wait for close_notify or alert");
-        }
-
-        while (!conContext.isInboundDone()) {
-            try {
-                Plaintext plainText = decode(null);
-                // discard and continue
-                if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
-                    SSLLogger.finest(
-                        "discard plaintext while waiting for close", plainText);
-                }
-            } catch (Exception e) {   // including RuntimeException
-                handleException(e);
-            }
+            startHandshake();
         }
     }
 
@@ -1041,41 +1276,6 @@
     }
 
     /**
-     * Return whether we have reached end-of-file.
-     *
-     * If the socket is not connected, has been shutdown because of an error
-     * or has been closed, throw an Exception.
-     */
-    synchronized boolean checkEOF() throws IOException {
-        if (conContext.isClosed()) {
-            return true;
-        } else if (conContext.isInputCloseNotified || conContext.isBroken) {
-            if (conContext.closeReason == null) {
-                return true;
-            } else {
-                throw new SSLException(
-                    "Connection has been shutdown: " + conContext.closeReason,
-                    conContext.closeReason);
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * Check if we can write data to this socket.
-     */
-    synchronized void checkWrite() throws IOException {
-        if (checkEOF() || conContext.isOutboundClosed()) {
-            // we are at EOF, write must throw Exception
-            throw new SocketException("Connection closed");
-        }
-        if (!isConnected) {
-            throw new SocketException("Socket is not connected");
-        }
-    }
-
-    /**
      * Handle an exception.
      *
      * This method is called by top level exception handlers (in read(),
@@ -1132,7 +1332,7 @@
         } else {
             // treat as if we had received a close_notify
             conContext.isInputCloseNotified = true;
-            conContext.transport.shutdown();
+            shutdownInput();
 
             return Plaintext.PLAINTEXT_NULL;
         }
@@ -1174,4 +1374,51 @@
             }
         }
     }
+
+    private void closeSocket(boolean selfInitiated) throws IOException {
+        if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+            SSLLogger.fine("close the SSL connection " +
+                (selfInitiated ? "(initiative)" : "(passive)"));
+        }
+
+        if (autoClose || !isLayered()) {
+            super.close();
+        } else if (selfInitiated) {
+            if (!conContext.isInboundClosed() && !isInputShutdown()) {
+                // wait for close_notify alert to clear input stream.
+                waitForClose();
+            }
+        }
+    }
+
+   /**
+    * Wait for close_notify alert for a graceful closure.
+    *
+    * [RFC 5246] If the application protocol using TLS provides that any
+    * data may be carried over the underlying transport after the TLS
+    * connection is closed, the TLS implementation must receive the responding
+    * close_notify alert before indicating to the application layer that
+    * the TLS connection has ended.  If the application protocol will not
+    * transfer any additional data, but will only close the underlying
+    * transport connection, then the implementation MAY choose to close the
+    * transport without waiting for the responding close_notify.
+    */
+    private void waitForClose() throws IOException {
+        if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+            SSLLogger.fine("wait for close_notify or alert");
+        }
+
+        while (!conContext.isInboundClosed()) {
+            try {
+                Plaintext plainText = decode(null);
+                // discard and continue
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                    SSLLogger.finest(
+                        "discard plaintext while waiting for close", plainText);
+                }
+            } catch (Exception e) {   // including RuntimeException
+                handleException(e);
+            }
+        }
+    }
 }
--- a/src/java.base/share/classes/sun/security/ssl/SSLSocketInputRecord.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/src/java.base/share/classes/sun/security/ssl/SSLSocketInputRecord.java	Tue Aug 14 18:16:47 2018 -0700
@@ -38,8 +38,6 @@
 import javax.net.ssl.SSLProtocolException;
 
 import sun.security.ssl.SSLCipher.SSLReadCipher;
-import sun.security.ssl.KeyUpdate.KeyUpdateMessage;
-import sun.security.ssl.KeyUpdate.KeyUpdateRequest;
 
 /**
  * {@code InputRecord} implementation for {@code SSLSocket}.
@@ -348,20 +346,6 @@
             return plaintexts.toArray(new Plaintext[0]);
         }
 
-        // KeyLimit check during application data.
-        // atKeyLimit() inactive when limits not checked, tc set when limits
-        // are active.
-
-        if (readCipher.atKeyLimit()) {
-            if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
-                SSLLogger.fine("KeyUpdate: triggered, read side.");
-            }
-
-            PostHandshakeContext p = new PostHandshakeContext(tc);
-            KeyUpdate.handshakeProducer.produce(p,
-                    new KeyUpdateMessage(p, KeyUpdateRequest.REQUESTED));
-        }
-
         return new Plaintext[] {
                 new Plaintext(contentType,
                     majorVersion, minorVersion, -1, -1L, fragment)
--- a/src/java.base/share/classes/sun/security/ssl/SSLSocketOutputRecord.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/src/java.base/share/classes/sun/security/ssl/SSLSocketOutputRecord.java	Tue Aug 14 18:16:47 2018 -0700
@@ -28,12 +28,10 @@
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.net.SocketException;
 import java.nio.ByteBuffer;
 import javax.net.ssl.SSLHandshakeException;
 
-import sun.security.ssl.KeyUpdate.KeyUpdateMessage;
-import sun.security.ssl.KeyUpdate.KeyUpdateRequest;
-
 /**
  * {@code OutputRecord} implementation for {@code SSLSocket}.
  */
@@ -53,7 +51,16 @@
     }
 
     @Override
-    void encodeAlert(byte level, byte description) throws IOException {
+    synchronized void encodeAlert(
+            byte level, byte description) throws IOException {
+        if (isClosed()) {
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                SSLLogger.warning("outbound has closed, ignore outbound " +
+                    "alert message: " + Alert.nameOf(description));
+            }
+            return;
+        }
+
         // use the buf of ByteArrayOutputStream
         int position = headerSize + writeCipher.getExplicitNonceSize();
         count = position;
@@ -63,6 +70,7 @@
         if (SSLLogger.isOn && SSLLogger.isOn("record")) {
             SSLLogger.fine("WRITE: " + protocolVersion +
                     " " + ContentType.ALERT.name +
+                    "(" + Alert.nameOf(description) + ")" +
                     ", length = " + (count - headerSize));
         }
 
@@ -83,8 +91,17 @@
     }
 
     @Override
-    void encodeHandshake(byte[] source,
+    synchronized void encodeHandshake(byte[] source,
             int offset, int length) throws IOException {
+        if (isClosed()) {
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                SSLLogger.warning("outbound has closed, ignore outbound " +
+                        "handshake message",
+                        ByteBuffer.wrap(source, offset, length));
+            }
+            return;
+        }
+
         if (firstMessage) {
             firstMessage = false;
 
@@ -182,7 +199,14 @@
     }
 
     @Override
-    void encodeChangeCipherSpec() throws IOException {
+    synchronized void encodeChangeCipherSpec() throws IOException {
+        if (isClosed()) {
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                SSLLogger.warning("outbound has closed, ignore outbound " +
+                    "change_cipher_spec message");
+            }
+            return;
+        }
 
         // use the buf of ByteArrayOutputStream
         int position = headerSize + writeCipher.getExplicitNonceSize();
@@ -207,7 +231,7 @@
     }
 
     @Override
-    public void flush() throws IOException {
+    public synchronized void flush() throws IOException {
         int position = headerSize + writeCipher.getExplicitNonceSize();
         if (count <= position) {
             return;
@@ -237,7 +261,12 @@
     }
 
     @Override
-    void deliver(byte[] source, int offset, int length) throws IOException {
+    synchronized void deliver(
+            byte[] source, int offset, int length) throws IOException {
+        if (isClosed()) {
+            throw new SocketException("Connection or outbound has been closed");
+        }
+
         if (writeCipher.authenticator.seqNumOverflow()) {
             if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
                 SSLLogger.fine(
@@ -304,23 +333,11 @@
             }
 
             offset += fragLen;
-
-            // atKeyLimit() inactive when limits not checked, tc set when limits
-            // are active.
-            if (writeCipher.atKeyLimit()) {
-                if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
-                    SSLLogger.fine("KeyUpdate: triggered, write side.");
-                }
-
-                PostHandshakeContext p = new PostHandshakeContext(tc);
-                KeyUpdate.handshakeProducer.produce(p,
-                        new KeyUpdateMessage(p, KeyUpdateRequest.REQUESTED));
-            }
         }
     }
 
     @Override
-    void setDeliverStream(OutputStream outputStream) {
+    synchronized void setDeliverStream(OutputStream outputStream) {
         this.deliverStream = outputStream;
     }
 
@@ -347,7 +364,7 @@
      * This avoids issues in the outbound direction.  For a full fix,
      * the peer must have similar protections.
      */
-    boolean needToSplitPayload() {
+    private boolean needToSplitPayload() {
         return (!protocolVersion.useTLS11PlusSpec()) &&
                 writeCipher.isCBCMode() && !isFirstAppOutputRecord &&
                 Record.enableCBCProtection;
--- a/src/java.base/share/classes/sun/security/ssl/TransportContext.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/src/java.base/share/classes/sun/security/ssl/TransportContext.java	Tue Aug 14 18:16:47 2018 -0700
@@ -25,7 +25,6 @@
 
 package sun.security.ssl;
 
-import java.io.Closeable;
 import java.io.IOException;
 import java.security.AccessControlContext;
 import java.security.AccessController;
@@ -45,7 +44,7 @@
 /**
  * SSL/(D)TLS transportation context.
  */
-class TransportContext implements ConnectionContext, Closeable {
+class TransportContext implements ConnectionContext {
     final SSLTransport              transport;
 
     // registered plaintext consumers
@@ -62,7 +61,7 @@
     boolean                         isNegotiated = false;
     boolean                         isBroken = false;
     boolean                         isInputCloseNotified = false;
-    boolean                         isOutputCloseNotified = false;
+    boolean                         peerUserCanceled = false;
     Exception                       closeReason = null;
 
     // negotiated security parameters
@@ -229,10 +228,6 @@
         }
     }
 
-    void keyUpdate() throws IOException {
-        kickstart();
-    }
-
     boolean isPostHandshakeContext() {
         return handshakeContext != null &&
                 (handshakeContext instanceof PostHandshakeContext);
@@ -348,7 +343,7 @@
         //
         // If we haven't even started handshaking yet, or we are the recipient
         // of a fatal alert, no need to generate a fatal close alert.
-        if (!recvFatalAlert && !isOutboundDone() && !isBroken &&
+        if (!recvFatalAlert && !isOutboundClosed() && !isBroken &&
                 (isNegotiated || handshakeContext != null)) {
             try {
                 outputRecord.encodeAlert(Alert.Level.FATAL.level, alert.id);
@@ -436,35 +431,26 @@
         return outputRecord.isClosed();
     }
 
-    boolean isInboundDone() {
+    boolean isInboundClosed() {
         return inputRecord.isClosed();
     }
 
-    boolean isClosed() {
-        return isOutboundClosed() && isInboundDone();
-    }
-
-    @Override
-    public void close() throws IOException {
-        if (!isOutboundDone()) {
-            closeOutbound();
-        }
-
-        if (!isInboundDone()) {
-            closeInbound();
-        }
-    }
-
-    void closeInbound() {
-        if (isInboundDone()) {
+    // Close inbound, no more data should be delivered to the underlying
+    // transportation connection.
+    void closeInbound() throws SSLException {
+        if (isInboundClosed()) {
             return;
         }
 
         try {
-            if (isInputCloseNotified) {     // passive close
+            // Important note: check if the initial handshake is started at
+            // first so that the passiveInboundClose() implementation need not
+            // to consider the case any more.
+            if (!isInputCloseNotified) {
+                // the initial handshake is not started
+                initiateInboundClose();
+            } else {
                 passiveInboundClose();
-            } else {                        // initiative close
-                initiateInboundClose();
             }
         } catch (IOException ioe) {
             if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
@@ -473,8 +459,58 @@
         }
     }
 
+    // Close the connection passively.  The closure could be kickoff by
+    // receiving a close_notify alert or reaching end_of_file of the socket.
+    //
+    // Note that this method is called only if the initial handshake has
+    // started or completed.
+    private void passiveInboundClose() throws IOException {
+        if (!isInboundClosed()) {
+            inputRecord.close();
+        }
+
+        // For TLS 1.2 and prior version, it is required to respond with
+        // a close_notify alert of its own and close down the connection
+        // immediately, discarding any pending writes.
+        if (!isOutboundClosed()) {
+            boolean needCloseNotify = SSLConfiguration.acknowledgeCloseNotify;
+            if (!needCloseNotify) {
+                if (isNegotiated) {
+                    if (!protocolVersion.useTLS13PlusSpec()) {
+                        needCloseNotify = true;
+                    }
+                } else if (handshakeContext != null) {  // initial handshake
+                    ProtocolVersion pv = handshakeContext.negotiatedProtocol;
+                    if (pv == null || (!pv.useTLS13PlusSpec())) {
+                        needCloseNotify = true;
+                    }
+                }
+            }
+
+            if (needCloseNotify) {
+                synchronized (outputRecord) {
+                    try {
+                        // send a close_notify alert
+                        warning(Alert.CLOSE_NOTIFY);
+                    } finally {
+                        outputRecord.close();
+                    }
+                }
+            }
+        }
+    }
+
+    // Initiate a inbound close when the handshake is not started.
+    private void initiateInboundClose() throws IOException {
+        if (!isInboundClosed()) {
+            inputRecord.close();
+        }
+    }
+
+    // Close outbound, no more data should be received from the underlying
+    // transportation connection.
     void closeOutbound() {
-        if (isOutboundDone()) {
+        if (isOutboundClosed()) {
             return;
         }
 
@@ -487,116 +523,59 @@
         }
     }
 
-    // Close the connection passively.  The closure could be kickoff by
-    // receiving a close_notify alert or reaching end_of_file of the socket.
-    private void passiveInboundClose() throws IOException {
-        if (!isInboundDone()) {
-            inputRecord.close();
+    // Initiate a close by sending a close_notify alert.
+    private void initiateOutboundClose() throws IOException {
+        boolean useUserCanceled = false;
+        if (!isNegotiated && (handshakeContext != null) && !peerUserCanceled) {
+            // initial handshake
+            useUserCanceled = true;
         }
 
-        // For TLS 1.2 and prior version, it is required to respond with
-        // a close_notify alert of its own and close down the connection
-        // immediately, discarding any pending writes.
-        if (!isOutboundDone() && !isOutputCloseNotified) {
+        // Need a lock here so that the user_canceled alert and the
+        // close_notify alert can be delivered together.
+        synchronized (outputRecord) {
             try {
-                // send a close_notify alert
-                warning(Alert.CLOSE_NOTIFY);
-            } finally {
-                // any data received after a closure alert is ignored.
-                isOutputCloseNotified = true;
-                outputRecord.close();
-            }
-        }
+                // send a user_canceled alert if needed.
+                if (useUserCanceled) {
+                    warning(Alert.USER_CANCELED);
+                }
 
-        transport.shutdown();
-    }
-
-    // Initiate a close by sending a close_notify alert.
-    private void initiateInboundClose() throws IOException {
-        // TLS 1.3 does not define how to initiate and close a TLS connection
-        // gracefully.  We will always send a close_notify alert, and close
-        // the underlying transportation layer if needed.
-        if (!isInboundDone() && !isInputCloseNotified) {
-            try {
                 // send a close_notify alert
                 warning(Alert.CLOSE_NOTIFY);
             } finally {
-                // any data received after a closure alert is ignored.
-                isInputCloseNotified = true;
-                inputRecord.close();
-            }
-        }
-
-        // For TLS 1.3, input closure is independent from output closure. Both
-        // parties need not wait to receive a "close_notify" alert before
-        // closing their read side of the connection.
-        //
-        // For TLS 1.2 and prior version, it is not required for the initiator
-        // of the close to wait for the responding close_notify alert before
-        // closing the read side of the connection.
-        try {
-            transport.shutdown();
-        } finally {
-            if (!isOutboundDone()) {
                 outputRecord.close();
             }
         }
     }
 
-    // Initiate a close by sending a close_notify alert.
-    private void initiateOutboundClose() throws IOException {
-        if (!isOutboundDone() && !isOutputCloseNotified) {
-            try {     // close outputRecord
-                // send a close_notify alert
-                warning(Alert.CLOSE_NOTIFY);
-            } finally {
-                // any data received after a closure alert is ignored.
-                isOutputCloseNotified = true;
-                outputRecord.close();
-            }
-        }
-
-        // It is not required for the initiator of the close to wait for the
-        // responding close_notify alert before closing the read side of the
-        // connection.  However, if the application protocol using TLS
-        // provides that any data may be carried over the underlying transport
-        // after the TLS connection is closed, the TLS implementation MUST
-        // receive a "close_notify" alert before indicating end-of-data to the
-        // application-layer.
-        try {
-            transport.shutdown();
-        } finally {
-            if (!isInboundDone()) {
-                inputRecord.close();
-            }
-        }
-    }
-
     // Note; HandshakeStatus.FINISHED status is retrieved in other places.
     HandshakeStatus getHandshakeStatus() {
         if (!outputRecord.isEmpty()) {
             // If no handshaking, special case to wrap alters or
             // post-handshake messages.
             return HandshakeStatus.NEED_WRAP;
+        } else if (isOutboundClosed() && isInboundClosed()) {
+            return HandshakeStatus.NOT_HANDSHAKING;
         } else if (handshakeContext != null) {
             if (!handshakeContext.delegatedActions.isEmpty()) {
                 return HandshakeStatus.NEED_TASK;
-            } else if (sslContext.isDTLS() &&
-                    !inputRecord.isEmpty()) {
-                return HandshakeStatus.NEED_UNWRAP_AGAIN;
-            } else {
-                return HandshakeStatus.NEED_UNWRAP;
+            } else if (!isInboundClosed()) {
+                if (sslContext.isDTLS() &&
+                        !inputRecord.isEmpty()) {
+                    return HandshakeStatus.NEED_UNWRAP_AGAIN;
+                } else {
+                    return HandshakeStatus.NEED_UNWRAP;
+                }
+            } else if (!isOutboundClosed()) {
+                // Special case that the inbound was closed, but outbound open.
+                return HandshakeStatus.NEED_WRAP;
             }
-        } else if (isOutboundDone() && !isInboundDone()) {
-            /*
-             * Special case where we're closing, but
-             * still need the close_notify before we
-             * can officially be closed.
-             *
-             * Note isOutboundDone is taken care of by
-             * hasOutboundData() above.
-             */
+        } else if (isOutboundClosed() && !isInboundClosed()) {
+            // Special case that the outbound was closed, but inbound open.
             return HandshakeStatus.NEED_UNWRAP;
+        } else if (!isOutboundClosed() && isInboundClosed()) {
+            // Special case that the inbound was closed, but outbound open.
+            return HandshakeStatus.NEED_WRAP;
         }
 
         return HandshakeStatus.NOT_HANDSHAKING;
@@ -607,8 +586,10 @@
             outputRecord.tc = this;
             inputRecord.tc = this;
             cipherSuite = handshakeContext.negotiatedCipherSuite;
-            inputRecord.readCipher.baseSecret = handshakeContext.baseReadSecret;
-            outputRecord.writeCipher.baseSecret = handshakeContext.baseWriteSecret;
+            inputRecord.readCipher.baseSecret =
+                    handshakeContext.baseReadSecret;
+            outputRecord.writeCipher.baseSecret =
+                    handshakeContext.baseWriteSecret;
         }
 
         handshakeContext = null;
--- a/test/jdk/java/net/httpclient/CookieHeaderTest.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/test/jdk/java/net/httpclient/CookieHeaderTest.java	Tue Aug 14 18:16:47 2018 -0700
@@ -35,6 +35,7 @@
  * @build Http2TestServer
  * @build jdk.testlibrary.SimpleSSLContext
  * @run testng/othervm
+ *       -Djdk.tls.acknowledgeCloseNotify=true
  *       -Djdk.httpclient.HttpClient.log=trace,headers,requests
  *       CookieHeaderTest
  */
--- a/test/jdk/java/net/httpclient/EncodedCharsInURI.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/test/jdk/java/net/httpclient/EncodedCharsInURI.java	Tue Aug 14 18:16:47 2018 -0700
@@ -33,6 +33,7 @@
  *          java.net.http/jdk.internal.net.http.frame
  *          java.net.http/jdk.internal.net.http.hpack
  * @run testng/othervm
+ *        -Djdk.tls.acknowledgeCloseNotify=true
  *        -Djdk.internal.httpclient.debug=true
  *        -Djdk.httpclient.HttpClient.log=headers,errors EncodedCharsInURI
  */
--- a/test/jdk/java/net/httpclient/ServerCloseTest.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/test/jdk/java/net/httpclient/ServerCloseTest.java	Tue Aug 14 18:16:47 2018 -0700
@@ -31,7 +31,7 @@
  *          java.net.http/jdk.internal.net.http.common
  *          java.net.http/jdk.internal.net.http.frame
  *          java.net.http/jdk.internal.net.http.hpack
- * @run testng/othervm ServerCloseTest
+ * @run testng/othervm -Djdk.tls.acknowledgeCloseNotify=true ServerCloseTest
  */
 //*        -Djdk.internal.httpclient.debug=true
 
--- a/test/jdk/javax/net/ssl/ALPN/SSLEngineAlpnTest.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/test/jdk/javax/net/ssl/ALPN/SSLEngineAlpnTest.java	Tue Aug 14 18:16:47 2018 -0700
@@ -191,6 +191,7 @@
         if (debug) {
             System.setProperty("javax.net.debug", "all");
         }
+        System.setProperty("jdk.tls.acknowledgeCloseNotify", "true");
         System.out.println("Test args: " + Arrays.toString(args));
 
         // Validate parameters
@@ -358,6 +359,7 @@
 
                 log("\tClosing clientEngine's *OUTBOUND*...");
                 clientEngine.closeOutbound();
+                // serverEngine.closeOutbound();
                 dataDone = true;
             }
         }
--- a/test/jdk/javax/net/ssl/SSLEngine/Arrays.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/test/jdk/javax/net/ssl/SSLEngine/Arrays.java	Tue Aug 14 18:16:47 2018 -0700
@@ -25,7 +25,14 @@
  * @test
  * @bug 5019096
  * @summary Add scatter/gather APIs for SSLEngine
- *
+ * @run main/othervm Arrays SSL
+ * @run main/othervm Arrays TLS
+ * @run main/othervm Arrays SSLv3
+ * @run main/othervm Arrays TLSv1
+ * @run main/othervm Arrays TLSv1.1
+ * @run main/othervm Arrays TLSv1.2
+ * @run main/othervm Arrays TLSv1.3
+ * @run main/othervm -Djdk.tls.acknowledgeCloseNotify=true Arrays TLSv1.3
  */
 
 import javax.net.ssl.*;
@@ -37,6 +44,8 @@
 public class Arrays {
 
     private static boolean debug = false;
+    private static boolean acknowledgeCloseNotify =
+        "true".equals(System.getProperty("jdk.tls.acknowledgeCloseNotify"));
 
     private SSLContext sslc;
     private SSLEngine ssle1;    // client
@@ -131,11 +140,13 @@
 
                 for (int i = 0; i < appOutArray1.length; i++) {
                     if (appOutArray1[i].remaining() != 0) {
+                        log("1st out not done");
                         done = false;
                     }
                 }
 
                 if (appOut2.remaining() != 0) {
+                    log("2nd out not done");
                     done = false;
                 }
 
@@ -145,6 +156,19 @@
                         appOutArray1[i].rewind();
                     }
                     ssle1.closeOutbound();
+                    String protocol = ssle2.getSession().getProtocol();
+                    if (!acknowledgeCloseNotify) {
+                        switch (ssle2.getSession().getProtocol()) {
+                            case "SSLv3":
+                            case "TLSv1":
+                            case "TLSv1.1":
+                            case "TLSv1.2":
+                                break;
+                            default:    // TLSv1.3
+                                // TLS 1.3, half-close only.
+                                ssle2.closeOutbound();
+                        }
+                    }
                     dataDone = true;
                 }
             }
@@ -155,7 +179,9 @@
         checkTransfer(appInArray1, appOut2);
     }
 
+    private static String contextVersion;
     public static void main(String args[]) throws Exception {
+        contextVersion = args[0];
 
         Arrays test;
 
@@ -165,7 +191,7 @@
 
         test.runTest();
 
-        System.out.println("Test Passed.");
+        System.err.println("Test Passed.");
     }
 
     /*
@@ -198,7 +224,7 @@
         TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
         tmf.init(ts);
 
-        SSLContext sslCtx = SSLContext.getInstance("TLS");
+        SSLContext sslCtx = SSLContext.getInstance(contextVersion);
 
         sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
 
@@ -288,7 +314,7 @@
 
     private static void log(String str) {
         if (debug) {
-            System.out.println(str);
+            System.err.println(str);
         }
     }
 }
--- a/test/jdk/javax/net/ssl/SSLEngine/ExtendedKeyEngine.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/test/jdk/javax/net/ssl/SSLEngine/ExtendedKeyEngine.java	Tue Aug 14 18:16:47 2018 -0700
@@ -26,6 +26,8 @@
  * @bug 4981697
  * @summary Rework the X509KeyManager to avoid incompatibility issues
  * @author Brad R. Wetmore
+ *
+ * @run main/othervm -Djdk.tls.acknowledgeCloseNotify=true ExtendedKeyEngine
  */
 
 import javax.net.ssl.*;
--- a/test/jdk/javax/net/ssl/TLSCommon/SSLEngineTestCase.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/test/jdk/javax/net/ssl/TLSCommon/SSLEngineTestCase.java	Tue Aug 14 18:16:47 2018 -0700
@@ -936,6 +936,7 @@
                 case SUPPORTED_NON_KRB_NON_SHA_CIPHERS:
                 case SUPPORTED_KRB_CIPHERS:
                 case ENABLED_NON_KRB_NOT_ANON_CIPHERS:
+                case TLS13_CIPHERS:
                     if (error != null) {
                         System.out.println("Test Failed: " + cs);
                         System.err.println("Test Exception for " + cs);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/javax/net/ssl/TLSv12/TLSEnginesClosureTest.java	Tue Aug 14 18:16:47 2018 -0700
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2018, 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.
+ */
+
+/*
+ * @test
+ * @bug 8207009
+ * @summary Testing TLS engines closing using each of the supported
+ *          cipher suites.
+ * @library /sun/security/krb5/auto /javax/net/ssl/TLSCommon
+ * @modules java.security.jgss
+ *          jdk.security.auth
+ *          java.security.jgss/sun.security.jgss.krb5
+ *          java.security.jgss/sun.security.krb5:+open
+ *          java.security.jgss/sun.security.krb5.internal:+open
+ *          java.security.jgss/sun.security.krb5.internal.ccache
+ *          java.security.jgss/sun.security.krb5.internal.crypto
+ *          java.security.jgss/sun.security.krb5.internal.ktab
+ *          java.base/sun.security.util
+ * @run main/othervm -Dtest.security.protocol=TLSv1.2
+ *     -Dtest.mode=norm TLSEnginesClosureTest
+ * @run main/othervm -Dtest.security.protocol=TLSv1.2
+ *     -Dtest.mode=norm_sni TLSEnginesClosureTest
+ * @run main/othervm -Dtest.security.protocol=TLSv1.2
+ *     -Dtest.mode=krb TLSEnginesClosureTest
+ */
+
+/**
+ * Testing TLS engines closing using each of the supported cipher suites.
+ */
+public class TLSEnginesClosureTest {
+    public static void main(String[] args) {
+        EnginesClosureTest.main(args);
+    }
+}
--- a/test/jdk/sun/security/ssl/AppInputStream/ReadBlocksClose.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/test/jdk/sun/security/ssl/AppInputStream/ReadBlocksClose.java	Tue Aug 14 18:16:47 2018 -0700
@@ -21,14 +21,16 @@
  * questions.
  */
 
+//
+// SunJSSE does not support dynamic system properties, no way to re-use
+// system properties in samevm/agentvm mode.
+//
+
 /*
  * @test
  * @bug 4814140
  * @summary AppInputStream: read can block a close
- * @run main/othervm ReadBlocksClose
- *
- *     SunJSSE does not support dynamic system properties, no way to re-use
- *     system properties in samevm/agentvm mode.
+ * @run main/othervm -Djdk.tls.acknowledgeCloseNotify=true ReadBlocksClose
  * @author Brad Wetmore
  */
 
@@ -141,7 +143,8 @@
                     System.out.println("Closing Thread started");
                     Thread.sleep(3000);
                     System.out.println("Closing Thread closing");
-                    sslIS.close();
+                    sslOS.close();
+                    System.out.println("Closing Thread closed");
                 } catch (Exception e) {
                     RuntimeException rte =
                         new RuntimeException("Check this out");
--- a/test/jdk/sun/security/ssl/SSLEngineImpl/CloseStart.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/test/jdk/sun/security/ssl/SSLEngineImpl/CloseStart.java	Tue Aug 14 18:16:47 2018 -0700
@@ -68,11 +68,6 @@
         }
     }
 
-    private static void runTest1(SSLEngine ssle) throws Exception {
-        ssle.closeInbound();
-        checkDone(ssle);
-    }
-
     private static void runTest2(SSLEngine ssle) throws Exception {
         ssle.closeOutbound();
         checkDone(ssle);
@@ -81,10 +76,16 @@
     public static void main(String args[]) throws Exception {
 
         SSLEngine ssle = createSSLEngine(keyFilename, trustFilename);
-        runTest1(ssle);
+        ssle.closeInbound();
+        if (!ssle.isInboundDone()) {
+            throw new Exception("isInboundDone isn't done");
+        }
 
         ssle = createSSLEngine(keyFilename, trustFilename);
-        runTest2(ssle);
+        ssle.closeOutbound();
+        if (!ssle.isOutboundDone()) {
+            throw new Exception("isOutboundDone isn't done");
+        }
 
         System.out.println("Test Passed.");
     }
--- a/test/jdk/sun/security/ssl/SSLEngineImpl/SSLEngineDeadlock.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/test/jdk/sun/security/ssl/SSLEngineImpl/SSLEngineDeadlock.java	Tue Aug 14 18:16:47 2018 -0700
@@ -280,6 +280,7 @@
 
                 log("\tClosing clientEngine's *OUTBOUND*...");
                 clientEngine.closeOutbound();
+                serverEngine.closeOutbound();
                 dataDone = true;
             }
         }
--- a/test/jdk/sun/security/ssl/SSLEngineImpl/SSLEngineKeyLimit.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/test/jdk/sun/security/ssl/SSLEngineImpl/SSLEngineKeyLimit.java	Tue Aug 14 18:16:47 2018 -0700
@@ -127,8 +127,7 @@
                     output.shouldNotContain("KeyUpdate: write key updated");
                     output.shouldNotContain("KeyUpdate: read key updated");
                 } else {
-                    output.shouldContain("KeyUpdate: triggered, read side");
-                    output.shouldContain("KeyUpdate: triggered, write side");
+                    output.shouldContain("trigger key update");
                     output.shouldContain("KeyUpdate: write key updated");
                     output.shouldContain("KeyUpdate: read key updated");
                 }
--- a/test/jdk/sun/security/ssl/SSLEngineImpl/TLS13BeginHandshake.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/test/jdk/sun/security/ssl/SSLEngineImpl/TLS13BeginHandshake.java	Tue Aug 14 18:16:47 2018 -0700
@@ -106,11 +106,11 @@
                 checkTransfer(clientOut, serverIn);
                 System.out.println("\tClosing...");
                 clientEngine.closeOutbound();
+                serverEngine.closeOutbound();
                 done++;
                 continue;
             }
         }
-
     }
 
     private static boolean isEngineClosed(SSLEngine engine) {
--- a/test/jdk/sun/security/ssl/SSLSocketImpl/AsyncSSLSocketClose.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/test/jdk/sun/security/ssl/SSLSocketImpl/AsyncSSLSocketClose.java	Tue Aug 14 18:16:47 2018 -0700
@@ -36,6 +36,7 @@
 
 import javax.net.ssl.*;
 import java.io.*;
+import java.net.SocketException;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -123,6 +124,9 @@
                 os.write(ba);
                 System.out.println(count + " bytes written");
             }
+        } catch (SocketException se) {
+            // the closing may be in progress
+            System.out.println("interrupted? " + se);
         } catch (Exception e) {
             if (socket.isClosed() || socket.isOutputShutdown()) {
                 System.out.println("interrupted, the socket is closed");
--- a/test/jdk/sun/security/ssl/SSLSocketImpl/SSLSocketKeyLimit.java	Tue Aug 14 19:52:34 2018 -0400
+++ b/test/jdk/sun/security/ssl/SSLSocketImpl/SSLSocketKeyLimit.java	Tue Aug 14 18:16:47 2018 -0700
@@ -119,7 +119,7 @@
             System.setProperty("test.java.opts",
                     "-Dtest.src=" + System.getProperty("test.src") +
                             " -Dtest.jdk=" + System.getProperty("test.jdk") +
-                            " -Djavax.net.debug=ssl,handshake " +
+                            " -Djavax.net.debug=ssl,handshake" +
                             " -Djava.security.properties=" + f.getName());
 
             System.out.println("test.java.opts: " +
@@ -134,8 +134,7 @@
                     output.shouldNotContain("KeyUpdate: write key updated");
                     output.shouldNotContain("KeyUpdate: read key updated");
                 } else {
-                    output.shouldContain("KeyUpdate: triggered, read side");
-                    output.shouldContain("KeyUpdate: triggered, write side");
+                    output.shouldContain("trigger key update");
                     output.shouldContain("KeyUpdate: write key updated");
                     output.shouldContain("KeyUpdate: read key updated");
                 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/sun/security/ssl/SSLSocketImpl/ServerRenegoWithTwoVersions.java	Tue Aug 14 18:16:47 2018 -0700
@@ -0,0 +1,329 @@
+/*
+ * Copyright (c) 2018, 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 8208642
+ * @summary Server initiated TLSv1.2 renegotiation fails if Java
+ *      client allows TLSv1.3
+ * @run main/othervm ServerRenegoWithTwoVersions TLSv1 SSLv3
+ * @run main/othervm ServerRenegoWithTwoVersions TLSv1 TLSv1.1
+ * @run main/othervm ServerRenegoWithTwoVersions TLSv1.2 TLSv1.1
+ * @run main/othervm ServerRenegoWithTwoVersions TLSv1.3 TLSv1.2
+ */
+
+import java.io.*;
+import java.net.*;
+import java.security.Security;
+import javax.net.ssl.*;
+
+public class ServerRenegoWithTwoVersions implements
+        HandshakeCompletedListener {
+
+    static byte handshakesCompleted = 0;
+
+    /*
+     * Define what happens when handshaking is completed
+     */
+    public void handshakeCompleted(HandshakeCompletedEvent event) {
+        synchronized (this) {
+            handshakesCompleted++;
+            System.out.println("Session: " + event.getSession().toString());
+            System.out.println("Seen handshake completed #" +
+                handshakesCompleted);
+        }
+    }
+
+    /*
+     * =============================================================
+     * Set the various variables needed for the tests, then
+     * specify what tests to run on each side.
+     */
+
+    /*
+     * Should we run the client or server in a separate thread?
+     * Both sides can throw exceptions, but do you have a preference
+     * as to which side should be the main thread.
+     */
+    static boolean separateServerThread = false;
+
+    /*
+     * Where do we find the keystores?
+     */
+    static String pathToStores = "../../../../javax/net/ssl/etc";
+    static String keyStoreFile = "keystore";
+    static String trustStoreFile = "truststore";
+    static String passwd = "passphrase";
+
+    /*
+     * Is the server ready to serve?
+     */
+    volatile static boolean serverReady = false;
+
+    /*
+     * Turn on SSL debugging?
+     */
+    static boolean debug = false;
+
+    /*
+     * If the client or server is doing some kind of object creation
+     * that the other side depends on, and that thread prematurely
+     * exits, you may experience a hang.  The test harness will
+     * terminate all hung threads after its timeout has expired,
+     * currently 3 minutes by default, but you might try to be
+     * smart about it....
+     */
+
+    /*
+     * Define the server side of the test.
+     *
+     * If the server prematurely exits, serverReady will be set to true
+     * to avoid infinite hangs.
+     */
+    void doServerSide() throws Exception {
+        SSLServerSocketFactory sslssf =
+            (SSLServerSocketFactory)SSLServerSocketFactory.getDefault();
+        SSLServerSocket sslServerSocket =
+            (SSLServerSocket) sslssf.createServerSocket(serverPort);
+
+        serverPort = sslServerSocket.getLocalPort();
+
+        /*
+         * Signal Client, we're ready for his connect.
+         */
+        serverReady = true;
+
+        SSLSocket sslSocket = (SSLSocket)sslServerSocket.accept();
+        sslSocket.setEnabledProtocols(new String[] { serverProtocol });
+        sslSocket.addHandshakeCompletedListener(this);
+        InputStream sslIS = sslSocket.getInputStream();
+        OutputStream sslOS = sslSocket.getOutputStream();
+
+        for (int i = 0; i < 10; i++) {
+            sslIS.read();
+            sslOS.write(85);
+            sslOS.flush();
+        }
+
+        System.out.println("invalidating");
+        sslSocket.getSession().invalidate();
+        System.out.println("starting new handshake");
+        sslSocket.startHandshake();
+
+        for (int i = 0; i < 10; i++) {
+            System.out.println("sending/receiving data, iteration: " + i);
+            sslIS.read();
+            sslOS.write(85);
+            sslOS.flush();
+        }
+
+        sslSocket.close();
+    }
+
+    /*
+     * Define the client side of the test.
+     *
+     * If the server prematurely exits, serverReady will be set to true
+     * to avoid infinite hangs.
+     */
+    void doClientSide() throws Exception {
+
+        /*
+         * Wait for server to get started.
+         */
+        while (!serverReady) {
+            Thread.sleep(50);
+        }
+
+        SSLSocketFactory sslsf =
+            (SSLSocketFactory)SSLSocketFactory.getDefault();
+        SSLSocket sslSocket = (SSLSocket)
+            sslsf.createSocket("localhost", serverPort);
+        sslSocket.setEnabledProtocols(
+            new String[] { serverProtocol, clientProtocol });
+
+        InputStream sslIS = sslSocket.getInputStream();
+        OutputStream sslOS = sslSocket.getOutputStream();
+
+        for (int i = 0; i < 10; i++) {
+            sslOS.write(280);
+            sslOS.flush();
+            sslIS.read();
+        }
+
+        for (int i = 0; i < 10; i++) {
+            sslOS.write(280);
+            sslOS.flush();
+            sslIS.read();
+        }
+
+        sslSocket.close();
+    }
+
+    /*
+     * =============================================================
+     * The remainder is just support stuff
+     */
+
+    // use any free port by default
+    volatile int serverPort = 0;
+
+    volatile Exception serverException = null;
+    volatile Exception clientException = null;
+
+    // the specified protocol
+    private static String clientProtocol;
+    private static String serverProtocol;
+
+    public static void main(String[] args) throws Exception {
+        String keyFilename =
+            System.getProperty("test.src", "./") + "/" + pathToStores +
+                "/" + keyStoreFile;
+        String trustFilename =
+            System.getProperty("test.src", "./") + "/" + pathToStores +
+                "/" + trustStoreFile;
+
+        System.setProperty("javax.net.ssl.keyStore", keyFilename);
+        System.setProperty("javax.net.ssl.keyStorePassword", passwd);
+        System.setProperty("javax.net.ssl.trustStore", trustFilename);
+        System.setProperty("javax.net.ssl.trustStorePassword", passwd);
+
+        if (debug) {
+            System.setProperty("javax.net.debug", "all");
+        }
+
+        Security.setProperty("jdk.tls.disabledAlgorithms", "");
+
+        clientProtocol = args[0];
+        serverProtocol = args[1];
+
+        /*
+         * Start the tests.
+         */
+        new ServerRenegoWithTwoVersions();
+    }
+
+    Thread clientThread = null;
+    Thread serverThread = null;
+
+    /*
+     * Primary constructor, used to drive remainder of the test.
+     *
+     * Fork off the other side, then do your work.
+     */
+    ServerRenegoWithTwoVersions() throws Exception {
+        if (separateServerThread) {
+            startServer(true);
+            startClient(false);
+        } else {
+            startClient(true);
+            startServer(false);
+        }
+
+        /*
+         * Wait for other side to close down.
+         */
+        if (separateServerThread) {
+            serverThread.join();
+        } else {
+            clientThread.join();
+        }
+
+        /*
+         * When we get here, the test is pretty much over.
+         *
+         * If the main thread excepted, that propagates back
+         * immediately.  If the other thread threw an exception, we
+         * should report back.
+         */
+        if (serverException != null) {
+            System.out.print("Server Exception:");
+            throw serverException;
+        }
+        if (clientException != null) {
+            System.out.print("Client Exception:");
+            throw clientException;
+        }
+
+        /*
+         * Give the Handshaker Thread a chance to run
+         */
+        Thread.sleep(1000);
+
+        synchronized (this) {
+            if (handshakesCompleted != 2) {
+                throw new Exception("Didn't see 2 handshake completed events.");
+            }
+        }
+    }
+
+    void startServer(boolean newThread) throws Exception {
+        if (newThread) {
+            serverThread = new Thread() {
+                public void run() {
+                    try {
+                        doServerSide();
+                    } catch (Exception e) {
+                        /*
+                         * Our server thread just died.
+                         *
+                         * Release the client, if not active already...
+                         */
+                        System.err.println("Server died...");
+                        serverReady = true;
+                        serverException = e;
+                    }
+                }
+            };
+            serverThread.start();
+        } else {
+            doServerSide();
+        }
+    }
+
+    void startClient(boolean newThread) throws Exception {
+        if (newThread) {
+            clientThread = new Thread() {
+                public void run() {
+                    try {
+                        doClientSide();
+                    } catch (Exception e) {
+                        /*
+                         * Our client thread just died.
+                         */
+                        System.err.println("Client died...");
+                        clientException = e;
+                    }
+                }
+            };
+            clientThread.start();
+        } else {
+            doClientSide();
+        }
+    }
+}