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