src/java.base/share/classes/sun/security/ssl/NewSessionTicket.java
changeset 57485 af4b0fc25bc4
parent 55336 c2398053ee90
child 58679 9c3209ff7550
--- 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(