src/java.base/share/classes/sun/security/ssl/NewSessionTicket.java
branchJDK-8145252-TLS13-branch
changeset 56702 75527e40bdfd
parent 56672 ec5537b61038
child 56716 38c2a4078033
--- a/src/java.base/share/classes/sun/security/ssl/NewSessionTicket.java	Fri Jun 08 11:10:40 2018 +0800
+++ b/src/java.base/share/classes/sun/security/ssl/NewSessionTicket.java	Thu Jun 07 21:16:21 2018 -0700
@@ -35,6 +35,7 @@
 import java.util.Optional;
 import javax.crypto.SecretKey;
 import javax.net.ssl.SSLHandshakeException;
+import sun.security.ssl.PskKeyExchangeModesExtension.PskKeyExchangeModesSpec;
 
 import sun.security.ssl.SSLHandshake.HandshakeMessage;
 
@@ -42,8 +43,7 @@
  * Pack of the NewSessionTicket handshake message.
  */
 final class NewSessionTicket {
-
-    private static final int SEVEN_DAYS_IN_SECONDS = 604800;
+    private static final int MAX_TICKET_LIFETIME = 604800;  // seconds, 7 days
 
     static final SSLConsumer handshakeConsumer =
         new NewSessionTicketConsumer();
@@ -78,24 +78,42 @@
                 ByteBuffer m) throws IOException {
             super(context);
 
+            // struct {
+            //     uint32 ticket_lifetime;
+            //     uint32 ticket_age_add;
+            //     opaque ticket_nonce<0..255>;
+            //     opaque ticket<1..2^16-1>;
+            //     Extension extensions<0..2^16-2>;
+            // } NewSessionTicket;
+            if (m.remaining() < 14) {
+                context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
+                    "Invalid NewSessionTicket message: no sufficient data");
+            }
+
             this.ticketLifetime = Record.getInt32(m);
             this.ticketAgeAdd = Record.getInt32(m);
             this.ticketNonce = Record.getBytes8(m);
+
+            if (m.remaining() < 5) {
+                context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
+                    "Invalid NewSessionTicket message: no sufficient data");
+            }
+
             this.ticket = Record.getBytes16(m);
-            if (this.ticket.length == 0) {
+            if (ticket.length == 0) {
                 context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
-                        "No ticket in the NewSessionTicket handshake message");
+                    "No ticket in the NewSessionTicket handshake message");
+            }
+
+            if (m.remaining() < 2) {
+                context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
+                    "Invalid NewSessionTicket message: no sufficient data");
             }
 
             SSLExtension[] supportedExtensions =
                     context.sslConfig.getEnabledExtensions(
                             SSLHandshake.NEW_SESSION_TICKET);
-            if (m.hasRemaining()) {
-                this.extensions =
-                    new SSLExtensions(this, m, supportedExtensions);
-            } else {
-                this.extensions = new SSLExtensions(this);
-            }
+            this.extensions = new SSLExtensions(this, m, supportedExtensions);
         }
 
         @Override
@@ -138,14 +156,14 @@
                 "  \"ticket_nonce\"         : \"{2}\",\n" +
                 "  \"ticket\"               : \"{3}\",\n" +
                 "  \"extensions\"           : [\n" +
-                "{5}\n" +
+                "{4}\n" +
                 "  ]\n" +
                 "'}'",
                 Locale.ENGLISH);
 
             Object[] messageFields = {
                 ticketLifetime,
-                "omitted", //ticketAgeAdd should not be logged
+                "<omitted>",    //ticketAgeAdd should not be logged
                 Utilities.toHexString(ticketNonce),
                 Utilities.toHexString(ticket),
                 Utilities.indent(extensions.toString(), "    ")
@@ -171,17 +189,32 @@
 
     private static final
             class NewSessionTicketKickstartProducer implements SSLProducer {
+        // Prevent instantiation of this class.
+        private NewSessionTicketKickstartProducer() {
+            // blank
+        }
+
         @Override
         public byte[] produce(ConnectionContext context) throws IOException {
             // The producing happens in server side only.
             ServerHandshakeContext shc = (ServerHandshakeContext)context;
 
-            if (shc.pskKeyExchangeModes.isEmpty()) {
-                // client doesn't support PSK
+            // Is this session resumable?
+            if (!shc.handshakeSession.isRejoinable()) {
                 return null;
             }
 
-            if (!shc.handshakeSession.isRejoinable()) {
+            // What's the requested PSK key exchange modes?
+            //
+            // Note that currently, the NewSessionTicket post-handshake is
+            // produced and delivered only in the current handshake context
+            // if required.
+            PskKeyExchangeModesSpec pkemSpec =
+                    (PskKeyExchangeModesSpec)shc.handshakeExtensions.get(
+                            SSLExtension.PSK_KEY_EXCHANGE_MODES);
+            if (pkemSpec == null || !pkemSpec.contains(
+                PskKeyExchangeModesExtension.PskKeyExchangeMode.PSK_DHE_KE)) {
+                // Client doesn't support PSK with (EC)DHE key establishment.
                 return null;
             }
 
@@ -209,7 +242,7 @@
                     resumptionMasterSecret.get(), nonceArr);
 
             int sessionTimeoutSeconds = sessionCache.getSessionTimeout();
-            if (sessionTimeoutSeconds > SEVEN_DAYS_IN_SECONDS) {
+            if (sessionTimeoutSeconds > MAX_TICKET_LIFETIME) {
                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
                     SSLLogger.fine(
                         "Session timeout is too long. No ticket sent.");
@@ -268,8 +301,6 @@
         }
     }
 
-
-
     private static final
             class NewSessionTicketConsumer implements SSLConsumer {
         // Prevent instantiation of this class.
@@ -280,9 +311,21 @@
         @Override
         public void consume(ConnectionContext context,
                             ByteBuffer message) throws IOException {
-            // The consuming happens in client side only.
-            PostHandshakeContext hc = (PostHandshakeContext) context;
-            NewSessionTicketMessage nstm = new NewSessionTicketMessage(hc, message);
+
+            // Note: Although the resumption master secret depends on the
+            // client's second flight, servers which do not request client
+            // authentication MAY compute the remainder of the transcript
+            // independently and then send a NewSessionTicket immediately
+            // upon sending its Finished rather than waiting for the client
+            // Finished.
+            //
+            // The consuming happens in client side only.  As the server
+            // may send the NewSessionTicket before handshake complete, the
+            // context may be a PostHandshakeContext or HandshakeContext
+            // instance.
+            HandshakeContext hc = (HandshakeContext)context;
+            NewSessionTicketMessage nstm =
+                    new NewSessionTicketMessage(hc, message);
             if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
                 SSLLogger.fine(
                 "Consuming NewSessionTicket message", nstm);
@@ -290,7 +333,7 @@
 
             // discard tickets with timeout 0
             if (nstm.ticketLifetime <= 0 ||
-                nstm.ticketLifetime > SEVEN_DAYS_IN_SECONDS) {
+                nstm.ticketLifetime > MAX_TICKET_LIFETIME) {
                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
                     SSLLogger.fine(
                     "Discarding NewSessionTicket with lifetime "
@@ -302,7 +345,7 @@
             SSLSessionContextImpl sessionCache = (SSLSessionContextImpl)
                 hc.sslContext.engineGetClientSessionContext();
 
-            if (sessionCache.getSessionTimeout() > SEVEN_DAYS_IN_SECONDS) {
+            if (sessionCache.getSessionTimeout() > MAX_TICKET_LIFETIME) {
                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
                     SSLLogger.fine(
                     "Session cache lifetime is too long. Discarding ticket.");