8226338: Updates to Stateless Resumption
authorascarpino
Wed, 17 Jul 2019 14:37:50 -0700
changeset 57485 af4b0fc25bc4
parent 57484 e8b8460b191c
child 57486 347804d623fc
8226338: Updates to Stateless Resumption Reviewed-by: xuelei, jnimeh
src/java.base/share/classes/javax/net/ssl/SSLSessionContext.java
src/java.base/share/classes/sun/security/ssl/Finished.java
src/java.base/share/classes/sun/security/ssl/NewSessionTicket.java
src/java.base/share/classes/sun/security/ssl/PostHandshakeContext.java
src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java
src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java
src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java
src/java.base/share/classes/sun/security/ssl/ServerHello.java
src/java.base/share/classes/sun/security/ssl/SessionTicketExtension.java
test/jdk/ProblemList.txt
test/jdk/sun/security/ssl/SSLSessionImpl/ResumptionUpdateBoundValues.java
--- a/src/java.base/share/classes/javax/net/ssl/SSLSessionContext.java	Wed Jul 17 13:41:12 2019 -0700
+++ b/src/java.base/share/classes/javax/net/ssl/SSLSessionContext.java	Wed Jul 17 14:37:50 2019 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 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,11 @@
  * it could be associated with a server or client who participates in many
  * sessions concurrently.
  * <p>
- * Not all environments will contain session contexts.
+ * Not all environments will contain session contexts.  For example, stateless
+ * session resumption.
+ * <p>
+ * Session contexts may not contain all sessions. For example, stateless
+ * sessions are not stored in the session context.
  * <p>
  * There are <code>SSLSessionContext</code> parameters that affect how
  * sessions are stored:
@@ -68,8 +72,11 @@
     public SSLSession getSession(byte[] sessionId);
 
     /**
-     * Returns an Enumeration of all session id's grouped under this
+     * Returns an Enumeration of all known session id's grouped under this
      * <code>SSLSessionContext</code>.
+     * <p>Session contexts may not contain all sessions. For example,
+     * stateless sessions are not stored in the session context.
+     * <p>
      *
      * @return an enumeration of all the Session id's
      */
--- a/src/java.base/share/classes/sun/security/ssl/Finished.java	Wed Jul 17 13:41:12 2019 -0700
+++ b/src/java.base/share/classes/sun/security/ssl/Finished.java	Wed Jul 17 14:37:50 2019 -0700
@@ -482,7 +482,7 @@
                 shc.conContext.inputRecord.expectingFinishFlight();
             } else {
                 if (shc.handshakeSession.isRejoinable() &&
-                        !shc.statelessResumption) {
+                        !shc.handshakeSession.isStatelessable(shc)) {
                     ((SSLSessionContextImpl)shc.sslContext.
                         engineGetServerSessionContext()).put(
                             shc.handshakeSession);
@@ -847,6 +847,8 @@
                 shc.conContext.serverVerifyData = fm.verifyData;
             }
 
+            shc.conContext.conSession = shc.handshakeSession.finish();
+
             // update the context
             shc.handshakeConsumers.put(
                     SSLHandshake.FINISHED.id, SSLHandshake.FINISHED);
--- a/src/java.base/share/classes/sun/security/ssl/NewSessionTicket.java	Wed Jul 17 13:41:12 2019 -0700
+++ b/src/java.base/share/classes/sun/security/ssl/NewSessionTicket.java	Wed Jul 17 14:37:50 2019 -0700
@@ -45,7 +45,6 @@
  */
 final class NewSessionTicket {
     static final int MAX_TICKET_LIFETIME = 604800;  // seconds, 7 days
-
     static final SSLConsumer handshakeConsumer =
         new T13NewSessionTicketConsumer();
     static final SSLConsumer handshake12Consumer =
@@ -60,7 +59,7 @@
      */
     abstract static class NewSessionTicketMessage extends HandshakeMessage {
         int ticketLifetime;
-        byte[] ticket;
+        byte[] ticket = new byte[0];
 
         NewSessionTicketMessage(HandshakeContext context) {
             super(context);
@@ -83,6 +82,9 @@
                     "TicketNonce not part of RFC 5077.");
         }
 
+        boolean isValid() {
+            return (ticket.length > 0);
+        }
     }
     /**
      * NewSessionTicket for TLS 1.2 and below (RFC 5077)
@@ -102,13 +104,13 @@
 
             // RFC5077 struct {
             //     uint32 ticket_lifetime;
-            //     opaque ticket<1..2^16-1>;
+            //     opaque ticket<0..2^16-1>;
             // } NewSessionTicket;
 
             super(context);
-            if (m.remaining() < 14) {
+            if (m.remaining() < 6) {
                 throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
-                        "Invalid NewSessionTicket message: no sufficient data");
+                    "Invalid NewSessionTicket message: insufficient data");
             }
 
             this.ticketLifetime = Record.getInt32(m);
@@ -186,7 +188,7 @@
 
             if (m.remaining() < 14) {
                 throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
-                    "Invalid NewSessionTicket message: no sufficient data");
+                    "Invalid NewSessionTicket message: insufficient data");
             }
 
             this.ticketLifetime = Record.getInt32(m);
@@ -195,18 +197,21 @@
 
             if (m.remaining() < 5) {
                 throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
-                    "Invalid NewSessionTicket message: no sufficient data");
+                    "Invalid NewSessionTicket message: insufficient ticket" +
+                            " data");
             }
 
             this.ticket = Record.getBytes16(m);
             if (ticket.length == 0) {
-                throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.fine(
                     "No ticket in the NewSessionTicket handshake message");
+                }
             }
 
             if (m.remaining() < 2) {
                 throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
-                    "Invalid NewSessionTicket message: no sufficient data");
+                    "Invalid NewSessionTicket message: extra data");
             }
 
             SSLExtension[] supportedExtensions =
@@ -310,36 +315,45 @@
 
         @Override
         public byte[] produce(ConnectionContext context) throws IOException {
-            // The producing happens in server side only.
-            ServerHandshakeContext shc = (ServerHandshakeContext)context;
+            HandshakeContext hc = (HandshakeContext)context;
 
-            // Is this session resumable?
-            if (!shc.handshakeSession.isRejoinable()) {
-                return null;
-            }
+            // The producing happens in server side only.
+            if (hc instanceof ServerHandshakeContext) {
+                // Is this session resumable?
+                if (!hc.handshakeSession.isRejoinable()) {
+                    return null;
+                }
 
-            // What's the requested PSK key exchange modes?
-            //
-            // Note that currently, the NewSessionTicket post-handshake is
-            // produced and delivered only in the current handshake context
-            // if required.
-            PskKeyExchangeModesSpec pkemSpec =
-                    (PskKeyExchangeModesSpec)shc.handshakeExtensions.get(
-                            SSLExtension.PSK_KEY_EXCHANGE_MODES);
-            if (pkemSpec == null || !pkemSpec.contains(
-                PskKeyExchangeModesExtension.PskKeyExchangeMode.PSK_DHE_KE)) {
-                // Client doesn't support PSK with (EC)DHE key establishment.
-                return null;
+                // What's the requested PSK key exchange modes?
+                //
+                // Note that currently, the NewSessionTicket post-handshake is
+                // produced and delivered only in the current handshake context
+                // if required.
+                PskKeyExchangeModesSpec pkemSpec =
+                        (PskKeyExchangeModesSpec) hc.handshakeExtensions.get(
+                                SSLExtension.PSK_KEY_EXCHANGE_MODES);
+                if (pkemSpec == null || !pkemSpec.contains(
+                        PskKeyExchangeModesExtension.PskKeyExchangeMode.PSK_DHE_KE)) {
+                    // Client doesn't support PSK with (EC)DHE key establishment.
+                    return null;
+                }
+            } else { // PostHandshakeContext
+
+                // Check if we have sent a PSK already, then we know it is using a
+                // allowable PSK exchange key mode
+                if (!hc.handshakeSession.isPSKable()) {
+                    return null;
+                }
             }
 
             // get a new session ID
             SSLSessionContextImpl sessionCache = (SSLSessionContextImpl)
-                shc.sslContext.engineGetServerSessionContext();
+                hc.sslContext.engineGetServerSessionContext();
             SessionId newId = new SessionId(true,
-                shc.sslContext.getSecureRandom());
+                hc.sslContext.getSecureRandom());
 
             SecretKey resumptionMasterSecret =
-                shc.handshakeSession.getResumptionMasterSecret();
+                hc.handshakeSession.getResumptionMasterSecret();
             if (resumptionMasterSecret == null) {
                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
                     SSLLogger.fine(
@@ -349,10 +363,10 @@
             }
 
             // construct the PSK and handshake message
-            BigInteger nonce = shc.handshakeSession.incrTicketNonceCounter();
+            BigInteger nonce = hc.handshakeSession.incrTicketNonceCounter();
             byte[] nonceArr = nonce.toByteArray();
             SecretKey psk = derivePreSharedKey(
-                    shc.negotiatedCipherSuite.hashAlg,
+                    hc.negotiatedCipherSuite.hashAlg,
                     resumptionMasterSecret, nonceArr);
 
             int sessionTimeoutSeconds = sessionCache.getSessionTimeout();
@@ -364,31 +378,35 @@
                 return null;
             }
 
-            NewSessionTicketMessage nstm;
+            NewSessionTicketMessage nstm = null;
 
             SSLSessionImpl sessionCopy =
-                    new SSLSessionImpl(shc.handshakeSession, newId);
+                    new SSLSessionImpl(hc.handshakeSession, newId);
             sessionCopy.setPreSharedKey(psk);
             sessionCopy.setPskIdentity(newId.getId());
 
-            if (shc.statelessResumption) {
-                try {
-                    nstm = new T13NewSessionTicketMessage(shc,
-                            sessionTimeoutSeconds, shc.sslContext.getSecureRandom(),
-                            nonceArr, new SessionTicketSpec().encrypt(shc, sessionCopy));
+            // If a stateless ticket is allowed, attempt to make one
+            if (hc.handshakeSession.isStatelessable(hc)) {
+                nstm = new T13NewSessionTicketMessage(hc,
+                        sessionTimeoutSeconds,
+                        hc.sslContext.getSecureRandom(),
+                        nonceArr,
+                        new SessionTicketSpec().encrypt(hc, sessionCopy));
+                // If ticket construction failed, switch to session cache
+                if (!nstm.isValid()) {
+                    hc.statelessResumption = false;
+                } else {
                     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,
+            }
+            // If a session cache ticket is being used, make one
+            if (!hc.handshakeSession.isStatelessable(hc)) {
+                nstm = new T13NewSessionTicketMessage(hc, sessionTimeoutSeconds,
+                        hc.sslContext.getSecureRandom(), nonceArr,
                         newId.getId());
                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
                     SSLLogger.fine(
@@ -399,13 +417,21 @@
                 // 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);
+                hc.handshakeSession.addChild(sessionCopy);
                 sessionCopy.setTicketAgeAdd(nstm.getTicketAgeAdd());
                 sessionCache.put(sessionCopy);
             }
+
             // Output the handshake message.
-            nstm.write(shc.handshakeOutput);
-            shc.handshakeOutput.flush();
+            if (nstm != null) {
+                // should never be null
+                nstm.write(hc.handshakeOutput);
+                hc.handshakeOutput.flush();
+            }
+
+            if (hc instanceof PostHandshakeContext) {
+                ((PostHandshakeContext) hc).finish();
+            }
 
             // The message has been delivered.
             return null;
@@ -448,23 +474,16 @@
                 return null;
             }
 
-            NewSessionTicketMessage nstm;
-
             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;
+            NewSessionTicketMessage 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);
             }
 
             // Output the handshake message.
@@ -505,6 +524,9 @@
                 "Consuming NewSessionTicket message", nstm);
             }
 
+            SSLSessionContextImpl sessionCache = (SSLSessionContextImpl)
+                    hc.sslContext.engineGetClientSessionContext();
+
             // discard tickets with timeout 0
             if (nstm.ticketLifetime <= 0 ||
                 nstm.ticketLifetime > MAX_TICKET_LIFETIME) {
@@ -513,12 +535,10 @@
                     "Discarding NewSessionTicket with lifetime "
                         + nstm.ticketLifetime, nstm);
                 }
+                sessionCache.remove(hc.handshakeSession.getSessionId());
                 return;
             }
 
-            SSLSessionContextImpl sessionCache = (SSLSessionContextImpl)
-                hc.sslContext.engineGetClientSessionContext();
-
             if (sessionCache.getSessionTimeout() > MAX_TICKET_LIFETIME) {
                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
                     SSLLogger.fine(
--- a/src/java.base/share/classes/sun/security/ssl/PostHandshakeContext.java	Wed Jul 17 13:41:12 2019 -0700
+++ b/src/java.base/share/classes/sun/security/ssl/PostHandshakeContext.java	Wed Jul 17 14:37:50 2019 -0700
@@ -54,6 +54,7 @@
 
         handshakeConsumers = new LinkedHashMap<>(consumers);
         handshakeFinished = true;
+        handshakeSession = context.conSession;
     }
 
     @Override
@@ -82,4 +83,9 @@
                     SSLHandshake.nameOf(handshakeType), be);
         }
     }
+
+    // Finish this PostHandshake event
+    void finish() {
+        handshakeSession = null;
+    }
 }
--- a/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java	Wed Jul 17 13:41:12 2019 -0700
+++ b/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java	Wed Jul 17 14:37:50 2019 -0700
@@ -344,6 +344,12 @@
             hsStatus = tryKeyUpdate(hsStatus);
         }
 
+        // Check if NewSessionTicket PostHandshake message needs to be sent
+        if (conContext.conSession.updateNST &&
+                !conContext.sslConfig.isClientMode) {
+            hsStatus = tryNewSessionTicket(hsStatus);
+        }
+
         // update context status
         ciphertext.handshakeStatus = hsStatus;
 
@@ -397,6 +403,29 @@
         return currentHandshakeStatus;
     }
 
+    // Try to generate a PostHandshake NewSessionTicket message.  This is
+    // TLS 1.3 only.
+    private HandshakeStatus tryNewSessionTicket(
+            HandshakeStatus currentHandshakeStatus) throws IOException {
+        // Don't bother to kickstart if handshaking is in progress, or if the
+        // connection is not duplex-open.
+        if ((conContext.handshakeContext == null) &&
+                conContext.protocolVersion.useTLS13PlusSpec() &&
+                !conContext.isOutboundClosed() &&
+                !conContext.isInboundClosed() &&
+                !conContext.isBroken) {
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                SSLLogger.finest("trigger NST");
+            }
+            conContext.conSession.updateNST = false;
+            NewSessionTicket.kickstartProducer.produce(
+                    new PostHandshakeContext(conContext));
+            return conContext.getHandshakeStatus();
+        }
+
+        return currentHandshakeStatus;
+    }
+
     private static void checkParams(
             ByteBuffer[] srcs, int srcsOffset, int srcsLength,
             ByteBuffer[] dsts, int dstsOffset, int dstsLength) {
--- a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java	Wed Jul 17 13:41:12 2019 -0700
+++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java	Wed Jul 17 14:37:50 2019 -0700
@@ -27,6 +27,7 @@
 import sun.security.x509.X509CertImpl;
 
 import java.io.IOException;
+import java.lang.reflect.Array;
 import java.math.BigInteger;
 import java.net.InetAddress;
 import java.nio.ByteBuffer;
@@ -35,6 +36,7 @@
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Queue;
 import java.util.Collection;
 import java.util.Collections;
@@ -104,7 +106,7 @@
     private X509Certificate[]   localCerts;
     private PrivateKey          localPrivateKey;
     private final Collection<SignatureScheme>     localSupportedSignAlgs;
-    private String[]            peerSupportedSignAlgs;      // for certificate
+    private Collection<SignatureScheme> peerSupportedSignAlgs; //for certificate
     private boolean             useDefaultPeerSignAlgs = false;
     private List<byte[]>        statusResponses;
     private SecretKey           resumptionMasterSecret;
@@ -236,7 +238,8 @@
                 baseSession.localSupportedSignAlgs == null ?
                 Collections.emptySet() : baseSession.localSupportedSignAlgs;
         this.peerSupportedSignAlgs =
-                baseSession.getPeerSupportedSignatureAlgorithms();
+                baseSession.peerSupportedSignAlgs == null ?
+                Collections.emptySet() : baseSession.peerSupportedSignAlgs;
         this.serverNameIndication = baseSession.serverNameIndication;
         this.requestedServerNames = baseSession.getRequestedServerNames();
         this.masterSecret = baseSession.getMasterSecret();
@@ -261,8 +264,10 @@
     /**
      * < 2 bytes > protocolVersion
      * < 2 bytes > cipherSuite
-     * < 2 bytes > localSupportedSignAlgs entries
+     * < 1 byte > localSupportedSignAlgs entries
      *   < 2 bytes per entries > localSupportedSignAlgs
+     * < 1 bytes > peerSupportedSignAlgs entries
+     *   < 2 bytes per entries > peerSupportedSignAlgs
      * < 2 bytes > preSharedKey length
      * < length in bytes > preSharedKey
      * < 1 byte > pskIdentity length
@@ -281,6 +286,9 @@
      *   < 1 byte > ServerName length
      *   < length in bytes > ServerName
      * < 4 bytes > creationTime
+     * < 2 byte > status response length
+     *   < 2 byte > status response entry length
+     *   < length in byte > status response entry
      * < 1 byte > Length of peer host
      *   < length in bytes > peer host
      * < 2 bytes> peer port
@@ -302,17 +310,17 @@
      *       < length in bytes> PSK identity
      *   Anonymous
      *     < 1 byte >
+     * < 4 bytes > maximumPacketSize
+     * < 4 bytes > negotiatedMaxFragSize
     */
 
     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()));
+        boundValues = new ConcurrentHashMap<>();
+        this.protocolVersion =
+                ProtocolVersion.valueOf(Short.toUnsignedInt(buf.getShort()));
 
         if (protocolVersion.useTLS13PlusSpec()) {
             this.sessionId = new SessionId(false, null);
@@ -322,14 +330,26 @@
                     hc.sslContext.getSecureRandom());
         }
 
-        this.cipherSuite = CipherSuite.valueOf(Short.toUnsignedInt(buf.getShort()));
+        this.cipherSuite =
+                CipherSuite.valueOf(Short.toUnsignedInt(buf.getShort()));
 
         // Local Supported signature algorithms
-        i = Short.toUnsignedInt(buf.getShort());
+        ArrayList<SignatureScheme> list = new ArrayList<>();
+        i = Byte.toUnsignedInt(buf.get());
         while (i-- > 0) {
-            this.localSupportedSignAlgs.add(SignatureScheme.valueOf(
+            list.add(SignatureScheme.valueOf(
                     Short.toUnsignedInt(buf.getShort())));
         }
+        this.localSupportedSignAlgs = Collections.unmodifiableCollection(list);
+
+        // Peer Supported signature algorithms
+        i = Byte.toUnsignedInt(buf.get());
+        list.clear();
+        while (i-- > 0) {
+            list.add(SignatureScheme.valueOf(
+                    Short.toUnsignedInt(buf.getShort())));
+        }
+        this.peerSupportedSignAlgs = Collections.unmodifiableCollection(list);
 
         // PSK
         i = Short.toUnsignedInt(buf.getShort());
@@ -410,9 +430,27 @@
             }
         }
 
+        maximumPacketSize = buf.getInt();
+        negotiatedMaxFragLen = buf.getInt();
+
         // Get creation time
         this.creationTime = buf.getLong();
 
+        // Get Buffer sizes
+
+        // Status Response
+        len = Short.toUnsignedInt(buf.getShort());
+        if (len == 0) {
+            statusResponses = Collections.emptyList();
+        } else {
+            statusResponses = new ArrayList<>();
+        }
+        while (len-- > 0) {
+            b = new byte[Short.toUnsignedInt(buf.getShort())];
+            buf.get(b);
+            statusResponses.add(b);
+        }
+
         // Get Peer host & port
         i = Byte.toUnsignedInt(buf.get());
         if (i == 0) {
@@ -484,6 +522,33 @@
 
         context = (SSLSessionContextImpl)
                 hc.sslContext.engineGetServerSessionContext();
+        this.lastUsedTime = System.currentTimeMillis();
+    }
+
+    // Some situations we cannot provide a stateless ticket, but after it
+    // has been negotiated
+    boolean isStatelessable(HandshakeContext hc) {
+        if (!hc.statelessResumption) {
+            return false;
+        }
+
+        // If there is no getMasterSecret with TLS1.2 or under, do not resume.
+        if (!protocolVersion.useTLS13PlusSpec() &&
+                getMasterSecret().getEncoded() == null) {
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                SSLLogger.finest("No MasterSecret, cannot make stateless" +
+                        " ticket");
+            }
+            return false;
+        }
+        if (boundValues != null && boundValues.size() > 0) {
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                SSLLogger.finest("There are boundValues, cannot make" +
+                        " stateless ticket");
+            }
+            return false;
+        }
+        return true;
     }
 
     /**
@@ -497,11 +562,14 @@
         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.putInt8(localSupportedSignAlgs.size());
+        for (SignatureScheme s : localSupportedSignAlgs) {
+            hos.putInt16(s.id);
+        }
+
+        // Peer Supported signature algorithms
+        hos.putInt8(peerSupportedSignAlgs.size());
+        for (SignatureScheme s : peerSupportedSignAlgs) {
             hos.putInt16(s.id);
         }
 
@@ -564,16 +632,30 @@
         // List of SNIServerName
         hos.putInt16(requestedServerNames.size());
         if (requestedServerNames.size() > 0) {
-            for (SNIServerName host: requestedServerNames) {
+            for (SNIServerName host : requestedServerNames) {
                 b = host.getEncoded();
                 hos.putInt8(b.length);
                 hos.write(b, 0, b.length);
             }
         }
 
+        // Buffer sizes
+        hos.putInt32(maximumPacketSize);
+        hos.putInt32(negotiatedMaxFragLen);
+
+        // creation time
         ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
         hos.writeBytes(buffer.putLong(creationTime).array());
 
+        // Status Responses
+        List<byte[]> list = getStatusResponses();
+        int l = list.size();
+        hos.putInt16(l);
+        for (byte[] e : list) {
+            hos.putInt16(e.length);
+            hos.write(e);
+        }
+
         // peer Host & Port
         if (host == null || host.length() == 0) {
             hos.putInt8(0);
@@ -649,10 +731,14 @@
 
     BigInteger incrTicketNonceCounter() {
         BigInteger result = ticketNonceCounter;
-        ticketNonceCounter = ticketNonceCounter.add(BigInteger.valueOf(1));
+        ticketNonceCounter = ticketNonceCounter.add(BigInteger.ONE);
         return result;
     }
 
+    boolean isPSKable() {
+        return (ticketNonceCounter.compareTo(BigInteger.ZERO) > 0);
+    }
+
     /**
      * Returns the master secret ... treat with extreme caution!
      */
@@ -725,8 +811,7 @@
 
     void setPeerSupportedSignatureAlgorithms(
             Collection<SignatureScheme> signatureSchemes) {
-        peerSupportedSignAlgs =
-            SignatureScheme.getAlgorithmNames(signatureSchemes);
+        peerSupportedSignAlgs = signatureSchemes;
     }
 
     // TLS 1.2 only
@@ -740,16 +825,20 @@
     // certificates and server key exchange), it MUST send the
     // signature_algorithms extension, listing the algorithms it
     // is willing to accept.
+    private static final ArrayList<SignatureScheme> defaultPeerSupportedSignAlgs =
+            new ArrayList<>(Arrays.asList(SignatureScheme.RSA_PKCS1_SHA1,
+                    SignatureScheme.DSA_SHA1,
+                    SignatureScheme.ECDSA_SHA1));
+
     void setUseDefaultPeerSignAlgs() {
         useDefaultPeerSignAlgs = true;
-        peerSupportedSignAlgs = new String[] {
-            "SHA1withRSA", "SHA1withDSA", "SHA1withECDSA"};
+        peerSupportedSignAlgs = defaultPeerSupportedSignAlgs;
     }
 
     // Returns the connection session.
     SSLSessionImpl finish() {
         if (useDefaultPeerSignAlgs) {
-            this.peerSupportedSignAlgs = new String[0];
+            peerSupportedSignAlgs = Collections.emptySet();
         }
 
         return this;
@@ -1212,6 +1301,7 @@
      * sessions can be shared across different protection domains.
      */
     private final ConcurrentHashMap<SecureKey, Object> boundValues;
+    boolean updateNST;
 
     /**
      * Assigns a session value.  Session change events are given if
@@ -1238,6 +1328,9 @@
             e = new SSLSessionBindingEvent(this, key);
             ((SSLSessionBindingListener)value).valueBound(e);
         }
+        if (protocolVersion.useTLS13PlusSpec()) {
+            updateNST = true;
+        }
     }
 
     /**
@@ -1273,6 +1366,9 @@
             e = new SSLSessionBindingEvent(this, key);
             ((SSLSessionBindingListener)value).valueUnbound(e);
         }
+        if (protocolVersion.useTLS13PlusSpec()) {
+            updateNST = true;
+        }
     }
 
 
@@ -1474,11 +1570,7 @@
      */
     @Override
     public String[] getPeerSupportedSignatureAlgorithms() {
-        if (peerSupportedSignAlgs != null) {
-            return peerSupportedSignAlgs.clone();
-        }
-
-        return new String[0];
+        return SignatureScheme.getAlgorithmNames(peerSupportedSignAlgs);
     }
 
     /**
--- a/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java	Wed Jul 17 13:41:12 2019 -0700
+++ b/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java	Wed Jul 17 14:37:50 2019 -0700
@@ -1264,6 +1264,11 @@
                     conContext.outputRecord.writeCipher.atKeyLimit()) {
                 tryKeyUpdate();
             }
+            // Check if NewSessionTicket PostHandshake message needs to be sent
+            if (conContext.conSession.updateNST) {
+                conContext.conSession.updateNST = false;
+                tryNewSessionTicket();
+            }
         }
 
         @Override
@@ -1499,6 +1504,25 @@
         }
     }
 
+    // Try to generate a PostHandshake NewSessionTicket message.  This is
+    // TLS 1.3 only.
+    private void tryNewSessionTicket() throws IOException {
+        // Don't bother to kickstart if handshaking is in progress, or if the
+        // connection is not duplex-open.
+        if (!conContext.sslConfig.isClientMode &&
+                conContext.protocolVersion.useTLS13PlusSpec() &&
+                conContext.handshakeContext == null &&
+                !conContext.isOutboundClosed() &&
+                !conContext.isInboundClosed() &&
+                !conContext.isBroken) {
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                SSLLogger.finest("trigger new session ticket");
+            }
+            NewSessionTicket.kickstartProducer.produce(
+                    new PostHandshakeContext(conContext));
+        }
+    }
+
     /**
      * Initialize the handshaker and socket streams.
      *
--- a/src/java.base/share/classes/sun/security/ssl/ServerHello.java	Wed Jul 17 13:41:12 2019 -0700
+++ b/src/java.base/share/classes/sun/security/ssl/ServerHello.java	Wed Jul 17 14:37:50 2019 -0700
@@ -1155,14 +1155,14 @@
                             chc, chc.resumingSession.getMasterSecret());
                 }
 
-                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.conContext.consumers.putIfAbsent(
+                        ContentType.CHANGE_CIPHER_SPEC.id,
+                        ChangeCipherSpec.t10Consumer);
                 chc.handshakeConsumers.put(
                         SSLHandshake.FINISHED.id,
                         SSLHandshake.FINISHED);
--- a/src/java.base/share/classes/sun/security/ssl/SessionTicketExtension.java	Wed Jul 17 13:41:12 2019 -0700
+++ b/src/java.base/share/classes/sun/security/ssl/SessionTicketExtension.java	Wed Jul 17 14:37:50 2019 -0700
@@ -46,7 +46,6 @@
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
 import java.text.MessageFormat;
-import java.util.Collection;
 import java.util.Locale;
 
 /**
@@ -255,13 +254,17 @@
             data = buf;
         }
 
-        public byte[] encrypt(HandshakeContext hc, SSLSessionImpl session)
-                throws IOException {
+        public byte[] encrypt(HandshakeContext hc, SSLSessionImpl session) {
             byte[] encrypted;
-            StatelessKey key = KeyState.getCurrentKey(hc);
-            byte[] iv = new byte[16];
+
+            if (!hc.handshakeSession.isStatelessable(hc)) {
+                return new byte[0];
+            }
 
             try {
+                StatelessKey key = KeyState.getCurrentKey(hc);
+                byte[] iv = new byte[16];
+
                 SecureRandom random = hc.sslContext.getSecureRandom();
                 random.nextBytes(iv);
                 Cipher c = Cipher.getInstance("AES/GCM/NoPadding");
@@ -273,8 +276,11 @@
                         (byte)(key.num >>> 8),
                         (byte)(key.num)}
                 );
-                encrypted = c.doFinal(session.write());
-
+                byte[] data = session.write();
+                if (data.length == 0) {
+                    return data;
+                }
+                encrypted = c.doFinal(data);
                 byte[] result = new byte[encrypted.length + Integer.BYTES +
                         iv.length];
                 result[0] = (byte)(key.num >>> 24);
@@ -286,7 +292,10 @@
                         Integer.BYTES + iv.length, encrypted.length);
                 return result;
             } catch (Exception e) {
-                throw hc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, e);
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.fine("Encryption failed." + e);
+                }
+                return new byte[0];
             }
         }
 
@@ -311,11 +320,7 @@
                         (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);
--- a/test/jdk/ProblemList.txt	Wed Jul 17 13:41:12 2019 -0700
+++ b/test/jdk/ProblemList.txt	Wed Jul 17 14:37:50 2019 -0700
@@ -655,7 +655,6 @@
 sun/security/pkcs11/ec/TestKeyFactory.java                      8026976 generic-all
 sun/security/pkcs11/Secmod/AddTrustedCert.java                  8180837 generic-all
 sun/security/pkcs11/tls/TestKeyMaterial.java                    8180837 generic-all
-sun/security/pkcs11/tls/tls12/FipsModeTLS12.java                8224954,8225678 windows-all,linux-all
 sun/security/pkcs11/sslecc/ClientJSSEServerJSSE.java            8161536 generic-all
 
 sun/security/tools/keytool/ListKeychainStore.sh                 8156889 macosx-all
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/sun/security/ssl/SSLSessionImpl/ResumptionUpdateBoundValues.java	Wed Jul 17 14:37:50 2019 -0700
@@ -0,0 +1,380 @@
+/*
+ * 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
+ * @library /test/lib
+ * @summary Test that a New Session Ticket will be generated when a
+ * SSLSessionBindingListener is set (boundValues)
+ * @run main/othervm ResumptionUpdateBoundValues
+ */
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.util.concurrent.ArrayBlockingQueue;
+
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSessionBindingEvent;
+import javax.net.ssl.SSLSessionBindingListener;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.process.ProcessTools;
+import jdk.test.lib.Utils;
+
+public class ResumptionUpdateBoundValues {
+
+    static boolean separateServerThread = true;
+
+    /*
+     * Where do we find the keystores?
+     */
+    static String pathToStores = "../../../../javax/net/ssl/etc/";
+    static String keyStoreFile = "keystore";
+    static String trustStoreFile = "truststore";
+    static String passwd = "passphrase";
+
+    /*
+     * Is the server ready to serve?
+     */
+    volatile static boolean serverReady = false;
+
+    /*
+     * Turn on SSL debugging?
+     */
+    static boolean debug = false;
+
+    /*
+     * Define the server side of the test.
+     *
+     * If the server prematurely exits, serverReady will be set to true
+     * to avoid infinite hangs.
+     */
+    void doServerSide() throws Exception {
+        SSLServerSocketFactory sslssf =
+            (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
+        SSLServerSocket sslServerSocket =
+            (SSLServerSocket) sslssf.createServerSocket(serverPort);
+        serverPort = sslServerSocket.getLocalPort();
+
+        /*
+         * Signal Client, we're ready for his connect.
+         */
+        serverReady = true;
+
+        while (serverReady) {
+            SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept();
+            InputStream sslIS = sslSocket.getInputStream();
+            OutputStream sslOS = sslSocket.getOutputStream();
+
+            sslIS.read();
+            sslOS.write(85);
+            sslOS.flush();
+            SSLSession sslSession = sslSocket.getSession();
+            SBListener sbListener = new SBListener(sslSession);
+            sslSession.putValue("x", sbListener);
+
+            sslIS.read();
+            sslOS.write(85);
+            sslOS.flush();
+
+            sslSocket.close();
+        }
+    }
+
+    /*
+     * Define the client side of the test.
+     *
+     * If the server prematurely exits, serverReady will be set to true
+     * to avoid infinite hangs.
+     */
+    SBListener doClientSide() throws Exception {
+
+        /*
+         * Wait for server to get started.
+         */
+        while (!serverReady) {
+            Thread.sleep(50);
+        }
+
+        SSLSocketFactory sslsf =
+            (SSLSocketFactory) SSLSocketFactory.getDefault();
+
+        try {
+                SSLSocket sslSocket = (SSLSocket)
+                    sslsf.createSocket("localhost", serverPort);
+                InputStream sslIS = sslSocket.getInputStream();
+                OutputStream sslOS = sslSocket.getOutputStream();
+
+            sslOS.write(280);
+            sslOS.flush();
+            sslIS.read();
+
+            SSLSession sslSession = sslSocket.getSession();
+            System.out.printf(" sslSession: %s %n   %s%n", sslSession, sslSession.getClass());
+            SBListener sbListener = new SBListener(sslSession);
+
+            sslOS.write(280);
+            sslOS.flush();
+            sslIS.read();
+
+            sslOS.write(280);
+            sslOS.flush();
+            sslIS.read();
+
+            sslOS.close();
+            sslIS.close();
+            sslSocket.close();
+
+            sslOS = null;
+            sslIS = null;
+            sslSession = null;
+            sslSocket = null;
+            Reference.reachabilityFence(sslOS);
+            Reference.reachabilityFence(sslIS);
+            Reference.reachabilityFence(sslSession);
+            Reference.reachabilityFence(sslSocket);
+
+            return sbListener;
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            throw ex;
+        }
+    }
+
+    /*
+     * =============================================================
+     * The remainder is just support stuff
+     */
+
+    // use any free port by default
+    volatile int serverPort = 0;
+
+    volatile Exception serverException = null;
+    volatile Exception clientException = null;
+
+    public static void main(String[] args) throws Exception {
+
+        if (args.length == 0) {
+            System.setProperty("test.java.opts",
+                    "-Dtest.src=" + System.getProperty("test.src") +
+                            " -Dtest.jdk=" + System.getProperty("test.jdk") +
+                            " -Djavax.net.debug=ssl,handshake");
+
+            System.out.println("test.java.opts: " +
+                    System.getProperty("test.java.opts"));
+
+            ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(true,
+                    Utils.addTestJavaOpts("ResumptionUpdateBoundValues", "p"));
+
+            OutputAnalyzer output = ProcessTools.executeProcess(pb);
+            try {
+                output.shouldContain("trigger new session ticket");
+                System.out.println("Found NST in debugging");
+            } catch (Exception e) {
+                throw e;
+            } finally {
+                System.out.println("-- BEGIN Stdout:");
+                System.out.println(output.getStdout());
+                System.out.println("-- END Stdout");
+                System.out.println("-- BEGIN Stderr:");
+                System.out.println(output.getStderr());
+                System.out.println("-- END Stderr");
+            }
+            return;
+        }
+
+        String keyFilename =
+            System.getProperty("test.src", "./") + "/" + pathToStores +
+                "/" + keyStoreFile;
+        String trustFilename =
+            System.getProperty("test.src", "./") + "/" + pathToStores +
+                "/" + trustStoreFile;
+        System.setProperty("javax.net.ssl.keyStore", keyFilename);
+        System.setProperty("javax.net.ssl.keyStorePassword", passwd);
+        System.setProperty("javax.net.ssl.trustStore", trustFilename);
+        System.setProperty("javax.net.ssl.trustStorePassword", passwd);
+
+        if (debug)
+            System.setProperty("javax.net.debug", "all");
+
+        /*
+         * Start the tests.
+         */
+
+        new ResumptionUpdateBoundValues();
+    }
+
+    ArrayBlockingQueue<Thread> threads = new ArrayBlockingQueue<Thread>(100);
+
+    ArrayBlockingQueue<SBListener> sbListeners = new ArrayBlockingQueue<>(100);
+
+    /*
+     * Primary constructor, used to drive remainder of the test.
+     *
+     * Fork off the other side, then do your work.
+     */
+    ResumptionUpdateBoundValues() throws Exception {
+        final int count = 1;
+        if (separateServerThread) {
+            startServer(true);
+            startClients(true, count);
+        } else {
+            startClients(true, count);
+            startServer(true);
+        }
+
+        /*
+         * Wait for other side to close down.
+         */
+        Thread t;
+        while ((t = threads.take()) != Thread.currentThread()) {
+            System.out.printf("  joining: %s%n", t);
+            t.join(1000L);
+        }
+        serverReady = false;
+        System.gc();
+        System.gc();
+
+
+        SBListener listener = null;
+        while ((listener = sbListeners.poll()) != null) {
+            if (!listener.check()) {
+                System.out.printf("  sbListener not called on finalize: %s%n",
+                        listener);
+            }
+        }
+
+        /*
+         * When we get here, the test is pretty much over.
+         *
+         * If the main thread excepted, that propagates back
+         * immediately.  If the other thread threw an exception, we
+         * should report back.
+         */
+        if (serverException != null) {
+            System.out.print("Server Exception:");
+            throw serverException;
+        }
+        if (clientException != null) {
+            System.out.print("Client Exception:");
+            throw clientException;
+        }
+    }
+
+    void startServer(boolean newThread) throws Exception {
+        if (newThread) {
+            Thread t = new Thread("Server") {
+                public void run() {
+                    try {
+                        doServerSide();
+                    } catch (Exception e) {
+                        /*
+                         * Our server thread just died.
+                         *
+                         * Release the client, if not active already...
+                         */
+                        System.err.println("Server died..." + e);
+                        serverReady = true;
+                        serverException = e;
+                    }
+                }
+            };
+            threads.add(t);
+            t.setDaemon(true);
+            t.start();
+        } else {
+            doServerSide();
+        }
+    }
+
+    void startClients(boolean newThread, int count) throws Exception {
+        for (int i = 0; i < count; i++) {
+            System.out.printf(" newClient: %d%n", i);
+            startClient(newThread);
+        }
+        serverReady = false;
+
+        threads.add(Thread.currentThread());    // add ourselves at the 'end'
+    }
+    void startClient(boolean newThread) throws Exception {
+        if (newThread) {
+            Thread t = new Thread("Client") {
+                public void run() {
+                    try {
+                        sbListeners.add(doClientSide());
+                    } catch (Exception e) {
+                        /*
+                         * Our client thread just died.
+                         */
+                        System.err.println("Client died..." + e);
+                        clientException = e;
+                    }
+                }
+            };
+            System.out.printf(" starting: %s%n", t);
+            threads.add(t);
+            t.start();
+        } else {
+            sbListeners.add(doClientSide());
+        }
+    }
+
+
+    static class SBListener implements SSLSessionBindingListener {
+        private volatile int unboundNotified;
+        private final WeakReference<SSLSession> session;
+
+        SBListener(SSLSession session) {
+            this.unboundNotified = 0;
+            this.session = new WeakReference<SSLSession>(session);
+        }
+
+        boolean check() {
+            System.out.printf("  check: %s%n", this);
+            return unboundNotified > 0 && session.get() == null;
+        }
+
+        @Override
+        public void valueBound(SSLSessionBindingEvent event) {
+            System.out.printf(" valueBound: %s%n", event.getName());
+        }
+
+        @Override
+        public void valueUnbound(SSLSessionBindingEvent event) {
+            System.out.printf(" valueUnbound: %s%n", event.getName());
+            unboundNotified++;
+        }
+
+        public String toString() {
+            return "count: " + unboundNotified +
+                    ", ref: " + session.get();
+        }
+    }
+}
+