8211018: Session Resumption without Server-Side State
authorascarpino
Tue, 11 Jun 2019 16:31:37 -0700
changeset 55336 c2398053ee90
parent 55335 f7cc25dda38a
child 55337 ae3dbc712839
8211018: Session Resumption without Server-Side State Reviewed-by: xuelei, jnimeh, jjiang
src/java.base/share/classes/sun/security/ssl/ClientHello.java
src/java.base/share/classes/sun/security/ssl/Finished.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/SSLContextImpl.java
src/java.base/share/classes/sun/security/ssl/SSLExtension.java
src/java.base/share/classes/sun/security/ssl/SSLHandshake.java
src/java.base/share/classes/sun/security/ssl/SSLSessionContextImpl.java
src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java
src/java.base/share/classes/sun/security/ssl/ServerHello.java
src/java.base/share/classes/sun/security/ssl/SessionId.java
src/java.base/share/classes/sun/security/ssl/SessionTicketExtension.java
src/java.base/share/classes/sun/security/ssl/TransportContext.java
test/jdk/javax/net/ssl/DTLS/PacketLossRetransmission.java
test/jdk/javax/net/ssl/SSLSession/SSLCtxAccessToSessCtx.java
test/jdk/sun/security/ssl/DHKeyExchange/DHEKeySizing.java
test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksClient.java
test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksClientStateless.java
test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksServer.java
test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksServerStateless.java
--- a/src/java.base/share/classes/sun/security/ssl/ClientHello.java	Tue Jun 11 19:15:31 2019 -0400
+++ b/src/java.base/share/classes/sun/security/ssl/ClientHello.java	Tue Jun 11 16:31:37 2019 -0700
@@ -35,7 +35,6 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
-import java.util.Objects;
 import javax.net.ssl.SSLException;
 import javax.net.ssl.SSLHandshakeException;
 import javax.net.ssl.SSLPeerUnverifiedException;
@@ -969,11 +968,24 @@
                 }
             }
 
-            // Is it an abbreviated handshake?
-            if (clientHello.sessionId.length() != 0) {
-                SSLSessionImpl previous = ((SSLSessionContextImpl)shc.sslContext
-                            .engineGetServerSessionContext())
-                            .get(clientHello.sessionId.getId());
+            // Consume a Session Ticket Extension if it exists
+            SSLExtension[] ext = new SSLExtension[]{
+                    SSLExtension.CH_SESSION_TICKET
+            };
+            clientHello.extensions.consumeOnLoad(shc, ext);
+
+            // Does the client want to resume a session?
+            if (clientHello.sessionId.length() != 0 || shc.statelessResumption) {
+                SSLSessionContextImpl cache = (SSLSessionContextImpl)shc.sslContext
+                        .engineGetServerSessionContext();
+
+                SSLSessionImpl previous;
+                // Use the stateless session ticket if provided
+                if (shc.statelessResumption) {
+                    previous = shc.resumingSession;
+                } else {
+                    previous = cache.get(clientHello.sessionId.getId());
+                }
 
                 boolean resumingSession =
                         (previous != null) && previous.isRejoinable();
@@ -1051,14 +1063,20 @@
                 // the resuming options later.
                 shc.isResumption = resumingSession;
                 shc.resumingSession = resumingSession ? previous : null;
+
+                if (!resumingSession && SSLLogger.isOn &&
+                        SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.fine("Session not resumed.");
+                }
             }
 
             // cache the client random number for further using
             shc.clientHelloRandom = clientHello.clientRandom;
 
             // Check and launch ClientHello extensions.
-            SSLExtension[] extTypes = shc.sslConfig.getEnabledExtensions(
-                    SSLHandshake.CLIENT_HELLO);
+            SSLExtension[] extTypes = shc.sslConfig.getExclusiveExtensions(
+                    SSLHandshake.CLIENT_HELLO,
+                    Arrays.asList(SSLExtension.CH_SESSION_TICKET));
             clientHello.extensions.consumeOnLoad(shc, extTypes);
 
             //
@@ -1276,11 +1294,25 @@
                 }
             }
 
-            // Is it an abbreviated handshake?
+
+            // Does the client want to resume a session?
             if (clientHello.sessionId.length() != 0) {
-                SSLSessionImpl previous = ((SSLSessionContextImpl)shc.sslContext
-                            .engineGetServerSessionContext())
-                            .get(clientHello.sessionId.getId());
+                SSLSessionContextImpl cache = (SSLSessionContextImpl)shc.sslContext
+                        .engineGetServerSessionContext();
+
+                // Consume a Session Ticket Extension if it exists
+                SSLExtension[] ext = new SSLExtension[]{
+                        SSLExtension.CH_SESSION_TICKET
+                };
+                clientHello.extensions.consumeOnLoad(shc, ext);
+
+                SSLSessionImpl previous;
+                // Use stateless session ticket if provided.
+                if (shc.statelessResumption) {
+                    previous = shc.resumingSession;
+                } else {
+                    previous = cache.get(clientHello.sessionId.getId());
+                }
 
                 boolean resumingSession =
                         (previous != null) && previous.isRejoinable();
--- a/src/java.base/share/classes/sun/security/ssl/Finished.java	Tue Jun 11 19:15:31 2019 -0400
+++ b/src/java.base/share/classes/sun/security/ssl/Finished.java	Tue Jun 11 16:31:37 2019 -0700
@@ -410,6 +410,10 @@
                 chc.conContext.clientVerifyData = fm.verifyData;
             }
 
+            if (chc.statelessResumption) {
+                chc.handshakeConsumers.put(
+                        SSLHandshake.NEW_SESSION_TICKET.id, SSLHandshake.NEW_SESSION_TICKET);
+            }
             // update the consumers and producers
             if (!chc.isResumption) {
                 chc.conContext.consumers.put(ContentType.CHANGE_CIPHER_SPEC.id,
@@ -441,6 +445,10 @@
 
         private byte[] onProduceFinished(ServerHandshakeContext shc,
                 HandshakeMessage message) throws IOException {
+            if (shc.statelessResumption) {
+                NewSessionTicket.handshake12Producer.produce(shc, message);
+            }
+
             // Refresh handshake hash
             shc.handshakeHash.update();
 
@@ -473,7 +481,8 @@
                         SSLHandshake.FINISHED.id, SSLHandshake.FINISHED);
                 shc.conContext.inputRecord.expectingFinishFlight();
             } else {
-                if (shc.handshakeSession.isRejoinable()) {
+                if (shc.handshakeSession.isRejoinable() &&
+                        !shc.statelessResumption) {
                     ((SSLSessionContextImpl)shc.sslContext.
                         engineGetServerSessionContext()).put(
                             shc.handshakeSession);
@@ -591,7 +600,8 @@
             }
 
             if (shc.isResumption) {
-                if (shc.handshakeSession.isRejoinable()) {
+                if (shc.handshakeSession.isRejoinable() &&
+                        !shc.statelessResumption) {
                     ((SSLSessionContextImpl)shc.sslContext.
                         engineGetServerSessionContext()).put(
                             shc.handshakeSession);
@@ -915,9 +925,9 @@
 
             // save the session
             if (!chc.isResumption && chc.handshakeSession.isRejoinable()) {
-                SSLSessionContextImpl sessionContext = (SSLSessionContextImpl)
-                chc.sslContext.engineGetClientSessionContext();
-                sessionContext.put(chc.handshakeSession);
+                ((SSLSessionContextImpl)chc.sslContext.
+                        engineGetClientSessionContext()).
+                        put(chc.handshakeSession);
             }
 
             // derive salt secret
@@ -1028,10 +1038,11 @@
                         shc.negotiatedProtocol);
             }
 
-            // save the session
-            if (!shc.isResumption && shc.handshakeSession.isRejoinable()) {
+            // Save the session if possible and not stateless
+            if (!shc.statelessResumption && !shc.isResumption &&
+                    shc.handshakeSession.isRejoinable()) {
                 SSLSessionContextImpl sessionContext = (SSLSessionContextImpl)
-                shc.sslContext.engineGetServerSessionContext();
+                        shc.sslContext.engineGetServerSessionContext();
                 sessionContext.put(shc.handshakeSession);
             }
 
--- a/src/java.base/share/classes/sun/security/ssl/HandshakeContext.java	Tue Jun 11 19:15:31 2019 -0400
+++ b/src/java.base/share/classes/sun/security/ssl/HandshakeContext.java	Tue Jun 11 16:31:37 2019 -0700
@@ -102,6 +102,8 @@
     // Resumption
     boolean                                 isResumption;
     SSLSessionImpl                          resumingSession;
+    // Session is using stateless resumption
+    boolean                                 statelessResumption = false;
 
     final Queue<Map.Entry<Byte, ByteBuffer>> delegatedActions;
     volatile boolean                        taskDelegated = false;
@@ -551,7 +553,7 @@
 
     List<SNIServerName> getRequestedServerNames() {
         if (requestedServerNames == null) {
-            return Collections.<SNIServerName>emptyList();
+            return Collections.emptyList();
         }
         return requestedServerNames;
     }
--- a/src/java.base/share/classes/sun/security/ssl/NewSessionTicket.java	Tue Jun 11 19:15:31 2019 -0400
+++ b/src/java.base/share/classes/sun/security/ssl/NewSessionTicket.java	Tue Jun 11 16:31:37 2019 -0700
@@ -28,40 +28,139 @@
 import java.math.BigInteger;
 import java.nio.ByteBuffer;
 import java.security.GeneralSecurityException;
-import java.security.ProviderException;
 import java.security.SecureRandom;
 import java.text.MessageFormat;
 import java.util.Locale;
 import javax.crypto.SecretKey;
 import javax.net.ssl.SSLHandshakeException;
 import sun.security.ssl.PskKeyExchangeModesExtension.PskKeyExchangeModesSpec;
+import sun.security.ssl.SessionTicketExtension.SessionTicketSpec;
+import sun.security.ssl.SSLHandshake.HandshakeMessage;
+import sun.security.util.HexDumpEncoder;
 
-import sun.security.ssl.SSLHandshake.HandshakeMessage;
+import static sun.security.ssl.SSLHandshake.NEW_SESSION_TICKET;
 
 /**
  * Pack of the NewSessionTicket handshake message.
  */
 final class NewSessionTicket {
-    private static final int MAX_TICKET_LIFETIME = 604800;  // seconds, 7 days
+    static final int MAX_TICKET_LIFETIME = 604800;  // seconds, 7 days
 
     static final SSLConsumer handshakeConsumer =
-        new NewSessionTicketConsumer();
+        new T13NewSessionTicketConsumer();
+    static final SSLConsumer handshake12Consumer =
+        new T12NewSessionTicketConsumer();
     static final SSLProducer kickstartProducer =
         new NewSessionTicketKickstartProducer();
-    static final HandshakeProducer handshakeProducer =
-        new NewSessionTicketProducer();
+    static final HandshakeProducer handshake12Producer =
+        new T12NewSessionTicketProducer();
 
     /**
-     * The NewSessionTicketMessage handshake message.
+     * The NewSessionTicketMessage handshake messages.
+     */
+    abstract static class NewSessionTicketMessage extends HandshakeMessage {
+        int ticketLifetime;
+        byte[] ticket;
+
+        NewSessionTicketMessage(HandshakeContext context) {
+            super(context);
+        }
+
+        @Override
+        public SSLHandshake handshakeType() {
+            return NEW_SESSION_TICKET;
+        }
+
+        // For TLS 1.3 only
+        int getTicketAgeAdd() throws IOException {
+            throw handshakeContext.conContext.fatal(Alert.ILLEGAL_PARAMETER,
+                    "TicketAgeAdd not part of RFC 5077.");
+        }
+
+        // For TLS 1.3 only
+        byte[] getTicketNonce() throws IOException {
+            throw handshakeContext.conContext.fatal(Alert.ILLEGAL_PARAMETER,
+                    "TicketNonce not part of RFC 5077.");
+        }
+
+    }
+    /**
+     * NewSessionTicket for TLS 1.2 and below (RFC 5077)
      */
-    static final class NewSessionTicketMessage extends HandshakeMessage {
-        final int ticketLifetime;
-        final int ticketAgeAdd;
-        final byte[] ticketNonce;
-        final byte[] ticket;
-        final SSLExtensions extensions;
+    static final class T12NewSessionTicketMessage extends NewSessionTicketMessage {
+
+        T12NewSessionTicketMessage(HandshakeContext context,
+                int ticketLifetime, byte[] ticket) {
+            super(context);
+
+            this.ticketLifetime = ticketLifetime;
+            this.ticket = ticket;
+        }
+
+        T12NewSessionTicketMessage(HandshakeContext context,
+                ByteBuffer m) throws IOException {
+
+            // RFC5077 struct {
+            //     uint32 ticket_lifetime;
+            //     opaque ticket<1..2^16-1>;
+            // } NewSessionTicket;
+
+            super(context);
+            if (m.remaining() < 14) {
+                throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
+                        "Invalid NewSessionTicket message: no sufficient data");
+            }
+
+            this.ticketLifetime = Record.getInt32(m);
+            this.ticket = Record.getBytes16(m);
+        }
+
+        @Override
+        public SSLHandshake handshakeType() {
+            return NEW_SESSION_TICKET;
+        }
 
-        NewSessionTicketMessage(HandshakeContext context,
+        @Override
+        public int messageLength() {
+            return 4 + // ticketLifetime
+                    2 + ticket.length;  // len of ticket + ticket
+        }
+
+        @Override
+        public void send(HandshakeOutStream hos) throws IOException {
+            hos.putInt32(ticketLifetime);
+            hos.putBytes16(ticket);
+        }
+
+        @Override
+        public String toString() {
+            MessageFormat messageFormat = new MessageFormat(
+                    "\"NewSessionTicket\": '{'\n" +
+                            "  \"ticket_lifetime\"      : \"{0}\",\n" +
+                            "  \"ticket\"               : '{'\n" +
+                            "{1}\n" +
+                            "  '}'" +
+                            "'}'",
+                Locale.ENGLISH);
+
+            HexDumpEncoder hexEncoder = new HexDumpEncoder();
+            Object[] messageFields = {
+                    ticketLifetime,
+                    Utilities.indent(hexEncoder.encode(ticket), "    "),
+            };
+            return messageFormat.format(messageFields);
+        }
+    }
+
+    /**
+     * NewSessionTicket defined by the TLS 1.3
+     */
+    static final class T13NewSessionTicketMessage extends NewSessionTicketMessage {
+        int ticketAgeAdd;
+        byte[] ticketNonce;
+        SSLExtensions extensions;
+
+        T13NewSessionTicketMessage(HandshakeContext context,
                 int ticketLifetime, SecureRandom generator,
                 byte[] ticketNonce, byte[] ticket) {
             super(context);
@@ -73,7 +172,7 @@
             this.extensions = new SSLExtensions(this);
         }
 
-        NewSessionTicketMessage(HandshakeContext context,
+        T13NewSessionTicketMessage(HandshakeContext context,
                 ByteBuffer m) throws IOException {
             super(context);
 
@@ -84,6 +183,7 @@
             //     opaque ticket<1..2^16-1>;
             //     Extension extensions<0..2^16-2>;
             // } NewSessionTicket;
+
             if (m.remaining() < 14) {
                 throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
                     "Invalid NewSessionTicket message: no sufficient data");
@@ -111,24 +211,36 @@
 
             SSLExtension[] supportedExtensions =
                     context.sslConfig.getEnabledExtensions(
-                            SSLHandshake.NEW_SESSION_TICKET);
+                            NEW_SESSION_TICKET);
             this.extensions = new SSLExtensions(this, m, supportedExtensions);
         }
 
         @Override
         public SSLHandshake handshakeType() {
-            return SSLHandshake.NEW_SESSION_TICKET;
+            return NEW_SESSION_TICKET;
+        }
+
+        int getTicketAgeAdd() {
+            return ticketAgeAdd;
+        }
+
+        byte[] getTicketNonce() {
+            return ticketNonce;
         }
 
         @Override
         public int messageLength() {
+
             int extLen = extensions.length();
             if (extLen == 0) {
                 extLen = 2;     // empty extensions
             }
 
-            return 8 + ticketNonce.length + 1 +
-                       ticket.length + 2 + extLen;
+            return 4 +// ticketLifetime
+                    4 + // ticketAgeAdd
+                    1 + ticketNonce.length + // len of nonce + nonce
+                    2 + ticket.length + // len of ticket + ticket
+                    extLen;
         }
 
         @Override
@@ -153,18 +265,21 @@
                 "  \"ticket_lifetime\"      : \"{0}\",\n" +
                 "  \"ticket_age_add\"       : \"{1}\",\n" +
                 "  \"ticket_nonce\"         : \"{2}\",\n" +
-                "  \"ticket\"               : \"{3}\",\n" +
+                "  \"ticket\"               : '{'\n" +
+                "{3}\n" +
+                "  '}'" +
                 "  \"extensions\"           : [\n" +
                 "{4}\n" +
                 "  ]\n" +
                 "'}'",
                 Locale.ENGLISH);
 
+            HexDumpEncoder hexEncoder = new HexDumpEncoder();
             Object[] messageFields = {
                 ticketLifetime,
                 "<omitted>",    //ticketAgeAdd should not be logged
                 Utilities.toHexString(ticketNonce),
-                Utilities.toHexString(ticket),
+                Utilities.indent(hexEncoder.encode(ticket), "    "),
                 Utilities.indent(extensions.toString(), "    ")
             };
 
@@ -248,25 +363,46 @@
                 }
                 return null;
             }
-            NewSessionTicketMessage nstm = new NewSessionTicketMessage(shc,
-                sessionTimeoutSeconds, shc.sslContext.getSecureRandom(),
-                nonceArr, newId.getId());
-            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
-                SSLLogger.fine(
-                        "Produced NewSessionTicket handshake message", nstm);
-            }
 
-            // create and cache the new session
-            // The new session must be a child of the existing session so
-            // they will be invalidated together, etc.
+            NewSessionTicketMessage nstm;
+
             SSLSessionImpl sessionCopy =
                     new SSLSessionImpl(shc.handshakeSession, newId);
-            shc.handshakeSession.addChild(sessionCopy);
             sessionCopy.setPreSharedKey(psk);
             sessionCopy.setPskIdentity(newId.getId());
-            sessionCopy.setTicketAgeAdd(nstm.ticketAgeAdd);
-            sessionCache.put(sessionCopy);
 
+            if (shc.statelessResumption) {
+                try {
+                    nstm = new T13NewSessionTicketMessage(shc,
+                            sessionTimeoutSeconds, shc.sslContext.getSecureRandom(),
+                            nonceArr, new SessionTicketSpec().encrypt(shc, sessionCopy));
+                    if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                        SSLLogger.fine(
+                                "Produced NewSessionTicket stateless " +
+                                        "handshake message", nstm);
+                    }
+                } catch (Exception e) {
+                    // Error with NST ticket, abort NST
+                    shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, e);
+                    return null;
+                }
+            } else {
+                nstm = new T13NewSessionTicketMessage(shc, sessionTimeoutSeconds,
+                        shc.sslContext.getSecureRandom(), nonceArr,
+                        newId.getId());
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.fine(
+                            "Produced NewSessionTicket handshake message",
+                            nstm);
+                }
+
+                // create and cache the new session
+                // The new session must be a child of the existing session so
+                // they will be invalidated together, etc.
+                shc.handshakeSession.addChild(sessionCopy);
+                sessionCopy.setTicketAgeAdd(nstm.getTicketAgeAdd());
+                sessionCache.put(sessionCopy);
+            }
             // Output the handshake message.
             nstm.write(shc.handshakeOutput);
             shc.handshakeOutput.flush();
@@ -277,13 +413,13 @@
     }
 
     /**
-     * The "NewSessionTicket" handshake message producer.
+     * The "NewSessionTicket" handshake message producer for RFC 5077
      */
-    private static final class NewSessionTicketProducer
+    private static final class T12NewSessionTicketProducer
             implements HandshakeProducer {
 
         // Prevent instantiation of this class.
-        private NewSessionTicketProducer() {
+        private T12NewSessionTicketProducer() {
             // blank
         }
 
@@ -291,24 +427,65 @@
         public byte[] produce(ConnectionContext context,
                 HandshakeMessage message) throws IOException {
 
-            // NSTM may be sent in response to handshake messages.
-            // For example: key update
+            ServerHandshakeContext shc = (ServerHandshakeContext)context;
+
+            // Is this session resumable?
+            if (!shc.handshakeSession.isRejoinable()) {
+                return null;
+            }
+
+            // get a new session ID
+            SessionId newId = shc.handshakeSession.getSessionId();
+
+            SSLSessionContextImpl sessionCache = (SSLSessionContextImpl)
+                    shc.sslContext.engineGetServerSessionContext();
+            int sessionTimeoutSeconds = sessionCache.getSessionTimeout();
+            if (sessionTimeoutSeconds > MAX_TICKET_LIFETIME) {
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.fine(
+                        "Session timeout is too long. No ticket sent.");
+                }
+                return null;
+            }
+
+            NewSessionTicketMessage nstm;
 
-            throw new ProviderException(
-                "NewSessionTicket handshake producer not implemented");
+            SSLSessionImpl sessionCopy =
+                    new SSLSessionImpl(shc.handshakeSession, newId);
+            sessionCopy.setPskIdentity(newId.getId());
+
+            try {
+                nstm = new T12NewSessionTicketMessage(shc, sessionTimeoutSeconds,
+                        new SessionTicketSpec().encrypt(shc, sessionCopy));
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.fine(
+                            "Produced NewSessionTicket stateless handshake message", nstm);
+                }
+            } catch (Exception e) {
+                // Abort on error with NST ticket
+                shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, e);
+                return null;
+            }
+
+            // Output the handshake message.
+            nstm.write(shc.handshakeOutput);
+            shc.handshakeOutput.flush();
+
+            // The message has been delivered.
+            return null;
         }
     }
 
     private static final
-            class NewSessionTicketConsumer implements SSLConsumer {
+    class T13NewSessionTicketConsumer implements SSLConsumer {
         // Prevent instantiation of this class.
-        private NewSessionTicketConsumer() {
+        private T13NewSessionTicketConsumer() {
             // blank
         }
 
         @Override
         public void consume(ConnectionContext context,
-                            ByteBuffer message) throws IOException {
+                ByteBuffer message) throws IOException {
 
             // Note: Although the resumption master secret depends on the
             // client's second flight, servers which do not request client
@@ -317,13 +494,12 @@
             // 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.
+            // The consuming happens in client side only and is received after
+            // the server's Finished message with PostHandshakeContext.
+
             HandshakeContext hc = (HandshakeContext)context;
             NewSessionTicketMessage nstm =
-                    new NewSessionTicketMessage(hc, message);
+                    new T13NewSessionTicketMessage(hc, message);
             if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
                 SSLLogger.fine(
                 "Consuming NewSessionTicket message", nstm);
@@ -352,37 +528,95 @@
             }
 
             SSLSessionImpl sessionToSave = hc.conContext.conSession;
-
-            SecretKey resumptionMasterSecret =
-                sessionToSave.getResumptionMasterSecret();
-            if (resumptionMasterSecret == null) {
-                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
-                    SSLLogger.fine(
-                    "Session has no resumption master secret. Ignoring ticket.");
+            SecretKey psk = null;
+            if (hc.negotiatedProtocol.useTLS13PlusSpec()) {
+                SecretKey resumptionMasterSecret =
+                        sessionToSave.getResumptionMasterSecret();
+                if (resumptionMasterSecret == null) {
+                    if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                        SSLLogger.fine(
+                                "Session has no resumption master secret." +
+                                        " Ignoring ticket.");
+                    }
+                    return;
                 }
-                return;
+
+                // derive the PSK
+                psk = derivePreSharedKey(
+                        sessionToSave.getSuite().hashAlg,
+                        resumptionMasterSecret, nstm.getTicketNonce());
             }
 
-            // derive the PSK
-            SecretKey psk = derivePreSharedKey(
-                sessionToSave.getSuite().hashAlg, resumptionMasterSecret,
-                nstm.ticketNonce);
-
             // create and cache the new session
             // The new session must be a child of the existing session so
             // they will be invalidated together, etc.
             SessionId newId =
-                new SessionId(true, hc.sslContext.getSecureRandom());
+                    new SessionId(true, hc.sslContext.getSecureRandom());
             SSLSessionImpl sessionCopy = new SSLSessionImpl(sessionToSave,
                     newId);
             sessionToSave.addChild(sessionCopy);
             sessionCopy.setPreSharedKey(psk);
-            sessionCopy.setTicketAgeAdd(nstm.ticketAgeAdd);
+            sessionCopy.setTicketAgeAdd(nstm.getTicketAgeAdd());
             sessionCopy.setPskIdentity(nstm.ticket);
             sessionCache.put(sessionCopy);
 
             // clean handshake context
-            hc.conContext.finishPostHandshake();
+            if (hc.negotiatedProtocol.useTLS13PlusSpec()) {
+                hc.conContext.finishPostHandshake();
+            }
+        }
+    }
+
+    private static final
+    class T12NewSessionTicketConsumer implements SSLConsumer {
+        // Prevent instantiation of this class.
+        private T12NewSessionTicketConsumer() {
+            // blank
+        }
+
+        @Override
+        public void consume(ConnectionContext context,
+                ByteBuffer message) throws IOException {
+
+            HandshakeContext hc = (HandshakeContext)context;
+            hc.handshakeConsumers.remove(NEW_SESSION_TICKET.id);
+
+            NewSessionTicketMessage nstm = new T12NewSessionTicketMessage(hc,
+                    message);
+            if (nstm.ticket.length == 0) {
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.fine("NewSessionTicket ticket was empty");
+                }
+                return;
+            }
+
+            // discard tickets with timeout 0
+            if (nstm.ticketLifetime <= 0 ||
+                nstm.ticketLifetime > MAX_TICKET_LIFETIME) {
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.fine(
+                    "Discarding NewSessionTicket with lifetime "
+                        + nstm.ticketLifetime, nstm);
+                }
+                return;
+            }
+
+            SSLSessionContextImpl sessionCache = (SSLSessionContextImpl)
+                    hc.sslContext.engineGetClientSessionContext();
+
+            if (sessionCache.getSessionTimeout() > MAX_TICKET_LIFETIME) {
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.fine(
+                    "Session cache lifetime is too long. Discarding ticket.");
+                }
+                return;
+            }
+
+            hc.handshakeSession.setPskIdentity(nstm.ticket);
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                SSLLogger.fine("Consuming NewSessionTicket\n" +
+                        nstm.toString());
+            }
         }
     }
 }
--- a/src/java.base/share/classes/sun/security/ssl/PreSharedKeyExtension.java	Tue Jun 11 19:15:31 2019 -0400
+++ b/src/java.base/share/classes/sun/security/ssl/PreSharedKeyExtension.java	Tue Jun 11 16:31:37 2019 -0700
@@ -32,7 +32,6 @@
 import java.util.ArrayList;
 import java.util.Locale;
 import java.util.Arrays;
-import java.util.Objects;
 import java.util.Collection;
 import javax.crypto.Mac;
 import javax.crypto.SecretKey;
@@ -42,6 +41,9 @@
 import sun.security.ssl.SSLExtension.ExtensionConsumer;
 import sun.security.ssl.SSLExtension.SSLExtensionSpec;
 import sun.security.ssl.SSLHandshake.HandshakeMessage;
+import sun.security.ssl.SessionTicketExtension.SessionTicketSpec;
+import sun.security.util.HexDumpEncoder;
+
 import static sun.security.ssl.SSLExtension.*;
 
 /**
@@ -88,7 +90,7 @@
 
         @Override
         public String toString() {
-            return "{" + Utilities.toHexString(identity) + "," +
+            return "{" + Utilities.toHexString(identity) + ", " +
                 obfuscatedAge + "}";
         }
     }
@@ -208,8 +210,10 @@
         public String toString() {
             MessageFormat messageFormat = new MessageFormat(
                 "\"PreSharedKey\": '{'\n" +
-                "  \"identities\"    : \"{0}\",\n" +
-                "  \"binders\"       : \"{1}\",\n" +
+                "  \"identities\": '{'\n" +
+                "{0}\n" +
+                "  '}'" +
+                "  \"binders\": \"{1}\",\n" +
                 "'}'",
                 Locale.ENGLISH);
 
@@ -222,9 +226,13 @@
         }
 
         String identitiesString() {
+            HexDumpEncoder hexEncoder = new HexDumpEncoder();
+
             StringBuilder result = new StringBuilder();
             for (PskIdentity curId : identities) {
-                result.append(curId.toString() + "\n");
+                result.append("  {\n"+ Utilities.indent(
+                        hexEncoder.encode(curId.identity), "    ") +
+                        "\n  }\n");
             }
 
             return result.toString();
@@ -278,7 +286,7 @@
             this.selectedIdentity = Record.getInt16(m);
         }
 
-        byte[] getEncoded() throws IOException {
+        byte[] getEncoded() {
             return new byte[] {
                 (byte)((selectedIdentity >> 8) & 0xFF),
                 (byte)(selectedIdentity & 0xFF)
@@ -368,8 +376,36 @@
                 SSLSessionContextImpl sessionCache = (SSLSessionContextImpl)
                         shc.sslContext.engineGetServerSessionContext();
                 int idIndex = 0;
+                SSLSessionImpl s = null;
+
                 for (PskIdentity requestedId : pskSpec.identities) {
-                    SSLSessionImpl s = sessionCache.get(requestedId.identity);
+                    // If we are keeping state, see if the identity is in the cache
+                    if (requestedId.identity.length == SessionId.MAX_LENGTH) {
+                        s = sessionCache.get(requestedId.identity);
+                    }
+                    // See if the identity is a stateless ticket
+                    if (s == null &&
+                            requestedId.identity.length > SessionId.MAX_LENGTH &&
+                            sessionCache.statelessEnabled()) {
+                        ByteBuffer b =
+                                new SessionTicketSpec(requestedId.identity).
+                                        decrypt(shc);
+                        if (b != null) {
+                            try {
+                                s = new SSLSessionImpl(shc, b);
+                            } catch (IOException | RuntimeException e) {
+                                s = null;
+                            }
+                        }
+                        if (b == null || s == null) {
+                            if (SSLLogger.isOn &&
+                                    SSLLogger.isOn("ssl,handshake")) {
+                                SSLLogger.fine(
+                                        "Stateless session ticket invalid");
+                            }
+                        }
+                    }
+
                     if (s != null && canRejoin(clientHello, shc, s)) {
                         if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
                             SSLLogger.fine("Resuming session: ", s);
@@ -391,7 +427,6 @@
                     shc.resumingSession = null;
                 }
             }
-
             // update the context
             shc.handshakeExtensions.put(
                 SSLExtension.CH_PRE_SHARED_KEY, pskSpec);
@@ -708,7 +743,8 @@
                 int hashLength, List<PskIdentity> identities) {
             List<byte[]> binders = new ArrayList<>();
             byte[] binderProto = new byte[hashLength];
-            for (PskIdentity curId : identities) {
+            int i = identities.size();
+            while (i-- > 0) {
                 binders.add(binderProto);
             }
 
--- a/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java	Tue Jun 11 19:15:31 2019 -0400
+++ b/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java	Tue Jun 11 16:31:37 2019 -0700
@@ -71,11 +71,13 @@
     private volatile StatusResponseManager statusResponseManager;
 
     private final ReentrantLock contextLock = new ReentrantLock();
+    final HashMap<Integer, SessionTicketExtension.StatelessKey> keyHashMap = new HashMap<>();
+
 
     SSLContextImpl() {
         ephemeralKeyManager = new EphemeralKeyManager();
-        clientCache = new SSLSessionContextImpl();
-        serverCache = new SSLSessionContextImpl();
+        clientCache = new SSLSessionContextImpl(false);
+        serverCache = new SSLSessionContextImpl(true);
     }
 
     @Override
--- a/src/java.base/share/classes/sun/security/ssl/SSLExtension.java	Tue Jun 11 19:15:31 2019 -0400
+++ b/src/java.base/share/classes/sun/security/ssl/SSLExtension.java	Tue Jun 11 16:31:37 2019 -0700
@@ -309,8 +309,28 @@
     // extensions defined in RFC 7924
     CACHED_INFO             (0x0019, "cached_info"),
 
-    // extensions defined in RFC 4507/5077
-    SESSION_TICKET          (0x0023, "session_ticket"),
+    // extensions defined in RFC 5077
+    CH_SESSION_TICKET       (0x0023, "session_ticket",
+            SSLHandshake.CLIENT_HELLO,
+            ProtocolVersion.PROTOCOLS_10_12,
+            SessionTicketExtension.chNetworkProducer,
+            SessionTicketExtension.chOnLoadConsumer,
+            null,
+            null,
+            null,
+            SessionTicketExtension.steStringizer),
+            //null),
+
+    SH_SESSION_TICKET       (0x0023, "session_ticket",
+            SSLHandshake.SERVER_HELLO,
+            ProtocolVersion.PROTOCOLS_10_12,
+            SessionTicketExtension.shNetworkProducer,
+            SessionTicketExtension.shOnLoadConsumer,
+            null,
+            null,
+            null,
+            SessionTicketExtension.steStringizer),
+            //null),
 
     // extensions defined in TLS 1.3
     CH_EARLY_DATA           (0x002A, "early_data"),
--- a/src/java.base/share/classes/sun/security/ssl/SSLHandshake.java	Tue Jun 11 19:15:31 2019 -0400
+++ b/src/java.base/share/classes/sun/security/ssl/SSLHandshake.java	Tue Jun 11 16:31:37 2019 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2006, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2006, 2019, 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
@@ -115,15 +115,19 @@
     NEW_SESSION_TICKET          ((byte)0x04, "new_session_ticket",
         (Map.Entry<SSLConsumer, ProtocolVersion[]>[])(new Map.Entry[] {
             new SimpleImmutableEntry<SSLConsumer, ProtocolVersion[]>(
-                NewSessionTicket.handshakeConsumer,
-                ProtocolVersion.PROTOCOLS_OF_13
-        )
+                 NewSessionTicket.handshake12Consumer,
+                 ProtocolVersion.PROTOCOLS_TO_12
+            ),
+            new SimpleImmutableEntry<SSLConsumer, ProtocolVersion[]>(
+                 NewSessionTicket.handshakeConsumer,
+                 ProtocolVersion.PROTOCOLS_OF_13
+            )
         }),
         (Map.Entry<HandshakeProducer, ProtocolVersion[]>[])(new Map.Entry[] {
             new SimpleImmutableEntry<HandshakeProducer, ProtocolVersion[]>(
-                NewSessionTicket.handshakeProducer,
-                ProtocolVersion.PROTOCOLS_OF_13
-        )
+                 NewSessionTicket.handshake12Producer,
+                 ProtocolVersion.PROTOCOLS_TO_12
+            )
         })),
     END_OF_EARLY_DATA           ((byte)0x05, "end_of_early_data"),
 
--- a/src/java.base/share/classes/sun/security/ssl/SSLSessionContextImpl.java	Tue Jun 11 19:15:31 2019 -0400
+++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionContextImpl.java	Tue Jun 11 16:31:37 2019 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1999, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2019, 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
@@ -33,11 +33,34 @@
 import javax.net.ssl.SSLSessionContext;
 
 import sun.security.action.GetIntegerAction;
+import sun.security.action.GetPropertyAction;
 import sun.security.util.Cache;
 
 
+/**
+ * @systemProperty jdk.tls.server.enableSessionTicketExtension} determines if the
+ * server will provide stateless session tickets, if the client supports it,
+ * as described in RFC 5077 and RFC 8446.  a stateless session ticket
+ * contains the encrypted server's state which saves server resources.
+ *
+ * {@systemProperty jdk.tls.client.enableSessionTicketExtension} determines if the
+ * client will send an extension in the ClientHello in the pre-TLS 1.3.
+ * This extension allows the client to accept the server's session state for
+ * Server Side stateless resumption (RFC 5077).  Setting the property to
+ * "true" turns this on, by default it is false.  For TLS 1.3, the system
+ * property is not needed as this support is part of the spec.
+ *
+ * {@systemProperty jdk.tls.server.sessionTicketTimeout} determines how long
+ * a session in the server cache or the stateless resumption tickets are
+ * available for use.  The value set by the property can be modified by
+ * {@code SSLSessionContext.setSessionTimeout()} during runtime.
+ *
+ */
+
 final class SSLSessionContextImpl implements SSLSessionContext {
     private final static int DEFAULT_MAX_CACHE_SIZE = 20480;
+    // Default lifetime of a session. 24 hours
+    final static int DEFAULT_SESSION_TIMEOUT = 86400;
 
     private final Cache<SessionId, SSLSessionImpl> sessionCache;
                                         // session cache, session id as key
@@ -46,16 +69,24 @@
     private int cacheLimit;             // the max cache size
     private int timeout;                // timeout in seconds
 
+    // Does this context support stateless session (RFC 5077)
+    private boolean statelessSession = true;
+
     // package private
-    SSLSessionContextImpl() {
-        cacheLimit = getDefaultCacheLimit();    // default cache size
-        timeout = 86400;                        // default, 24 hours
+    SSLSessionContextImpl(boolean server) {
+        timeout = DEFAULT_SESSION_TIMEOUT;
+        cacheLimit = getDefaults(server);    // default cache size
 
         // use soft reference
         sessionCache = Cache.newSoftMemoryCache(cacheLimit, timeout);
         sessionHostPortCache = Cache.newSoftMemoryCache(cacheLimit, timeout);
     }
 
+    // Stateless sessions when available, but there is a cache
+    boolean statelessEnabled() {
+        return statelessSession;
+    }
+
     /**
      * Returns the <code>SSLSession</code> bound to the specified session id.
      */
@@ -163,8 +194,7 @@
     }
 
     private static String getKey(String hostname, int port) {
-        return (hostname + ":" +
-            String.valueOf(port)).toLowerCase(Locale.ENGLISH);
+        return (hostname + ":" + port).toLowerCase(Locale.ENGLISH);
     }
 
     // cache a SSLSession
@@ -197,8 +227,51 @@
         }
     }
 
-    private static int getDefaultCacheLimit() {
+    private int getDefaults(boolean server) {
         try {
+            String st;
+
+            // Property for Session Cache state
+            if (server) {
+                st = GetPropertyAction.privilegedGetProperty(
+                        "jdk.tls.server.enableSessionTicketExtension", "true");
+            } else {
+                st = GetPropertyAction.privilegedGetProperty(
+                        "jdk.tls.client.enableSessionTicketExtension", "true");
+            }
+            if (st.compareToIgnoreCase("false") == 0) {
+                statelessSession = false;
+            }
+
+            // Property for Session Ticket Timeout.  The value can be changed
+            // by SSLSessionContext.setSessionTimeout(int)
+            String s = GetPropertyAction.privilegedGetProperty(
+                    "jdk.tls.server.sessionTicketTimeout");
+            if (s != null) {
+                try {
+                    int t = Integer.parseInt(s);
+                    if (t < 0 ||
+                            t > NewSessionTicket.MAX_TICKET_LIFETIME) {
+                        timeout = DEFAULT_SESSION_TIMEOUT;
+                        if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                            SSLLogger.warning("Invalid timeout given " +
+                                    "jdk.tls.server.sessionTicketTimeout: " + t +
+                                    ".  Set to default value " + timeout);
+                        }
+                    } else {
+                        timeout = t;
+                    }
+                } catch (NumberFormatException e) {
+                    setSessionTimeout(DEFAULT_SESSION_TIMEOUT);
+                    if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                        SSLLogger.warning("Invalid timeout for " +
+                                "jdk.tls.server.sessionTicketTimeout: " + s +
+                                ".  Set to default value " + timeout);
+
+                    }
+                }
+            }
+
             int defaultCacheLimit = GetIntegerAction.privilegedGetProperty(
                     "javax.net.ssl.sessionCacheSize", DEFAULT_MAX_CACHE_SIZE);
 
--- a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java	Tue Jun 11 19:15:31 2019 -0400
+++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java	Tue Jun 11 16:31:37 2019 -0700
@@ -24,8 +24,12 @@
  */
 package sun.security.ssl;
 
+import sun.security.x509.X509CertImpl;
+
+import java.io.IOException;
 import java.math.BigInteger;
 import java.net.InetAddress;
+import java.nio.ByteBuffer;
 import java.security.Principal;
 import java.security.PrivateKey;
 import java.security.cert.CertificateEncodingException;
@@ -40,8 +44,11 @@
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.locks.ReentrantLock;
 import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
 import javax.net.ssl.ExtendedSSLSession;
+import javax.net.ssl.SNIHostName;
 import javax.net.ssl.SNIServerName;
+import javax.net.ssl.SSLException;
 import javax.net.ssl.SSLPeerUnverifiedException;
 import javax.net.ssl.SSLPermission;
 import javax.net.ssl.SSLSessionBindingEvent;
@@ -251,6 +258,371 @@
         }
     }
 
+    /**
+     * < 2 bytes > protocolVersion
+     * < 2 bytes > cipherSuite
+     * < 2 bytes > localSupportedSignAlgs entries
+     *   < 2 bytes per entries > localSupportedSignAlgs
+     * < 2 bytes > preSharedKey length
+     * < length in bytes > preSharedKey
+     * < 1 byte > pskIdentity length
+     * < length in bytes > pskIdentity
+     * < 1 byte > masterSecret length
+     *   < 1 byte > masterSecret algorithm length
+     *   < length in bytes > masterSecret algorithm
+     *   < 2 bytes > masterSecretKey length
+     *   < length in bytes> masterSecretKey
+     * < 1 byte > useExtendedMasterSecret
+     * < 1 byte > identificationProtocol length
+     * < length in bytes > identificationProtocol
+     * < 1 byte > serverNameIndication length
+     * < length in bytes > serverNameIndication
+     * < 1 byte > Number of requestedServerNames entries
+     *   < 1 byte > ServerName length
+     *   < length in bytes > ServerName
+     * < 4 bytes > creationTime
+     * < 1 byte > Length of peer host
+     *   < length in bytes > peer host
+     * < 2 bytes> peer port
+     * < 1 byte > Number of peerCerts entries
+     *   < 4 byte > peerCert length
+     *   < length in bytes > peerCert
+     * < 1 byte > localCerts type (Cert, PSK, Anonymous)
+     *   Certificate
+     *     < 1 byte > Number of Certificate entries
+     *       < 4 byte> Certificate length
+     *       < length in bytes> Certificate
+     *   PSK
+     *     < 1 byte > Number of PSK entries
+     *       < 1 bytes > PSK algorithm length
+     *       < length in bytes > PSK algorithm string
+     *       < 4 bytes > PSK key length
+     *       < length in bytes> PSK key
+     *       < 4 bytes > PSK identity length
+     *       < length in bytes> PSK identity
+     *   Anonymous
+     *     < 1 byte >
+    */
+
+    SSLSessionImpl(HandshakeContext hc, ByteBuffer buf) throws IOException {
+        int i = 0;
+        byte[] b;
+
+        this.localSupportedSignAlgs = new ArrayList<>();
+
+        boundValues = null;
+
+        this.protocolVersion = ProtocolVersion.valueOf(Short.toUnsignedInt(buf.getShort()));
+
+        if (protocolVersion.useTLS13PlusSpec()) {
+            this.sessionId = new SessionId(false, null);
+        } else {
+            // The CH session id may reset this if it's provided
+            this.sessionId = new SessionId(true,
+                    hc.sslContext.getSecureRandom());
+        }
+
+        this.cipherSuite = CipherSuite.valueOf(Short.toUnsignedInt(buf.getShort()));
+
+        // Local Supported signature algorithms
+        i = Short.toUnsignedInt(buf.getShort());
+        while (i-- > 0) {
+            this.localSupportedSignAlgs.add(SignatureScheme.valueOf(
+                    Short.toUnsignedInt(buf.getShort())));
+        }
+
+        // PSK
+        i = Short.toUnsignedInt(buf.getShort());
+        if (i > 0) {
+            b = new byte[i];
+            // Get algorithm string
+            buf.get(b, 0, i);
+            // Encoded length
+            i = Short.toUnsignedInt(buf.getShort());
+            // Encoded SecretKey
+            b = new byte[i];
+            buf.get(b);
+            this.preSharedKey = new SecretKeySpec(b, "TlsMasterSecret");
+        } else {
+            this.preSharedKey = null;
+        }
+
+        // PSK identity
+        i = buf.get();
+        if (i > 0) {
+            b = new byte[i];
+            buf.get(b);
+            this.pskIdentity = b;
+        } else {
+            this.pskIdentity = null;
+        }
+
+        // Master secret length of secret key algorithm  (one byte)
+        i = buf.get();
+        if (i > 0) {
+            b = new byte[i];
+            // Get algorithm string
+            buf.get(b, 0, i);
+            // Encoded length
+            i = Short.toUnsignedInt(buf.getShort());
+            // Encoded SecretKey
+            b = new byte[i];
+            buf.get(b);
+            this.masterSecret = new SecretKeySpec(b, "TlsMasterSecret");
+        } else {
+            this.masterSecret = null;
+        }
+        // Use extended master secret
+        this.useExtendedMasterSecret = (buf.get() != 0);
+
+        // Identification Protocol
+        i = buf.get();
+        if (i == 0) {
+            identificationProtocol = null;
+        } else {
+            b = new byte[i];
+            identificationProtocol =
+                    buf.get(b, 0, i).asCharBuffer().toString();
+        }
+
+        // SNI
+        i = buf.get();  // length
+        if (i == 0) {
+            serverNameIndication = null;
+        } else {
+            b = new byte[i];
+            buf.get(b, 0, b.length);
+            serverNameIndication = new SNIHostName(b);
+        }
+
+        // List of SNIServerName
+        int len = Short.toUnsignedInt(buf.getShort());
+        if (len == 0) {
+            this.requestedServerNames = Collections.<SNIServerName>emptyList();
+        } else {
+            requestedServerNames = new ArrayList<>();
+            while (len > 0) {
+                int l = buf.get();
+                b = new byte[l];
+                buf.get(b, 0, l);
+                requestedServerNames.add(new SNIHostName(new String(b)));
+                len--;
+            }
+        }
+
+        // Get creation time
+        this.creationTime = buf.getLong();
+
+        // Get Peer host & port
+        i = Byte.toUnsignedInt(buf.get());
+        if (i == 0) {
+            this.host = new String();
+        } else {
+            b = new byte[i];
+            this.host = buf.get(b).toString();
+        }
+        this.port = Short.toUnsignedInt(buf.getShort());
+
+        // Peer certs
+        i = buf.get();
+        if (i == 0) {
+            this.peerCerts = null;
+        } else {
+            this.peerCerts = new X509Certificate[i];
+            int j = 0;
+            while (i > j) {
+                b = new byte[buf.getInt()];
+                buf.get(b);
+                try {
+                    this.peerCerts[j] = new X509CertImpl(b);
+                } catch (Exception e) {
+                    throw new IOException(e);
+                }
+                j++;
+            }
+        }
+
+        // Get local certs of PSK
+        switch (buf.get()) {
+            case 0:
+                break;
+            case 1:
+                // number of certs
+                len = buf.get();
+                this.localCerts = new X509Certificate[len];
+                i = 0;
+                while (len > i) {
+                    b = new byte[buf.getInt()];
+                    buf.get(b);
+                    try {
+                        this.localCerts[i] = new X509CertImpl(b);
+                    } catch (Exception e) {
+                        throw new IOException(e);
+                    }
+                    i++;
+                }
+                break;
+            case 2:
+                // pre-shared key
+                // Length of pre-shared key algorithm  (one byte)
+                i = buf.get();
+                b = new byte[i];
+                String alg = buf.get(b, 0, i).asCharBuffer().toString();
+                // Get length of encoding
+                i = Short.toUnsignedInt(buf.getShort());
+                // Get encoding
+                b = new byte[i];
+                buf.get(b);
+                this.preSharedKey = new SecretKeySpec(b, alg);
+                // Get identity len
+                this.pskIdentity = new byte[buf.get()];
+                buf.get(pskIdentity);
+                break;
+            default:
+                throw new SSLException("Failed local certs of session.");
+        }
+
+        context = (SSLSessionContextImpl)
+                hc.sslContext.engineGetServerSessionContext();
+    }
+
+    /**
+     * Write out a SSLSessionImpl in a byte array for a stateless session ticket
+     */
+    byte[] write() throws Exception {
+        byte[] b;
+        HandshakeOutStream hos = new HandshakeOutStream(null);
+
+        hos.putInt16(protocolVersion.id);
+        hos.putInt16(cipherSuite.id);
+
+        // Local Supported signature algorithms
+        int l = localSupportedSignAlgs.size();
+        hos.putInt16(l);
+        SignatureScheme[] sig = new SignatureScheme[l];
+        localSupportedSignAlgs.toArray(sig);
+        for (SignatureScheme s : sig) {
+            hos.putInt16(s.id);
+        }
+
+        // PSK
+        if (preSharedKey == null ||
+                preSharedKey.getAlgorithm() == null) {
+            hos.putInt16(0);
+        } else {
+            hos.putInt16(preSharedKey.getAlgorithm().length());
+            if (preSharedKey.getAlgorithm().length() != 0) {
+                hos.write(preSharedKey.getAlgorithm().getBytes());
+            }
+            b = preSharedKey.getEncoded();
+            hos.putInt16(b.length);
+            hos.write(b, 0, b.length);
+        }
+
+        // PSK Identity
+        if (pskIdentity == null) {
+            hos.putInt8(0);
+        } else {
+            hos.putInt8(pskIdentity.length);
+            hos.write(pskIdentity, 0, pskIdentity.length);
+        }
+
+        // Master Secret
+        if (getMasterSecret() == null ||
+                getMasterSecret().getAlgorithm() == null) {
+            hos.putInt8(0);
+        } else {
+            hos.putInt8(getMasterSecret().getAlgorithm().length());
+            if (getMasterSecret().getAlgorithm().length() != 0) {
+                hos.write(getMasterSecret().getAlgorithm().getBytes());
+            }
+            b = getMasterSecret().getEncoded();
+            hos.putInt16(b.length);
+            hos.write(b, 0, b.length);
+        }
+
+        hos.putInt8(useExtendedMasterSecret ? 1 : 0);
+
+        // Identification Protocol
+        if (identificationProtocol == null) {
+            hos.putInt8(0);
+        } else {
+            hos.putInt8(identificationProtocol.length());
+            hos.write(identificationProtocol.getBytes(), 0,
+                    identificationProtocol.length());
+        }
+
+        // SNI
+        if (serverNameIndication == null) {
+            hos.putInt8(0);
+        } else {
+            b = serverNameIndication.getEncoded();
+            hos.putInt8(b.length);
+            hos.write(b, 0, b.length);
+        }
+
+        // List of SNIServerName
+        hos.putInt16(requestedServerNames.size());
+        if (requestedServerNames.size() > 0) {
+            for (SNIServerName host: requestedServerNames) {
+                b = host.getEncoded();
+                hos.putInt8(b.length);
+                hos.write(b, 0, b.length);
+            }
+        }
+
+        ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
+        hos.writeBytes(buffer.putLong(creationTime).array());
+
+        // peer Host & Port
+        if (host == null || host.length() == 0) {
+            hos.putInt8(0);
+        } else {
+            hos.putInt8(host.length());
+            hos.writeBytes(host.getBytes());
+        }
+        hos.putInt16(port);
+
+        // Peer cert
+        if (peerCerts == null || peerCerts.length == 0) {
+            hos.putInt8(0);
+        } else {
+            hos.putInt8(peerCerts.length);
+            for (X509Certificate c : peerCerts) {
+                b = c.getEncoded();
+                hos.putInt32(b.length);
+                hos.writeBytes(b);
+            }
+        }
+
+        // Client identity
+        if (localCerts != null && localCerts.length > 0) {
+            // certificate based
+            hos.putInt8(1);
+            hos.putInt8(localCerts.length);
+            for (X509Certificate c : localCerts) {
+                b = c.getEncoded();
+                hos.putInt32(b.length);
+                hos.writeBytes(b);
+            }
+        } else if (preSharedKey != null) {
+            // pre-shared key
+            hos.putInt8(2);
+            hos.putInt8(preSharedKey.getAlgorithm().length());
+            hos.write(preSharedKey.getAlgorithm().getBytes());
+            b = preSharedKey.getEncoded();
+            hos.putInt32(b.length);
+            hos.writeBytes(b);
+            hos.putInt32(pskIdentity.length);
+            hos.writeBytes(pskIdentity);
+        } else {
+            // anonymous
+            hos.putInt8(0);
+        }
+
+        return hos.toByteArray();
+    }
+
     void setMasterSecret(SecretKey secret) {
         masterSecret = secret;
     }
@@ -333,6 +705,10 @@
         }
     }
 
+    byte[] getPskIdentity() {
+        return pskIdentity;
+    }
+
     void setPeerCertificates(X509Certificate[] peer) {
         if (peerCerts == null) {
             peerCerts = peer;
@@ -400,8 +776,12 @@
      * maximum lifetime in any case.
      */
     boolean isRejoinable() {
+        // TLS 1.3 can have no session id
+        if (protocolVersion.useTLS13PlusSpec()) {
+            return (!invalidated && isLocalAuthenticationValid());
+        }
         return sessionId != null && sessionId.length() != 0 &&
-            !invalidated && isLocalAuthenticationValid();
+                !invalidated && isLocalAuthenticationValid();
     }
 
     @Override
--- a/src/java.base/share/classes/sun/security/ssl/ServerHello.java	Tue Jun 11 19:15:31 2019 -0400
+++ b/src/java.base/share/classes/sun/security/ssl/ServerHello.java	Tue Jun 11 16:31:37 2019 -0700
@@ -47,6 +47,8 @@
 import sun.security.ssl.SSLHandshake.HandshakeMessage;
 import sun.security.ssl.SupportedVersionsExtension.SHSupportedVersionsSpec;
 
+import static sun.security.ssl.SSLExtension.SH_SESSION_TICKET;
+
 /**
  * Pack of the ServerHello/HelloRetryRequest handshake message.
  */
@@ -337,6 +339,15 @@
                 shc.handshakeProducers.put(SSLHandshake.SERVER_HELLO_DONE.id,
                         SSLHandshake.SERVER_HELLO_DONE);
             } else {
+                // stateless and use the client session id (RFC 5077 3.4)
+                if (shc.statelessResumption) {
+                    shc.resumingSession = new SSLSessionImpl(shc.resumingSession,
+                            (clientHello.sessionId.length() == 0) ?
+                                    new SessionId(true,
+                                            shc.sslContext.getSecureRandom()) :
+                                    new SessionId(clientHello.sessionId.getId())
+                    );
+                }
                 shc.handshakeSession = shc.resumingSession;
                 shc.negotiatedProtocol =
                         shc.resumingSession.getProtocolVersion();
@@ -491,6 +502,9 @@
             ServerHandshakeContext shc = (ServerHandshakeContext)context;
             ClientHelloMessage clientHello = (ClientHelloMessage)message;
 
+            SSLSessionContextImpl sessionCache = (SSLSessionContextImpl)
+                    shc.sslContext.engineGetServerSessionContext();
+
             // If client hasn't specified a session we can resume, start a
             // new one and choose its cipher suite and compression options,
             // unless new session creation is disabled for this connection!
@@ -546,8 +560,6 @@
                         shc.resumingSession.consumePreSharedKey());
 
                 // The session can't be resumed again---remove it from cache
-                SSLSessionContextImpl sessionCache = (SSLSessionContextImpl)
-                    shc.sslContext.engineGetServerSessionContext();
                 sessionCache.remove(shc.resumingSession.getSessionId());
             }
 
@@ -679,6 +691,11 @@
             // Update the context for master key derivation.
             shc.handshakeKeyDerivation = kd;
 
+            // Check if the server supports stateless resumption
+            if (sessionCache.statelessEnabled()) {
+                shc.statelessResumption = true;
+            }
+
             // The handshake message has been delivered.
             return null;
         }
@@ -1098,9 +1115,23 @@
                     throw chc.conContext.fatal(Alert.PROTOCOL_VERSION,
                         "New session creation is disabled");
                 }
-                chc.handshakeSession = new SSLSessionImpl(chc,
-                        chc.negotiatedCipherSuite,
-                        serverHello.sessionId);
+
+                if (serverHello.sessionId.length() == 0 &&
+                        chc.statelessResumption) {
+                    SessionId newId = new SessionId(true,
+                            chc.sslContext.getSecureRandom());
+                    chc.handshakeSession = new SSLSessionImpl(chc,
+                            chc.negotiatedCipherSuite, newId);
+
+                    if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                        SSLLogger.fine("Locally assigned Session Id: " +
+                                newId.toString());
+                    }
+                } else {
+                    chc.handshakeSession = new SSLSessionImpl(chc,
+                            chc.negotiatedCipherSuite,
+                            serverHello.sessionId);
+                }
                 chc.handshakeSession.setMaximumPacketSize(
                         chc.sslConfig.maximumPacketSize);
             }
@@ -1127,6 +1158,11 @@
                 chc.conContext.consumers.putIfAbsent(
                         ContentType.CHANGE_CIPHER_SPEC.id,
                         ChangeCipherSpec.t10Consumer);
+                if (chc.statelessResumption) {
+                    chc.handshakeConsumers.putIfAbsent(
+                            SSLHandshake.NEW_SESSION_TICKET.id,
+                            SSLHandshake.NEW_SESSION_TICKET);
+                }
                 chc.handshakeConsumers.put(
                         SSLHandshake.FINISHED.id,
                         SSLHandshake.FINISHED);
--- a/src/java.base/share/classes/sun/security/ssl/SessionId.java	Tue Jun 11 19:15:31 2019 -0400
+++ b/src/java.base/share/classes/sun/security/ssl/SessionId.java	Tue Jun 11 16:31:37 2019 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1996, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2019, 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
@@ -36,7 +36,7 @@
  * @author David Brownell
  */
 final class SessionId {
-    private static final int MAX_LENGTH = 32;
+    static final int MAX_LENGTH = 32;
     private final byte[] sessionId;          // max 32 bytes
 
     // Constructs a new session ID ... perhaps for a rejoinable session
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/sun/security/ssl/SessionTicketExtension.java	Tue Jun 11 16:31:37 2019 -0700
@@ -0,0 +1,539 @@
+/*
+ * Copyright (c) 2019, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package sun.security.ssl;
+
+import sun.security.action.GetPropertyAction;
+import sun.security.ssl.SSLExtension.ExtensionConsumer;
+import sun.security.ssl.SSLExtension.SSLExtensionSpec;
+import sun.security.ssl.SSLHandshake.HandshakeMessage;
+import sun.security.ssl.SupportedGroupsExtension.SupportedGroups;
+import sun.security.util.HexDumpEncoder;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.net.ssl.SSLProtocolException;
+
+import static sun.security.ssl.SSLExtension.CH_SESSION_TICKET;
+import static sun.security.ssl.SSLExtension.SH_SESSION_TICKET;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.text.MessageFormat;
+import java.util.Collection;
+import java.util.Locale;
+
+/**
+ * SessionTicketExtension is an implementation of RFC 5077 with some internals
+ * that are used for stateless operation in TLS 1.3.
+ *
+ * {@systemProperty jdk.tls.server.statelessKeyTimeout} can override the default
+ * amount of time, in seconds, for how long a randomly-generated key and
+ * parameters can be used before being regenerated.  The key material is used
+ * to encrypt the stateless session ticket that is sent to the client that will
+ * be used during resumption.  Default is 3600 seconds (1 hour)
+ *
+ */
+
+final class SessionTicketExtension {
+
+    static final HandshakeProducer chNetworkProducer =
+            new T12CHSessionTicketProducer();
+    static final ExtensionConsumer chOnLoadConsumer =
+            new T12CHSessionTicketConsumer();
+    static final HandshakeProducer shNetworkProducer =
+            new T12SHSessionTicketProducer();
+    static final ExtensionConsumer shOnLoadConsumer =
+            new T12SHSessionTicketConsumer();
+
+    static final SSLStringizer steStringizer = new SessionTicketStringizer();
+
+    // Time in milliseconds until key is changed for encrypting session state
+    private static final int TIMEOUT_DEFAULT = 3600 * 1000;
+    private static final int keyTimeout;
+    private static int currentKeyID = new SecureRandom().nextInt();
+    private static final int KEYLEN = 256;
+
+    static {
+        String s = GetPropertyAction.privilegedGetProperty(
+                "jdk.tls.server.statelessKeyTimeout");
+        if (s != null) {
+            int kt;
+            try {
+                kt = Integer.parseInt(s) * 1000;  // change to ms
+                if (kt < 0 ||
+                        kt > NewSessionTicket.MAX_TICKET_LIFETIME) {
+                    if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                        SSLLogger.warning("Invalid timeout for " +
+                                "jdk.tls.server.statelessKeyTimeout: " +
+                                kt + ".  Set to default value " +
+                                TIMEOUT_DEFAULT + "sec");
+                    }
+                    kt = TIMEOUT_DEFAULT;
+                }
+            } catch (NumberFormatException e) {
+                kt = TIMEOUT_DEFAULT;
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                    SSLLogger.warning("Invalid timeout for " +
+                            "jdk.tls.server.statelessKeyTimeout: " + s +
+                            ".  Set to default value " + TIMEOUT_DEFAULT +
+                            "sec");
+                }
+            }
+            keyTimeout = kt;
+        } else {
+            keyTimeout = TIMEOUT_DEFAULT;
+        }
+    }
+
+    // Crypto key context for session state.  Used with stateless operation.
+    final static class StatelessKey {
+        final long timeout;
+        final SecretKey key;
+        final int num;
+
+        StatelessKey(HandshakeContext hc, int newNum) {
+            SecretKey k = null;
+            try {
+                KeyGenerator kg = KeyGenerator.getInstance("AES");
+                kg.init(KEYLEN, hc.sslContext.getSecureRandom());
+                k = kg.generateKey();
+            } catch (NoSuchAlgorithmException e) {
+                // should not happen;
+            }
+            key = k;
+            timeout = System.currentTimeMillis() + keyTimeout;
+            num = newNum;
+            hc.sslContext.keyHashMap.put(Integer.valueOf(num), this);
+        }
+
+        // Check if key needs to be changed
+        boolean isExpired() {
+            return ((System.currentTimeMillis()) > timeout);
+        }
+
+        // Check if this key is ready for deletion.
+        boolean isInvalid(long sessionTimeout) {
+            return ((System.currentTimeMillis()) > (timeout + sessionTimeout));
+        }
+    }
+
+    private static final class KeyState {
+
+        // Get a key with a specific key number
+        static StatelessKey getKey(HandshakeContext hc, int num)  {
+            StatelessKey ssk = hc.sslContext.keyHashMap.get(num);
+
+            if (ssk == null || ssk.isInvalid(getSessionTimeout(hc))) {
+                return null;
+            }
+            return ssk;
+        }
+
+        // Get the current valid key, this will generate a new key if needed
+        static StatelessKey getCurrentKey(HandshakeContext hc) {
+            StatelessKey ssk = hc.sslContext.keyHashMap.get(currentKeyID);
+
+            if (ssk != null && !ssk.isExpired()) {
+                return ssk;
+            }
+            return nextKey(hc);
+        }
+
+        // This method locks when the first getCurrentKey() finds it to be too
+        // old and create a new key to replace the current key.  After the new
+        // key established, the lock can be released so following
+        // operations will start using the new key.
+        // The first operation will take a longer code path by generating the
+        // next key and cleaning up old keys.
+        private static StatelessKey nextKey(HandshakeContext hc) {
+            StatelessKey ssk;
+
+            synchronized (hc.sslContext.keyHashMap) {
+                // If the current key is no longer expired, it was already
+                // updated by a previous operation and we can return.
+                ssk = hc.sslContext.keyHashMap.get(currentKeyID);
+                if (ssk != null && !ssk.isExpired()) {
+                    return ssk;
+                }
+                int newNum;
+                if (currentKeyID == Integer.MAX_VALUE) {
+                    newNum = 0;
+                } else {
+                    newNum = currentKeyID + 1;
+                }
+                // Get new key
+                ssk = new StatelessKey(hc, newNum);
+                currentKeyID = newNum;
+                // Release lock since the new key is ready to be used.
+            }
+
+            // Clean up any old keys, then return the current key
+            cleanup(hc);
+            return ssk;
+        }
+
+        // Deletes any invalid SessionStateKeys.
+        static void cleanup(HandshakeContext hc) {
+            int sessionTimeout = getSessionTimeout(hc);
+
+            StatelessKey ks;
+            for (Object o : hc.sslContext.keyHashMap.keySet().toArray()) {
+                Integer i = (Integer)o;
+                ks = hc.sslContext.keyHashMap.get(i);
+                if (ks.isInvalid(sessionTimeout)) {
+                    try {
+                        ks.key.destroy();
+                    } catch (Exception e) {
+                        // Suppress
+                    }
+                    hc.sslContext.keyHashMap.remove(i);
+                }
+            }
+        }
+
+        static int getSessionTimeout(HandshakeContext hc) {
+            return hc.sslContext.engineGetServerSessionContext().
+                    getSessionTimeout() * 1000;
+        }
+    }
+
+    /**
+     * This class contains the session state that is in the session ticket.
+     * Using the key associated with the ticket, the class encrypts and
+     * decrypts the data, but does not interpret the data.
+     */
+    static final class SessionTicketSpec implements SSLExtensionSpec {
+        private static final int GCM_TAG_LEN = 128;
+        ByteBuffer data;
+        static final ByteBuffer zero = ByteBuffer.wrap(new byte[0]);
+
+        SessionTicketSpec() {
+            data = zero;
+        }
+
+        SessionTicketSpec(byte[] b) throws IOException {
+            this(ByteBuffer.wrap(b));
+        }
+
+        SessionTicketSpec(ByteBuffer buf) throws IOException {
+            if (buf == null) {
+                throw new SSLProtocolException(
+                        "SessionTicket buffer too small");
+            }
+            if (buf.remaining() > 65536) {
+                throw new SSLProtocolException(
+                        "SessionTicket buffer too large. " + buf.remaining());
+            }
+
+            data = buf;
+        }
+
+        public byte[] encrypt(HandshakeContext hc, SSLSessionImpl session)
+                throws IOException {
+            byte[] encrypted;
+            StatelessKey key = KeyState.getCurrentKey(hc);
+            byte[] iv = new byte[16];
+
+            try {
+                SecureRandom random = hc.sslContext.getSecureRandom();
+                random.nextBytes(iv);
+                Cipher c = Cipher.getInstance("AES/GCM/NoPadding");
+                c.init(Cipher.ENCRYPT_MODE, key.key,
+                        new GCMParameterSpec(GCM_TAG_LEN, iv));
+                c.updateAAD(new byte[] {
+                        (byte)(key.num >>> 24),
+                        (byte)(key.num >>> 16),
+                        (byte)(key.num >>> 8),
+                        (byte)(key.num)}
+                );
+                encrypted = c.doFinal(session.write());
+
+                byte[] result = new byte[encrypted.length + Integer.BYTES +
+                        iv.length];
+                result[0] = (byte)(key.num >>> 24);
+                result[1] = (byte)(key.num >>> 16);
+                result[2] = (byte)(key.num >>> 8);
+                result[3] = (byte)(key.num);
+                System.arraycopy(iv, 0, result, Integer.BYTES, iv.length);
+                System.arraycopy(encrypted, 0, result,
+                        Integer.BYTES + iv.length, encrypted.length);
+                return result;
+            } catch (Exception e) {
+                throw hc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, e);
+            }
+        }
+
+        ByteBuffer decrypt(HandshakeContext hc) {
+            int keyID;
+            byte[] iv;
+            try {
+                keyID = data.getInt();
+                StatelessKey key = KeyState.getKey(hc, keyID);
+                if (key == null) {
+                    return null;
+                }
+
+                iv = new byte[16];
+                data.get(iv);
+                Cipher c = Cipher.getInstance("AES/GCM/NoPadding");
+                c.init(Cipher.DECRYPT_MODE, key.key,
+                        new GCMParameterSpec(GCM_TAG_LEN, iv));
+                c.updateAAD(new byte[] {
+                        (byte)(keyID >>> 24),
+                        (byte)(keyID >>> 16),
+                        (byte)(keyID >>> 8),
+                        (byte)(keyID)}
+                );
+                /*
+                return ByteBuffer.wrap(c.doFinal(data,
+                        Integer.BYTES + iv.length,
+                        data.length - (Integer.BYTES + iv.length)));
+                 */
+                ByteBuffer out;
+                out = ByteBuffer.allocate(data.remaining() - GCM_TAG_LEN / 8);
+                c.doFinal(data, out);
+                out.flip();
+                return out;
+            } catch (Exception e) {
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.fine("Decryption failed." + e.getMessage());
+                }
+            }
+            return null;
+        }
+
+        byte[] getEncoded() {
+            byte[] out = new byte[data.capacity()];
+            data.duplicate().get(out);
+            return out;
+        }
+
+        @Override
+        public String toString() {
+            if (data == null) {
+                return "<null>";
+            }
+            if (data.capacity() == 0) {
+                return "<empty>";
+            }
+
+            MessageFormat messageFormat = new MessageFormat(
+                    "  \"ticket\" : '{'\n" +
+                            "{0}\n" +
+                            "  '}'",
+                    Locale.ENGLISH);
+            HexDumpEncoder hexEncoder = new HexDumpEncoder();
+
+            Object[] messageFields = {
+                    Utilities.indent(hexEncoder.encode(data.duplicate()),
+                            "    "),
+            };
+
+            return messageFormat.format(messageFields);
+        }
+    }
+
+    static final class SessionTicketStringizer implements SSLStringizer {
+        SessionTicketStringizer() {}
+
+        @Override
+        public String toString(ByteBuffer buffer) {
+            try {
+                return new SessionTicketSpec(buffer).toString();
+            } catch (IOException e) {
+                return e.getMessage();
+            }
+        }
+    }
+
+    private static final class T12CHSessionTicketProducer
+            extends SupportedGroups implements HandshakeProducer {
+        T12CHSessionTicketProducer() {
+        }
+
+        @Override
+        public byte[] produce(ConnectionContext context,
+                HandshakeMessage message) throws IOException {
+
+            ClientHandshakeContext chc = (ClientHandshakeContext)context;
+
+            // If the context does not allow stateless tickets, exit
+            if (!((SSLSessionContextImpl)chc.sslContext.
+                    engineGetClientSessionContext()).statelessEnabled()) {
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.fine("Stateless resumption not supported");
+                }
+                return null;
+            }
+
+            chc.statelessResumption = true;
+
+            // If resumption is not in progress, return an empty value
+            if (!chc.isResumption || chc.resumingSession == null) {
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.fine("Stateless resumption supported");
+                }
+                return new SessionTicketSpec().getEncoded();
+            }
+
+            if (chc.localSupportedSignAlgs == null) {
+                chc.localSupportedSignAlgs =
+                        SignatureScheme.getSupportedAlgorithms(
+                                chc.algorithmConstraints, chc.activeProtocols);
+            }
+
+            return chc.resumingSession.getPskIdentity();
+        }
+
+    }
+
+    private static final class T12CHSessionTicketConsumer
+            implements ExtensionConsumer {
+        T12CHSessionTicketConsumer() {
+        }
+
+        @Override
+        public void consume(ConnectionContext context,
+                HandshakeMessage message, ByteBuffer buffer)
+                throws IOException {
+            ServerHandshakeContext shc = (ServerHandshakeContext) context;
+
+            // Skip if extension is not provided
+            if (!shc.sslConfig.isAvailable(CH_SESSION_TICKET)) {
+                return;
+            }
+
+            // Skip consumption if we are already in stateless resumption
+            if (shc.statelessResumption) {
+                return;
+            }
+            // If the context does not allow stateless tickets, exit
+            SSLSessionContextImpl cache = (SSLSessionContextImpl)shc.sslContext
+                    .engineGetServerSessionContext();
+            if (!cache.statelessEnabled()) {
+                return;
+            }
+
+            if (buffer.remaining() == 0) {
+                shc.statelessResumption = true;
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.fine("Client accepts session tickets.");
+                }
+                return;
+            }
+
+            // Parse the extension.
+            SessionTicketSpec spec;
+            try {
+                 spec = new SessionTicketSpec(buffer);
+            } catch (IOException | RuntimeException e) {
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.fine("SessionTicket data invalid. Doing full " +
+                            "handshake.");
+                }
+                return;
+            }
+            ByteBuffer b = spec.decrypt(shc);
+            if (b != null) {
+                shc.resumingSession = new SSLSessionImpl(shc, b);
+                shc.isResumption = true;
+                shc.statelessResumption = true;
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.fine("Valid stateless session ticket found");
+                }
+            }
+        }
+    }
+
+
+    private static final class T12SHSessionTicketProducer
+            extends SupportedGroups implements HandshakeProducer {
+        T12SHSessionTicketProducer() {
+        }
+
+        @Override
+        public byte[] produce(ConnectionContext context,
+                HandshakeMessage message) {
+
+            ServerHandshakeContext shc = (ServerHandshakeContext)context;
+
+            // If boolean is false, the CH did not have this extension
+            if (!shc.statelessResumption) {
+                return null;
+            }
+            // If the client has sent a SessionTicketExtension and stateless
+            // is enabled on the server, return an empty message.
+            // If the context does not allow stateless tickets, exit
+            SSLSessionContextImpl cache = (SSLSessionContextImpl)shc.sslContext
+                    .engineGetServerSessionContext();
+            if (cache.statelessEnabled()) {
+                return new byte[0];
+            }
+
+            shc.statelessResumption = false;
+            return null;
+        }
+    }
+
+    private static final class T12SHSessionTicketConsumer
+            implements ExtensionConsumer {
+        T12SHSessionTicketConsumer() {
+        }
+
+        @Override
+        public void consume(ConnectionContext context,
+                HandshakeMessage message, ByteBuffer buffer)
+                throws IOException {
+            ClientHandshakeContext chc = (ClientHandshakeContext) context;
+
+            // Skip if extension is not provided
+            if (!chc.sslConfig.isAvailable(SH_SESSION_TICKET)) {
+                chc.statelessResumption = false;
+                return;
+            }
+
+            // If the context does not allow stateless tickets, exit
+            if (!((SSLSessionContextImpl)chc.sslContext.
+                    engineGetClientSessionContext()).statelessEnabled()) {
+                chc.statelessResumption = false;
+                return;
+            }
+
+            try {
+                if (new SessionTicketSpec(buffer) == null) {
+                    return;
+                }
+                chc.statelessResumption = true;
+            } catch (IOException e) {
+                throw chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, e);
+            }
+        }
+    }
+}
--- a/src/java.base/share/classes/sun/security/ssl/TransportContext.java	Tue Jun 11 19:15:31 2019 -0400
+++ b/src/java.base/share/classes/sun/security/ssl/TransportContext.java	Tue Jun 11 16:31:37 2019 -0700
@@ -159,14 +159,19 @@
                 if (handshakeContext == null) {
                     if (type == SSLHandshake.KEY_UPDATE.id ||
                             type == SSLHandshake.NEW_SESSION_TICKET.id) {
-                        if (isNegotiated &&
-                                protocolVersion.useTLS13PlusSpec()) {
-                            handshakeContext = new PostHandshakeContext(this);
-                        } else {
+                        if (!isNegotiated) {
+                            throw fatal(Alert.UNEXPECTED_MESSAGE,
+                                    "Unexpected unnegotiated post-handshake" +
+                                            " message: " +
+                                            SSLHandshake.nameOf(type));
+                        }
+                        if (type == SSLHandshake.KEY_UPDATE.id &&
+                                !protocolVersion.useTLS13PlusSpec()) {
                             throw fatal(Alert.UNEXPECTED_MESSAGE,
                                     "Unexpected post-handshake message: " +
                                     SSLHandshake.nameOf(type));
                         }
+                        handshakeContext = new PostHandshakeContext(this);
                     } else {
                         handshakeContext = sslConfig.isClientMode ?
                                 new ClientHandshakeContext(sslContext, this) :
--- a/test/jdk/javax/net/ssl/DTLS/PacketLossRetransmission.java	Tue Jun 11 19:15:31 2019 -0400
+++ b/test/jdk/javax/net/ssl/DTLS/PacketLossRetransmission.java	Tue Jun 11 16:31:37 2019 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2019 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
@@ -35,7 +35,7 @@
  * @run main/othervm PacketLossRetransmission client 1 client_hello
  * @run main/othervm PacketLossRetransmission client 2 server_hello
  * @run main/othervm PacketLossRetransmission client 3 hello_verify_request
- * @run main/othervm PacketLossRetransmission client 4 new_session_ticket
+ * @run main/othervm -Djdk.tls.client.enableSessionTicketExtension=false PacketLossRetransmission client 4 new_session_ticket
  * @run main/othervm PacketLossRetransmission client 11 certificate
  * @run main/othervm PacketLossRetransmission client 12 server_key_exchange
  * @run main/othervm PacketLossRetransmission client 13 certificate_request
@@ -51,7 +51,7 @@
  * @run main/othervm PacketLossRetransmission server 1 client_hello
  * @run main/othervm PacketLossRetransmission server 2 server_hello
  * @run main/othervm PacketLossRetransmission server 3 hello_verify_request
- * @run main/othervm PacketLossRetransmission server 4 new_session_ticket
+ * @run main/othervm -Djdk.tls.client.enableSessionTicketExtension=false PacketLossRetransmission server 4 new_session_ticket
  * @run main/othervm PacketLossRetransmission server 11 certificate
  * @run main/othervm PacketLossRetransmission server 12 server_key_exchange
  * @run main/othervm PacketLossRetransmission server 13 certificate_request
--- a/test/jdk/javax/net/ssl/SSLSession/SSLCtxAccessToSessCtx.java	Tue Jun 11 19:15:31 2019 -0400
+++ b/test/jdk/javax/net/ssl/SSLSession/SSLCtxAccessToSessCtx.java	Tue Jun 11 16:31:37 2019 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2001, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2001, 2019, 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
@@ -25,7 +25,9 @@
  * @test
  * @bug 4473210
  * @summary SSLSessionContext should be accessible from SSLContext
- * @run main/othervm SSLCtxAccessToSessCtx
+ * @run main/othervm -Djdk.tls.server.enableSessionTicketExtension=false
+ *   SSLCtxAccessToSessCtx
+ *
  *
  *     SunJSSE does not support dynamic system properties, no way to re-use
  *     system properties in samevm/agentvm mode.
--- a/test/jdk/sun/security/ssl/DHKeyExchange/DHEKeySizing.java	Tue Jun 11 19:15:31 2019 -0400
+++ b/test/jdk/sun/security/ssl/DHKeyExchange/DHEKeySizing.java	Tue Jun 11 16:31:37 2019 -0700
@@ -30,44 +30,57 @@
  * @test
  * @bug 6956398
  * @summary make ephemeral DH key match the length of the certificate key
- * @run main/othervm
+ * @run main/othervm -Djdk.tls.client.enableSessionTicketExtension=false
  *      DHEKeySizing TLS_DHE_RSA_WITH_AES_128_CBC_SHA  false 1643 267
  * @run main/othervm -Djsse.enableFFDHE=false
+ *      -Djdk.tls.client.enableSessionTicketExtension=false
  *      DHEKeySizing SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA true 1259 75
  * @run main/othervm -Djsse.enableFFDHE=false
  *      -Djdk.tls.ephemeralDHKeySize=matched
+ *      -Djdk.tls.client.enableSessionTicketExtension=false
  *      DHEKeySizing SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA true 1259 75
  * @run main/othervm -Djsse.enableFFDHE=false
  *      -Djdk.tls.ephemeralDHKeySize=legacy
+ *      -Djdk.tls.client.enableSessionTicketExtension=false
  *      DHEKeySizing SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA true 1259 75
  * @run main/othervm -Djsse.enableFFDHE=false
  *      -Djdk.tls.ephemeralDHKeySize=1024
+ *      -Djdk.tls.client.enableSessionTicketExtension=false
  *      DHEKeySizing SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA true 1259 75
  *
  * @run main/othervm -Djsse.enableFFDHE=false
+ *      -Djdk.tls.client.enableSessionTicketExtension=false
  *      DHEKeySizing SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA true 233 75
  *
  * @run main/othervm -Djsse.enableFFDHE=false
+ *      -Djdk.tls.client.enableSessionTicketExtension=false
  *      DHEKeySizing TLS_DHE_RSA_WITH_AES_128_CBC_SHA  false 1387 139
  * @run main/othervm -Djsse.enableFFDHE=false
  *      -Djdk.tls.ephemeralDHKeySize=legacy
+ *      -Djdk.tls.client.enableSessionTicketExtension=false
  *      DHEKeySizing TLS_DHE_RSA_WITH_AES_128_CBC_SHA  false 1323 107
  * @run main/othervm -Djsse.enableFFDHE=false
  *      -Djdk.tls.ephemeralDHKeySize=matched
+ *      -Djdk.tls.client.enableSessionTicketExtension=false
  *      DHEKeySizing TLS_DHE_RSA_WITH_AES_128_CBC_SHA  false 1643 267
  * @run main/othervm -Djsse.enableFFDHE=false
  *      -Djdk.tls.ephemeralDHKeySize=1024
+ *      -Djdk.tls.client.enableSessionTicketExtension=false
  *      DHEKeySizing TLS_DHE_RSA_WITH_AES_128_CBC_SHA  false 1387 139
  *
  * @run main/othervm -Djsse.enableFFDHE=false
+ *      -Djdk.tls.client.enableSessionTicketExtension=false
  *      DHEKeySizing SSL_DH_anon_WITH_RC4_128_MD5  false 361 139
  * @run main/othervm -Djsse.enableFFDHE=false
+ *      -Djdk.tls.client.enableSessionTicketExtension=false
  *      -Djdk.tls.ephemeralDHKeySize=legacy
  *      DHEKeySizing SSL_DH_anon_WITH_RC4_128_MD5  false 297 107
  * @run main/othervm -Djsse.enableFFDHE=false
+ *      -Djdk.tls.client.enableSessionTicketExtension=false
  *      -Djdk.tls.ephemeralDHKeySize=matched
  *      DHEKeySizing SSL_DH_anon_WITH_RC4_128_MD5  false 361 139
  * @run main/othervm -Djsse.enableFFDHE=false
+ *      -Djdk.tls.client.enableSessionTicketExtension=false
  *      -Djdk.tls.ephemeralDHKeySize=1024
  *      DHEKeySizing SSL_DH_anon_WITH_RC4_128_MD5  false 361 139
  */
--- a/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksClient.java	Tue Jun 11 19:15:31 2019 -0400
+++ b/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksClient.java	Tue Jun 11 16:31:37 2019 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, 2019 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
@@ -26,13 +26,17 @@
  * @bug 8206929 8212885
  * @summary ensure that client only resumes a session if certain properties
  *    of the session are compatible with the new connection
- * @run main/othervm -Djdk.tls.client.protocols=TLSv1.2 ResumeChecksClient BASIC
- * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 ResumeChecksClient BASIC
- * @run main/othervm ResumeChecksClient BASIC
- * @run main/othervm ResumeChecksClient VERSION_2_TO_3
- * @run main/othervm ResumeChecksClient VERSION_3_TO_2
- * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 ResumeChecksClient CIPHER_SUITE
- * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 ResumeChecksClient SIGNATURE_SCHEME
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.2 -Djdk.tls.server.enableSessionTicketExtension=false -Djdk.tls.client.enableSessionTicketExtension=false ResumeChecksClient BASIC
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.2 -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=false ResumeChecksClient BASIC
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.2 -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=true ResumeChecksClient BASIC
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=true ResumeChecksClient BASIC
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.2 -Djdk.tls.server.enableSessionTicketExtension=false -Djdk.tls.client.enableSessionTicketExtension=true ResumeChecksClient BASIC
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.server.enableSessionTicketExtension=false -Djdk.tls.client.enableSessionTicketExtension=true ResumeChecksClient BASIC
+ * @run main/othervm -Djdk.tls.server.enableSessionTicketExtension=false -Djdk.tls.client.enableSessionTicketExtension=true ResumeChecksClient BASIC
+ * @run main/othervm -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=true ResumeChecksClient VERSION_2_TO_3
+ * @run main/othervm -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=true ResumeChecksClient VERSION_3_TO_2
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=true ResumeChecksClient CIPHER_SUITE
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=true ResumeChecksClient SIGNATURE_SCHEME
  *
  */
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksClientStateless.java	Tue Jun 11 16:31:37 2019 -0700
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2019, 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 8211018
+ * @summary ensure that client only resumes a session if certain properties
+ *    of the session are compatible with the new connection
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.2 -Djdk.tls.server.enableSessionTicketExtension=false -Djdk.tls.client.enableSessionTicketExtension=false ResumeChecksClient BASIC
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.2 -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=false ResumeChecksClient BASIC
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.2 -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=true ResumeChecksClient BASIC
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=true ResumeChecksClient BASIC
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.2 -Djdk.tls.server.enableSessionTicketExtension=false -Djdk.tls.client.enableSessionTicketExtension=true ResumeChecksClient BASIC
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.server.enableSessionTicketExtension=false -Djdk.tls.client.enableSessionTicketExtension=true ResumeChecksClient BASIC
+ * @run main/othervm -Djdk.tls.server.enableSessionTicketExtension=false -Djdk.tls.client.enableSessionTicketExtension=true ResumeChecksClient BASIC
+ * @run main/othervm -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=true ResumeChecksClient VERSION_2_TO_3
+ * @run main/othervm -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=true ResumeChecksClient VERSION_3_TO_2
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=true ResumeChecksClient CIPHER_SUITE
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=true ResumeChecksClient SIGNATURE_SCHEME
+ */
--- a/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksServer.java	Tue Jun 11 19:15:31 2019 -0400
+++ b/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksServer.java	Tue Jun 11 16:31:37 2019 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, 2019, 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
@@ -26,16 +26,18 @@
  * @bug 8206929
  * @summary ensure that server only resumes a session if certain properties
  *    of the session are compatible with the new connection
- * @run main/othervm -Djdk.tls.client.protocols=TLSv1.2 ResumeChecksServer BASIC
- * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 ResumeChecksServer BASIC
- * @run main/othervm ResumeChecksServer BASIC
- * @run main/othervm -Djdk.tls.client.protocols=TLSv1.2 ResumeChecksServer CLIENT_AUTH
- * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 ResumeChecksServer CLIENT_AUTH
- * @run main/othervm ResumeChecksServer CLIENT_AUTH
- * @run main/othervm ResumeChecksServer VERSION_2_TO_3
- * @run main/othervm ResumeChecksServer VERSION_3_TO_2
- * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 ResumeChecksServer CIPHER_SUITE
- * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 ResumeChecksServer SIGNATURE_SCHEME
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.2 -Djdk.tls.server.enableSessionTicketExtension=false -Djdk.tls.client.enableSessionTicketExtension=false ResumeChecksServer BASIC
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.2 -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=false ResumeChecksServer BASIC
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.2 -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=true ResumeChecksServer BASIC
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.2 -Djdk.tls.server.enableSessionTicketExtension=false -Djdk.tls.client.enableSessionTicketExtension=false ResumeChecksServer CLIENT_AUTH
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.2 -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=false ResumeChecksServer CLIENT_AUTH
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.2 -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=true ResumeChecksServer CLIENT_AUTH
+ * @run main/othervm -Djdk.tls.server.enableSessionTicketExtension=false -Djdk.tls.client.enableSessionTicketExtension=false ResumeChecksServer VERSION_2_TO_3
+ * @run main/othervm -Djdk.tls.server.enableSessionTicketExtension=false -Djdk.tls.client.enableSessionTicketExtension=true ResumeChecksServer VERSION_2_TO_3
+ * @run main/othervm -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=false ResumeChecksServer VERSION_2_TO_3
+ * @run main/othervm -Djdk.tls.server.enableSessionTicketExtension=false -Djdk.tls.client.enableSessionTicketExtension=false ResumeChecksServer VERSION_3_TO_2
+ * @run main/othervm -Djdk.tls.server.enableSessionTicketExtension=false -Djdk.tls.client.enableSessionTicketExtension=true ResumeChecksServer VERSION_3_TO_2
+ * @run main/othervm -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=false ResumeChecksServer VERSION_3_TO_2
  *
  */
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksServerStateless.java	Tue Jun 11 16:31:37 2019 -0700
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2019, 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 8211018
+ * @summary ensure that server only resumes a session if certain properties
+ *    of the session are compatible with the new connection
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 ResumeChecksServer BASIC
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 ResumeChecksServer CLIENT_AUTH
+ * @run main/othervm ResumeChecksServer VERSION_2_TO_3
+ * @run main/othervm ResumeChecksServer VERSION_3_TO_2
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 ResumeChecksServer CIPHER_SUITE
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 ResumeChecksServer SIGNATURE_SCHEME
+ */