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