# HG changeset patch # User ascarpino # Date 1560295897 25200 # Node ID c2398053ee900bc0aa78309c65163bf41e484eb5 # Parent f7cc25dda38a14c214a0be36126b7ad6f2850034 8211018: Session Resumption without Server-Side State Reviewed-by: xuelei, jnimeh, jjiang diff -r f7cc25dda38a -r c2398053ee90 src/java.base/share/classes/sun/security/ssl/ClientHello.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(); diff -r f7cc25dda38a -r c2398053ee90 src/java.base/share/classes/sun/security/ssl/Finished.java --- 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); } diff -r f7cc25dda38a -r c2398053ee90 src/java.base/share/classes/sun/security/ssl/HandshakeContext.java --- 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> delegatedActions; volatile boolean taskDelegated = false; @@ -551,7 +553,7 @@ List getRequestedServerNames() { if (requestedServerNames == null) { - return Collections.emptyList(); + return Collections.emptyList(); } return requestedServerNames; } diff -r f7cc25dda38a -r c2398053ee90 src/java.base/share/classes/sun/security/ssl/NewSessionTicket.java --- 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, "", //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()); + } } } } diff -r f7cc25dda38a -r c2398053ee90 src/java.base/share/classes/sun/security/ssl/PreSharedKeyExtension.java --- 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 identities) { List binders = new ArrayList<>(); byte[] binderProto = new byte[hashLength]; - for (PskIdentity curId : identities) { + int i = identities.size(); + while (i-- > 0) { binders.add(binderProto); } diff -r f7cc25dda38a -r c2398053ee90 src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java --- 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 keyHashMap = new HashMap<>(); + SSLContextImpl() { ephemeralKeyManager = new EphemeralKeyManager(); - clientCache = new SSLSessionContextImpl(); - serverCache = new SSLSessionContextImpl(); + clientCache = new SSLSessionContextImpl(false); + serverCache = new SSLSessionContextImpl(true); } @Override diff -r f7cc25dda38a -r c2398053ee90 src/java.base/share/classes/sun/security/ssl/SSLExtension.java --- 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"), diff -r f7cc25dda38a -r c2398053ee90 src/java.base/share/classes/sun/security/ssl/SSLHandshake.java --- 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[])(new Map.Entry[] { new SimpleImmutableEntry( - NewSessionTicket.handshakeConsumer, - ProtocolVersion.PROTOCOLS_OF_13 - ) + NewSessionTicket.handshake12Consumer, + ProtocolVersion.PROTOCOLS_TO_12 + ), + new SimpleImmutableEntry( + NewSessionTicket.handshakeConsumer, + ProtocolVersion.PROTOCOLS_OF_13 + ) }), (Map.Entry[])(new Map.Entry[] { new SimpleImmutableEntry( - NewSessionTicket.handshakeProducer, - ProtocolVersion.PROTOCOLS_OF_13 - ) + NewSessionTicket.handshake12Producer, + ProtocolVersion.PROTOCOLS_TO_12 + ) })), END_OF_EARLY_DATA ((byte)0x05, "end_of_early_data"), diff -r f7cc25dda38a -r c2398053ee90 src/java.base/share/classes/sun/security/ssl/SSLSessionContextImpl.java --- 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 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 SSLSession 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); diff -r f7cc25dda38a -r c2398053ee90 src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java --- 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.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 diff -r f7cc25dda38a -r c2398053ee90 src/java.base/share/classes/sun/security/ssl/ServerHello.java --- 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); diff -r f7cc25dda38a -r c2398053ee90 src/java.base/share/classes/sun/security/ssl/SessionId.java --- 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 diff -r f7cc25dda38a -r c2398053ee90 src/java.base/share/classes/sun/security/ssl/SessionTicketExtension.java --- /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 ""; + } + if (data.capacity() == 0) { + return ""; + } + + 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); + } + } + } +} diff -r f7cc25dda38a -r c2398053ee90 src/java.base/share/classes/sun/security/ssl/TransportContext.java --- 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) : diff -r f7cc25dda38a -r c2398053ee90 test/jdk/javax/net/ssl/DTLS/PacketLossRetransmission.java --- 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 diff -r f7cc25dda38a -r c2398053ee90 test/jdk/javax/net/ssl/SSLSession/SSLCtxAccessToSessCtx.java --- 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. diff -r f7cc25dda38a -r c2398053ee90 test/jdk/sun/security/ssl/DHKeyExchange/DHEKeySizing.java --- 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 */ diff -r f7cc25dda38a -r c2398053ee90 test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksClient.java --- 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 * */ diff -r f7cc25dda38a -r c2398053ee90 test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksClientStateless.java --- /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 + */ diff -r f7cc25dda38a -r c2398053ee90 test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksServer.java --- 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 * */ diff -r f7cc25dda38a -r c2398053ee90 test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksServerStateless.java --- /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 + */