Updates for session resumption and key update JDK-8145252-TLS13-branch
authorxuelei
Thu, 07 Jun 2018 21:16:21 -0700
branchJDK-8145252-TLS13-branch
changeset 56702 75527e40bdfd
parent 56701 5d76e867b5cd
child 56703 33a2451070d3
child 56704 c3ee22c3a0f6
Updates for session resumption and key update
src/java.base/share/classes/sun/security/ssl/CertStatusExtension.java
src/java.base/share/classes/sun/security/ssl/ClientHello.java
src/java.base/share/classes/sun/security/ssl/HandshakeContext.java
src/java.base/share/classes/sun/security/ssl/NewSessionTicket.java
src/java.base/share/classes/sun/security/ssl/PreSharedKeyExtension.java
src/java.base/share/classes/sun/security/ssl/PskKeyExchangeModesExtension.java
src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java
src/java.base/share/classes/sun/security/ssl/SSLExtension.java
src/java.base/share/classes/sun/security/ssl/ServerHello.java
--- a/src/java.base/share/classes/sun/security/ssl/CertStatusExtension.java	Fri Jun 08 11:10:40 2018 +0800
+++ b/src/java.base/share/classes/sun/security/ssl/CertStatusExtension.java	Thu Jun 07 21:16:21 2018 -0700
@@ -612,7 +612,8 @@
 
             // Update the context.
             shc.handshakeExtensions.put(CH_STATUS_REQUEST, spec);
-            if (!shc.negotiatedProtocol.useTLS13PlusSpec()) {
+            if (!shc.isResumption &&
+                    !shc.negotiatedProtocol.useTLS13PlusSpec()) {
                 shc.handshakeProducers.put(SSLHandshake.CERTIFICATE_STATUS.id,
                     SSLHandshake.CERTIFICATE_STATUS);
             }   // Otherwise, the certificate status presents in server cert.
@@ -969,9 +970,12 @@
 
             // Update the context.
             shc.handshakeExtensions.put(CH_STATUS_REQUEST_V2, spec);
-            shc.handshakeProducers.putIfAbsent(
-                    SSLHandshake.CERTIFICATE_STATUS.id,
-                    SSLHandshake.CERTIFICATE_STATUS);
+            if (!shc.isResumption) {
+                shc.handshakeProducers.putIfAbsent(
+                        SSLHandshake.CERTIFICATE_STATUS.id,
+                        SSLHandshake.CERTIFICATE_STATUS);
+            }
+
             // No impact on session resumption.
         }
     }
--- a/src/java.base/share/classes/sun/security/ssl/ClientHello.java	Fri Jun 08 11:10:40 2018 +0800
+++ b/src/java.base/share/classes/sun/security/ssl/ClientHello.java	Thu Jun 07 21:16:21 2018 -0700
@@ -1092,12 +1092,30 @@
                     ContentType.CHANGE_CIPHER_SPEC.id,
                     ChangeCipherSpec.t13Consumer);
 
-            //
-            // validate
+            // Is it a resumption?
             //
-            // Check and launch ClientHello extensions.
-            SSLExtension[] extTypes = shc.sslConfig.getEnabledExtensions(
-                    SSLHandshake.CLIENT_HELLO);
+            // Check and launch the "psk_key_exchange_modes" and
+            // "pre_shared_key" extensions first, which will reset the
+            // resuming session, no matter the extensions present or not.
+            shc.isResumption = true;
+            SSLExtension[] extTypes = new SSLExtension[] {
+                    SSLExtension.PSK_KEY_EXCHANGE_MODES,
+                    SSLExtension.CH_PRE_SHARED_KEY
+                };
+            clientHello.extensions.consumeOnLoad(shc, extTypes);
+
+            // Check and launch ClientHello extensions other than
+            // "psk_key_exchange_modes", "pre_shared_key", "protocol_version"
+            // and "key_share" extensions.
+            //
+            // These extensions may discard session resumption, or ask for
+            // hello retry.
+            extTypes = shc.sslConfig.getExclusiveExtensions(
+                    SSLHandshake.CLIENT_HELLO,
+                    Arrays.asList(
+                            SSLExtension.PSK_KEY_EXCHANGE_MODES,
+                            SSLExtension.CH_PRE_SHARED_KEY,
+                            SSLExtension.CH_SUPPORTED_VERSIONS));
             clientHello.extensions.consumeOnLoad(shc, extTypes);
 
             if (!shc.handshakeProducers.isEmpty()) {
--- a/src/java.base/share/classes/sun/security/ssl/HandshakeContext.java	Fri Jun 08 11:10:40 2018 +0800
+++ b/src/java.base/share/classes/sun/security/ssl/HandshakeContext.java	Thu Jun 07 21:16:21 2018 -0700
@@ -152,8 +152,6 @@
     List<SNIServerName>                     requestedServerNames;
     SNIServerName                           negotiatedServerName;
 
-    List<PskKeyExchangeMode>                pskKeyExchangeModes = new ArrayList<>();
-
     // OCSP Stapling info
     boolean                                 staplingActive = false;
 
--- a/src/java.base/share/classes/sun/security/ssl/NewSessionTicket.java	Fri Jun 08 11:10:40 2018 +0800
+++ b/src/java.base/share/classes/sun/security/ssl/NewSessionTicket.java	Thu Jun 07 21:16:21 2018 -0700
@@ -35,6 +35,7 @@
 import java.util.Optional;
 import javax.crypto.SecretKey;
 import javax.net.ssl.SSLHandshakeException;
+import sun.security.ssl.PskKeyExchangeModesExtension.PskKeyExchangeModesSpec;
 
 import sun.security.ssl.SSLHandshake.HandshakeMessage;
 
@@ -42,8 +43,7 @@
  * Pack of the NewSessionTicket handshake message.
  */
 final class NewSessionTicket {
-
-    private static final int SEVEN_DAYS_IN_SECONDS = 604800;
+    private static final int MAX_TICKET_LIFETIME = 604800;  // seconds, 7 days
 
     static final SSLConsumer handshakeConsumer =
         new NewSessionTicketConsumer();
@@ -78,24 +78,42 @@
                 ByteBuffer m) throws IOException {
             super(context);
 
+            // struct {
+            //     uint32 ticket_lifetime;
+            //     uint32 ticket_age_add;
+            //     opaque ticket_nonce<0..255>;
+            //     opaque ticket<1..2^16-1>;
+            //     Extension extensions<0..2^16-2>;
+            // } NewSessionTicket;
+            if (m.remaining() < 14) {
+                context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
+                    "Invalid NewSessionTicket message: no sufficient data");
+            }
+
             this.ticketLifetime = Record.getInt32(m);
             this.ticketAgeAdd = Record.getInt32(m);
             this.ticketNonce = Record.getBytes8(m);
+
+            if (m.remaining() < 5) {
+                context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
+                    "Invalid NewSessionTicket message: no sufficient data");
+            }
+
             this.ticket = Record.getBytes16(m);
-            if (this.ticket.length == 0) {
+            if (ticket.length == 0) {
                 context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
-                        "No ticket in the NewSessionTicket handshake message");
+                    "No ticket in the NewSessionTicket handshake message");
+            }
+
+            if (m.remaining() < 2) {
+                context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
+                    "Invalid NewSessionTicket message: no sufficient data");
             }
 
             SSLExtension[] supportedExtensions =
                     context.sslConfig.getEnabledExtensions(
                             SSLHandshake.NEW_SESSION_TICKET);
-            if (m.hasRemaining()) {
-                this.extensions =
-                    new SSLExtensions(this, m, supportedExtensions);
-            } else {
-                this.extensions = new SSLExtensions(this);
-            }
+            this.extensions = new SSLExtensions(this, m, supportedExtensions);
         }
 
         @Override
@@ -138,14 +156,14 @@
                 "  \"ticket_nonce\"         : \"{2}\",\n" +
                 "  \"ticket\"               : \"{3}\",\n" +
                 "  \"extensions\"           : [\n" +
-                "{5}\n" +
+                "{4}\n" +
                 "  ]\n" +
                 "'}'",
                 Locale.ENGLISH);
 
             Object[] messageFields = {
                 ticketLifetime,
-                "omitted", //ticketAgeAdd should not be logged
+                "<omitted>",    //ticketAgeAdd should not be logged
                 Utilities.toHexString(ticketNonce),
                 Utilities.toHexString(ticket),
                 Utilities.indent(extensions.toString(), "    ")
@@ -171,17 +189,32 @@
 
     private static final
             class NewSessionTicketKickstartProducer implements SSLProducer {
+        // Prevent instantiation of this class.
+        private NewSessionTicketKickstartProducer() {
+            // blank
+        }
+
         @Override
         public byte[] produce(ConnectionContext context) throws IOException {
             // The producing happens in server side only.
             ServerHandshakeContext shc = (ServerHandshakeContext)context;
 
-            if (shc.pskKeyExchangeModes.isEmpty()) {
-                // client doesn't support PSK
+            // Is this session resumable?
+            if (!shc.handshakeSession.isRejoinable()) {
                 return null;
             }
 
-            if (!shc.handshakeSession.isRejoinable()) {
+            // What's the requested PSK key exchange modes?
+            //
+            // Note that currently, the NewSessionTicket post-handshake is
+            // produced and delivered only in the current handshake context
+            // if required.
+            PskKeyExchangeModesSpec pkemSpec =
+                    (PskKeyExchangeModesSpec)shc.handshakeExtensions.get(
+                            SSLExtension.PSK_KEY_EXCHANGE_MODES);
+            if (pkemSpec == null || !pkemSpec.contains(
+                PskKeyExchangeModesExtension.PskKeyExchangeMode.PSK_DHE_KE)) {
+                // Client doesn't support PSK with (EC)DHE key establishment.
                 return null;
             }
 
@@ -209,7 +242,7 @@
                     resumptionMasterSecret.get(), nonceArr);
 
             int sessionTimeoutSeconds = sessionCache.getSessionTimeout();
-            if (sessionTimeoutSeconds > SEVEN_DAYS_IN_SECONDS) {
+            if (sessionTimeoutSeconds > MAX_TICKET_LIFETIME) {
                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
                     SSLLogger.fine(
                         "Session timeout is too long. No ticket sent.");
@@ -268,8 +301,6 @@
         }
     }
 
-
-
     private static final
             class NewSessionTicketConsumer implements SSLConsumer {
         // Prevent instantiation of this class.
@@ -280,9 +311,21 @@
         @Override
         public void consume(ConnectionContext context,
                             ByteBuffer message) throws IOException {
-            // The consuming happens in client side only.
-            PostHandshakeContext hc = (PostHandshakeContext) context;
-            NewSessionTicketMessage nstm = new NewSessionTicketMessage(hc, message);
+
+            // Note: Although the resumption master secret depends on the
+            // client's second flight, servers which do not request client
+            // authentication MAY compute the remainder of the transcript
+            // independently and then send a NewSessionTicket immediately
+            // upon sending its Finished rather than waiting for the client
+            // Finished.
+            //
+            // The consuming happens in client side only.  As the server
+            // may send the NewSessionTicket before handshake complete, the
+            // context may be a PostHandshakeContext or HandshakeContext
+            // instance.
+            HandshakeContext hc = (HandshakeContext)context;
+            NewSessionTicketMessage nstm =
+                    new NewSessionTicketMessage(hc, message);
             if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
                 SSLLogger.fine(
                 "Consuming NewSessionTicket message", nstm);
@@ -290,7 +333,7 @@
 
             // discard tickets with timeout 0
             if (nstm.ticketLifetime <= 0 ||
-                nstm.ticketLifetime > SEVEN_DAYS_IN_SECONDS) {
+                nstm.ticketLifetime > MAX_TICKET_LIFETIME) {
                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
                     SSLLogger.fine(
                     "Discarding NewSessionTicket with lifetime "
@@ -302,7 +345,7 @@
             SSLSessionContextImpl sessionCache = (SSLSessionContextImpl)
                 hc.sslContext.engineGetClientSessionContext();
 
-            if (sessionCache.getSessionTimeout() > SEVEN_DAYS_IN_SECONDS) {
+            if (sessionCache.getSessionTimeout() > MAX_TICKET_LIFETIME) {
                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
                     SSLLogger.fine(
                     "Session cache lifetime is too long. Discarding ticket.");
--- a/src/java.base/share/classes/sun/security/ssl/PreSharedKeyExtension.java	Fri Jun 08 11:10:40 2018 +0800
+++ b/src/java.base/share/classes/sun/security/ssl/PreSharedKeyExtension.java	Thu Jun 07 21:16:21 2018 -0700
@@ -305,13 +305,12 @@
                 return;     // fatal() always throws, make the compiler happy.
             }
 
-            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
-                SSLLogger.fine("Received PSK extension: ", pskSpec);
-            }
-
-            if (shc.pskKeyExchangeModes.isEmpty()) {
+            // The "psk_key_exchange_modes" extension should have been loaded.
+            if (!shc.handshakeExtensions.containsKey(
+                    SSLExtension.PSK_KEY_EXCHANGE_MODES)) {
                 shc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
-                        "Client sent PSK but does not support PSK modes");
+                        "Client sent PSK but not PSK modes, or the PSK " +
+                        "extension is not the last extension");
             }
 
             // error if id and binder lists are not the same length
@@ -320,7 +319,7 @@
                         "PSK extension has incorrect number of binders");
             }
 
-            if (shc.isResumption && shc.resumingSession != null) {
+            if (shc.isResumption) {     // resumingSession may not be set
                 SSLSessionContextImpl sessionCache = (SSLSessionContextImpl)
                         shc.sslContext.engineGetServerSessionContext();
                 int idIndex = 0;
--- a/src/java.base/share/classes/sun/security/ssl/PskKeyExchangeModesExtension.java	Fri Jun 08 11:10:40 2018 +0800
+++ b/src/java.base/share/classes/sun/security/ssl/PskKeyExchangeModesExtension.java	Thu Jun 07 21:16:21 2018 -0700
@@ -28,6 +28,7 @@
 import java.nio.ByteBuffer;
 import java.text.MessageFormat;
 import java.util.*;
+import javax.net.ssl.SSLProtocolException;
 import sun.security.ssl.SSLExtension.ExtensionConsumer;
 
 import sun.security.ssl.SSLExtension.SSLExtensionSpec;
@@ -41,88 +42,129 @@
             new PskKeyExchangeModesProducer();
     static final ExtensionConsumer chOnLoadConsumer =
             new PskKeyExchangeModesConsumer();
+    static final HandshakeAbsence chOnLoadAbsence =
+            new PskKeyExchangeModesOnLoadAbsence();
+    static final HandshakeAbsence chOnTradeAbsence =
+            new PskKeyExchangeModesOnTradeAbsence();
+
+    static final SSLStringize pkemStringize =
+            new PskKeyExchangeModesStringize();
 
     enum PskKeyExchangeMode {
-        PSK_KE(0),
-        PSK_DHE_KE(1);
+        PSK_KE          ((byte)0, "psk_ke"),
+        PSK_DHE_KE      ((byte)1, "psk_dhe_ke");
 
-        private final int v;
+        final byte id;
+        final String name;
 
-        PskKeyExchangeMode(int v) {
-            this.v = v;
+        PskKeyExchangeMode(byte id, String name) {
+            this.id = id;
+            this.name = name;
         }
 
-        static PskKeyExchangeMode ofInt(int v) throws IOException {
-            for(PskKeyExchangeMode mode : values()) {
-                if (mode.v == v) {
-                    return mode;
+        static PskKeyExchangeMode valueOf(byte id) {
+            for(PskKeyExchangeMode pkem : values()) {
+                if (pkem.id == id) {
+                    return pkem;
                 }
             }
 
-            throw new IOException("invalid key exchange mode: " + v);
+            return null;
+        }
+
+        static String nameOf(byte id) {
+            for (PskKeyExchangeMode pkem : PskKeyExchangeMode.values()) {
+                if (pkem.id == id) {
+                    return pkem.name;
+                }
+            }
+
+            return "<UNKNOWN PskKeyExchangeMode TYPE: " + (id & 0x0FF) + ">";
         }
     }
 
-    private static final class PskKeyExchangeModesSpec implements SSLExtensionSpec {
-
+    static final
+            class PskKeyExchangeModesSpec implements SSLExtensionSpec {
+        private static final PskKeyExchangeModesSpec DEFAULT =
+                new PskKeyExchangeModesSpec(new byte[] {
+                        PskKeyExchangeMode.PSK_DHE_KE.id});
 
-        final List<PskKeyExchangeMode> modes;
+        final byte[] modes;
 
-        PskKeyExchangeModesSpec(List<PskKeyExchangeMode> modes) {
+        PskKeyExchangeModesSpec(byte[] modes) {
             this.modes = modes;
         }
 
         PskKeyExchangeModesSpec(ByteBuffer m) throws IOException {
+            if (m.remaining() < 2) {
+                throw new SSLProtocolException(
+                    "Invalid psk_key_exchange_modes extension: " +
+                    "insufficient data");
+            }
 
-            modes = new ArrayList<>();
-            int modesEncodedLength = Record.getInt8(m);
-            int modesReadLength = 0;
-            while (modesReadLength < modesEncodedLength) {
-                int mode = Record.getInt8(m);
-                modes.add(PskKeyExchangeMode.ofInt(mode));
-                modesReadLength += 1;
-            }
+            this.modes = Record.getBytes8(m);
         }
 
-        byte[] getEncoded() throws IOException {
-
-            int encodedLength = modes.size() + 1;
-            byte[] buffer = new byte[encodedLength];
-            ByteBuffer m = ByteBuffer.wrap(buffer);
-            Record.putInt8(m, modes.size());
-            for(PskKeyExchangeMode curMode : modes) {
-                Record.putInt8(m, curMode.v);
+        boolean contains(PskKeyExchangeMode mode) {
+            if (modes != null) {
+                for (byte m : modes) {
+                    if (mode.id == m) {
+                        return true;
+                    }
+                }
             }
 
-            return buffer;
+            return false;
         }
 
         @Override
         public String toString() {
             MessageFormat messageFormat = new MessageFormat(
-            "\"PskKeyExchangeModes\": '{'\n" +
-            "  \"ke_modes\"      : \"{0}\",\n" +
-            "'}'",
-            Locale.ENGLISH);
-
-            Object[] messageFields = {
-            Utilities.indent(modesString()),
-            };
+                "\"ke_modes\": '['{0}']'", Locale.ENGLISH);
+            if (modes == null || modes.length ==  0) {
+                Object[] messageFields = {
+                        "<no PSK key exchange modes specified>"
+                    };
+                return messageFormat.format(messageFields);
+            } else {
+                StringBuilder builder = new StringBuilder(64);
+                boolean isFirst = true;
+                for (byte mode : modes) {
+                    if (isFirst) {
+                        isFirst = false;
+                    } else {
+                        builder.append(", ");
+                    }
 
-            return messageFormat.format(messageFields);
-        }
+                    builder.append(PskKeyExchangeMode.nameOf(mode));
+                }
 
-        String modesString() {
-            StringBuilder result = new StringBuilder();
-            for(PskKeyExchangeMode curMode : modes) {
-                result.append(curMode.toString() + "\n");
+                Object[] messageFields = {
+                        builder.toString()
+                    };
+
+                return messageFormat.format(messageFields);
             }
-
-            return result.toString();
         }
     }
 
+    private static final
+            class PskKeyExchangeModesStringize implements SSLStringize {
+        @Override
+        public String toString(ByteBuffer buffer) {
+            try {
+                return (new PskKeyExchangeModesSpec(buffer)).toString();
+            } catch (IOException ioe) {
+                // For debug logging only, so please swallow exceptions.
+                return ioe.getMessage();
+            }
+        }
+    }
 
+    /**
+     * Network data consumer of a "psk_key_exchange_modes" extension in
+     * the ClientHello handshake message.
+     */
     private static final
             class PskKeyExchangeModesConsumer implements ExtensionConsumer {
         // Prevent instantiation of this class.
@@ -134,28 +176,66 @@
         public void consume(ConnectionContext context,
             HandshakeMessage message, ByteBuffer buffer) throws IOException {
 
-            ServerHandshakeContext shc =
-                    (ServerHandshakeContext) message.handshakeContext;
+            // The comsuming happens in server side only.
+            ServerHandshakeContext shc = (ServerHandshakeContext)context;
+
+            // Is it a supported and enabled extension?
+            if (!shc.sslConfig.isAvailable(
+                    SSLExtension.PSK_KEY_EXCHANGE_MODES)) {
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.fine(
+                        "Ignore unavailable psk_key_exchange_modes extension");
+                }
 
-            PskKeyExchangeModesSpec modes = new PskKeyExchangeModesSpec(buffer);
-            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
-                SSLLogger.fine(
-                "Received PskKeyExchangeModes extension: ", modes);
+                // No session resumption is allowed.
+                if (shc.isResumption && shc.resumingSession != null) {
+                    shc.isResumption = false;
+                    shc.resumingSession = null;
+                }
+
+                return;     // ignore the extension
+            }
+
+            // Parse the extension.
+            PskKeyExchangeModesSpec spec;
+            try {
+                spec = new PskKeyExchangeModesSpec(buffer);
+            } catch (IOException ioe) {
+                shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe);
+                return;     // fatal() always throws, make the compiler happy.
             }
 
-            shc.pskKeyExchangeModes = modes.modes;
+            // Update the context.
+            shc.handshakeExtensions.put(
+                    SSLExtension.PSK_KEY_EXCHANGE_MODES, spec);
+
+            // Impact on session resumption.
+            //
+            // Do the requested modes support session resumption?
+            if (shc.isResumption) {     // resumingSession may not be set
+                // Note: psk_dhe_ke is the only supported mode now.  If the
+                // psk_ke mode is supported in the future, may need an update
+                // here.
+                if (!spec.contains(PskKeyExchangeMode.PSK_DHE_KE)) {
+                    shc.isResumption = false;
+                    shc.resumingSession = null;
+                    if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                        SSLLogger.fine(
+                            "abort session resumption, " +
+                            "no supported psk_dhe_ke PSK key exchange mode");
+                    }
+                }
+            }
         }
-
     }
 
+    /**
+     * Network data producer of a "psk_key_exchange_modes" extension in the
+     * ClientHello handshake message.
+     */
     private static final
             class PskKeyExchangeModesProducer implements HandshakeProducer {
 
-        static final List<PskKeyExchangeMode> MODES =
-            List.of(PskKeyExchangeMode.PSK_DHE_KE);
-        static final PskKeyExchangeModesSpec MODES_MSG =
-            new PskKeyExchangeModesSpec(MODES);
-
         // Prevent instantiation of this class.
         private PskKeyExchangeModesProducer() {
             // blank
@@ -164,10 +244,90 @@
         @Override
         public byte[] produce(ConnectionContext context,
                 HandshakeMessage message) throws IOException {
+            // The producing happens in client side only.
+            ClientHandshakeContext chc = (ClientHandshakeContext)context;
 
-            return MODES_MSG.getEncoded();
+            // Is it a supported and enabled extension?
+            if (!chc.sslConfig.isAvailable(
+                    SSLExtension.PSK_KEY_EXCHANGE_MODES)) {
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.warning(
+                        "Ignore unavailable psk_key_exchange_modes extension");
+                }
+
+                return null;
+            }
+
+            byte[] extData = new byte[] {0x01, 0x01};   // psk_dhe_ke
+
+            // Update the context.
+            chc.handshakeExtensions.put(
+                    SSLExtension.PSK_KEY_EXCHANGE_MODES,
+                    PskKeyExchangeModesSpec.DEFAULT);
+
+            return extData;
+        }
+    }
+    /**
+     * The absence processing if a "psk_key_exchange_modes" extension is
+     * not present in the ClientHello handshake message.
+     */
+    private static final
+        class PskKeyExchangeModesOnLoadAbsence implements HandshakeAbsence {
+
+        // Prevent instantiation of this class.
+        private PskKeyExchangeModesOnLoadAbsence() {
+            // blank
         }
 
+        @Override
+        public void absent(ConnectionContext context,
+                HandshakeMessage message) throws IOException {
+            // The comsuming happens in server side only.
+            ServerHandshakeContext shc = (ServerHandshakeContext)context;
+
+            // No session resumptio is allowed.
+            if (shc.isResumption) {     // resumingSession may not be set
+                shc.isResumption = false;
+                shc.resumingSession = null;
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.fine(
+                            "abort session resumption, " +
+                            "no supported psk_dhe_ke PSK key exchange mode");
+                }
+            }
+        }
+    }
+
+    /**
+     * The absence processing if a "signature_algorithms" extension is
+     * not present in the ClientHello handshake message.
+     */
+    private static final
+        class PskKeyExchangeModesOnTradeAbsence implements HandshakeAbsence {
+
+        // Prevent instantiation of this class.
+        private PskKeyExchangeModesOnTradeAbsence() {
+            // blank
+        }
+
+        @Override
+        public void absent(ConnectionContext context,
+                HandshakeMessage message) throws IOException {
+            // The comsuming happens in server side only.
+            ServerHandshakeContext shc = (ServerHandshakeContext)context;
+
+            // A client MUST provide a "psk_key_exchange_modes" extension if
+            // it offers a "pre_shared_key" extension.  If clients offer
+            // "pre_shared_key" without a "psk_key_exchange_modes" extension,
+            // servers MUST abort the handshake.
+            SSLExtensionSpec spec =
+                shc.handshakeExtensions.get(SSLExtension.CH_PRE_SHARED_KEY);
+            if (spec == null) {
+                shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                        "pre_shared_key key extension is offered " +
+                        "without a psk_key_exchange_modes extension");
+            }
+        }
     }
 }
-
--- a/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java	Fri Jun 08 11:10:40 2018 +0800
+++ b/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java	Thu Jun 07 21:16:21 2018 -0700
@@ -342,6 +342,26 @@
     }
 
     /**
+     * Get the enabled extensions for the specific handshake message, excluding
+     * the specified extensions.
+     *
+     * Used to consume handshake extensions.
+     */
+    SSLExtension[] getExclusiveExtensions(SSLHandshake handshakeType,
+            List<SSLExtension> excluded) {
+        List<SSLExtension> extensions = new ArrayList<>();
+        for (SSLExtension extension : SSLExtension.values()) {
+            if (extension.handshakeType == handshakeType) {
+                if (isAvailable(extension) && !excluded.contains(extension)) {
+                    extensions.add(extension);
+                }
+            }
+        }
+
+        return extensions.toArray(new SSLExtension[0]);
+    }
+
+    /**
      * Get the enabled extensions for the specific handshake message
      * and the specific protocol version.
      *
--- a/src/java.base/share/classes/sun/security/ssl/SSLExtension.java	Fri Jun 08 11:10:40 2018 +0800
+++ b/src/java.base/share/classes/sun/security/ssl/SSLExtension.java	Thu Jun 07 21:16:21 2018 -0700
@@ -383,7 +383,10 @@
                                 ProtocolVersion.PROTOCOLS_OF_13,
                                 PskKeyExchangeModesExtension.chNetworkProducer,
                                 PskKeyExchangeModesExtension.chOnLoadConsumer,
-                                null, null, null, null),
+                                PskKeyExchangeModesExtension.chOnLoadAbsence,
+                                null,
+                                PskKeyExchangeModesExtension.chOnTradeAbsence,
+                                PskKeyExchangeModesExtension.pkemStringize),
     CERTIFICATE_AUTHORITIES (0x002F, "certificate_authorities"),
     OID_FILTERS             (0x0030, "oid_filters"),
     POST_HANDSHAKE_AUTH     (0x0030, "post_handshake_auth"),
--- a/src/java.base/share/classes/sun/security/ssl/ServerHello.java	Fri Jun 08 11:10:40 2018 +0800
+++ b/src/java.base/share/classes/sun/security/ssl/ServerHello.java	Thu Jun 07 21:16:21 2018 -0700
@@ -872,7 +872,7 @@
                 ServerHelloMessage helloRetryRequest) throws IOException {
             // Negotiate protocol version.
             //
-            // Check and lanuch SupportedVersions.
+            // Check and launch SupportedVersions.
             SSLExtension[] extTypes = new SSLExtension[] {
                     SSLExtension.HRR_SUPPORTED_VERSIONS
                 };
@@ -924,7 +924,7 @@
                 ServerHelloMessage serverHello) throws IOException {
             // Negotiate protocol version.
             //
-            // Check and lanuch SupportedVersions.
+            // Check and launch SupportedVersions.
             SSLExtension[] extTypes = new SSLExtension[] {
                     SSLExtension.SH_SUPPORTED_VERSIONS
                 };
@@ -1023,7 +1023,7 @@
             // validate
             //
 
-            // Check and lanuch the "renegotiation_info" extension.
+            // Check and launch the "renegotiation_info" extension.
             SSLExtension[] extTypes = new SSLExtension[] {
                     SSLExtension.SH_RENEGOTIATION_INFO
                 };