8357033: Reduce stateless session ticket size

Reviewed-by: wetmore, djelinski, ascarpino
This commit is contained in:
Artur Barashev 2025-05-30 16:03:13 +00:00
parent 26275a10b2
commit 99048c3d4a
5 changed files with 261 additions and 238 deletions

View File

@ -443,12 +443,6 @@ final class PreSharedKeyExtension {
result = false; result = false;
} }
// Make sure that the server handshake context's
// localSupportedCertSignAlgs field is populated. This is particularly
// important when client authentication was used in an initial session,
// and it is now being resumed.
SignatureScheme.updateHandshakeLocalSupportedAlgs(shc);
// Validate the required client authentication. // Validate the required client authentication.
if (result && if (result &&
(shc.sslConfig.clientAuthType == CLIENT_AUTH_REQUIRED)) { (shc.sslConfig.clientAuthType == CLIENT_AUTH_REQUIRED)) {
@ -464,7 +458,9 @@ final class PreSharedKeyExtension {
result = false; result = false;
} }
// Make sure the list of supported signature algorithms matches // Make sure the list of supported signature algorithms matches.
// HandshakeContext's localSupportedCertSignAlgs has been already
// updated when we set the negotiated protocol.
Collection<SignatureScheme> sessionSigAlgs = Collection<SignatureScheme> sessionSigAlgs =
s.getLocalSupportedSignatureSchemes(); s.getLocalSupportedSignatureSchemes();
if (result && if (result &&

View File

@ -24,34 +24,33 @@
*/ */
package sun.security.ssl; package sun.security.ssl;
import sun.security.provider.X509Factory;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.Principal; import java.security.Principal;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Queue;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.zip.Adler32;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.ExtendedSSLSession; import javax.net.ssl.ExtendedSSLSession;
import javax.net.ssl.SNIHostName; import javax.net.ssl.SNIHostName;
import javax.net.ssl.SNIServerName; import javax.net.ssl.SNIServerName;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSessionBindingEvent; import javax.net.ssl.SSLSessionBindingEvent;
import javax.net.ssl.SSLSessionBindingListener; import javax.net.ssl.SSLSessionBindingListener;
import javax.net.ssl.SSLSessionContext; import javax.net.ssl.SSLSessionContext;
import sun.security.provider.X509Factory;
import sun.security.ssl.X509Authentication.X509Possession;
/** /**
* Implements the SSL session interface, and exposes the session context * Implements the SSL session interface, and exposes the session context
@ -256,59 +255,51 @@ final class SSLSessionImpl extends ExtendedSSLSession {
} }
/** /**
* Reassemble new session ticket.
* <p>
* < 2 bytes > protocolVersion * < 2 bytes > protocolVersion
* < 2 bytes > cipherSuite * < 2 bytes > cipherSuite
* < 1 byte > localSupportedSignAlgs entries * < 1 byte > localSupportedSignAlgs entries
* < 2 bytes per entries > localSupportedSignAlgs * < 2 bytes per entries > localSupportedSignAlgs
* < 1 bytes > peerSupportedSignAlgs entries * select (protocolVersion)
* < 2 bytes per entries > peerSupportedSignAlgs * case TLS13Plus:
* < 2 bytes > preSharedKey length * < 2 bytes > preSharedKey length
* < length in bytes > preSharedKey * < length in bytes > preSharedKey
* < 1 byte > pskIdentity length * case non-TLS13Plus:
* < length in bytes > pskIdentity * < 2 bytes > masterSecretKey length
* < 1 byte > masterSecret length * < length in bytes> masterSecretKey
* < 1 byte > masterSecret algorithm length * < 1 byte > useExtendedMasterSecret
* < length in bytes > masterSecret algorithm
* < 2 bytes > masterSecretKey length
* < length in bytes> masterSecretKey
* < 1 byte > useExtendedMasterSecret
* < 1 byte > identificationProtocol length * < 1 byte > identificationProtocol length
* < length in bytes > identificationProtocol * < length in bytes > identificationProtocol
* < 1 byte > serverNameIndication length * < 1 byte > serverNameIndication length
* < length in bytes > serverNameIndication * < length in bytes > serverNameIndication
* < 1 byte > Number of requestedServerNames entries * < 1 byte > Number of requestedServerNames entries
* < 1 byte > ServerName length * For each entry {
* < length in bytes > ServerName * < 1 byte > ServerName length
* < length in bytes > ServerName
* }
* < 4 bytes > maximumPacketSize * < 4 bytes > maximumPacketSize
* < 4 bytes > negotiatedMaxFragSize * < 4 bytes > negotiatedMaxFragSize
* < 4 bytes > creationTime * < 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 * < 1 byte > Length of peer host
* < length in bytes > peer host * < length in bytes > peer host
* < 2 bytes> peer port * < 2 bytes> peer port
* < 1 byte > Number of peerCerts entries * < 1 byte > Number of Peer Certificate entries
* < 4 byte > peerCert length * For each entry {
* < length in bytes > peerCert * < 4 bytes > Peer certificate length
* < 1 byte > localCerts type (Cert, PSK, Anonymous) * < length in bytes> Peer certificate
* Certificate * }
* < 1 byte > Number of Certificate entries * < 1 byte > Number of Local Certificate entries
* < 4 byte> Certificate length * For each entry {
* < length in bytes> Certificate * < 1 byte > Local Certificate algorithm length
* PSK * < length in bytes> Local Certificate algorithm
* < 1 byte > Number of PSK entries * < 4 bytes > Certificate checksum
* < 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 { SSLSessionImpl(HandshakeContext hc, ByteBuffer buf) throws IOException {
int len;
byte[] b;
boundValues = new ConcurrentHashMap<>(); boundValues = new ConcurrentHashMap<>();
this.protocolVersion = this.protocolVersion =
ProtocolVersion.valueOf(Record.getInt16(buf)); ProtocolVersion.valueOf(Record.getInt16(buf));
@ -321,51 +312,36 @@ final class SSLSessionImpl extends ExtendedSSLSession {
CipherSuite.valueOf(Record.getInt16(buf)); CipherSuite.valueOf(Record.getInt16(buf));
// Local Supported signature algorithms // Local Supported signature algorithms
ArrayList<SignatureScheme> list = new ArrayList<>(); List<SignatureScheme> list = new ArrayList<>();
int i = Record.getInt8(buf); len = Record.getInt8(buf);
while (i-- > 0) { while (len-- > 0) {
list.add(SignatureScheme.valueOf( list.add(SignatureScheme.valueOf(
Record.getInt16(buf))); Record.getInt16(buf)));
} }
this.localSupportedSignAlgs = Collections.unmodifiableCollection(list); this.localSupportedSignAlgs = Collections.unmodifiableCollection(list);
// Peer Supported signature algorithms if (protocolVersion.useTLS13PlusSpec()) {
i = Record.getInt8(buf); // PSK
list.clear();
while (i-- > 0) {
list.add(SignatureScheme.valueOf(
Record.getInt16(buf)));
}
this.peerSupportedSignAlgs = Collections.unmodifiableCollection(list);
// PSK
byte[] b = Record.getBytes16(buf);
if (b.length > 0) {
b = Record.getBytes16(buf); b = Record.getBytes16(buf);
this.preSharedKey = new SecretKeySpec(b, "TlsMasterSecret"); if (b.length > 0) {
} else { this.preSharedKey = new SecretKeySpec(b, "TlsMasterSecret");
this.preSharedKey = null; } else {
} this.preSharedKey = null;
}
// PSK identity this.useExtendedMasterSecret = false;
b = Record.getBytes8(buf);
if (b.length > 0) {
this.pskIdentity = b;
} else { } else {
this.pskIdentity = null; // Master secret
}
// Master secret length of secret key algorithm (one byte)
b = Record.getBytes8(buf);
if (b.length > 0) {
b = Record.getBytes16(buf); b = Record.getBytes16(buf);
this.masterSecret = new SecretKeySpec(b, "TlsMasterSecret"); if (b.length > 0) {
} else { this.masterSecret = new SecretKeySpec(b, "TlsMasterSecret");
this.masterSecret = null; } else {
} this.masterSecret = null;
}
// Use extended master secret // Extended master secret usage.
this.useExtendedMasterSecret = (Record.getInt8(buf) != 0); this.useExtendedMasterSecret = (Record.getInt8(buf) != 0);
}
// Identification Protocol // Identification Protocol
b = Record.getBytes8(buf); b = Record.getBytes8(buf);
@ -384,7 +360,7 @@ final class SSLSessionImpl extends ExtendedSSLSession {
} }
// List of SNIServerName // List of SNIServerName
int len = Record.getInt16(buf); len = Record.getInt16(buf);
if (len == 0) { if (len == 0) {
this.requestedServerNames = Collections.emptyList(); this.requestedServerNames = Collections.emptyList();
} else { } else {
@ -401,20 +377,6 @@ final class SSLSessionImpl extends ExtendedSSLSession {
// Get creation time // Get creation time
this.creationTime = buf.getLong(); this.creationTime = buf.getLong();
// Get Buffer sizes
// Status Response
len = Record.getInt16(buf);
if (len == 0) {
statusResponses = Collections.emptyList();
} else {
statusResponses = new ArrayList<>();
}
while (len-- > 0) {
b = Record.getBytes16(buf);
statusResponses.add(b);
}
// Get Peer host & port // Get Peer host & port
b = Record.getBytes8(buf); b = Record.getBytes8(buf);
if (b.length == 0) { if (b.length == 0) {
@ -424,71 +386,89 @@ final class SSLSessionImpl extends ExtendedSSLSession {
} }
this.port = Record.getInt16(buf); this.port = Record.getInt16(buf);
// Peer certs // Peer certs.
i = Record.getInt8(buf); len = Record.getInt8(buf);
if (i == 0) { if (len == 0) {
this.peerCerts = null; this.peerCerts = null;
} else { } else {
this.peerCerts = new X509Certificate[i]; this.peerCerts = new X509Certificate[len];
int j = 0; for (int i = 0; len > i; i++) {
while (i > j) {
b = new byte[buf.getInt()]; b = new byte[buf.getInt()];
buf.get(b); buf.get(b);
try { try {
this.peerCerts[j] = X509Factory.cachedGetX509Cert(b); this.peerCerts[i] = X509Factory.cachedGetX509Cert(b);
} catch (Exception e) { } catch (Exception e) {
throw new IOException(e); throw new IOException(e);
} }
j++;
} }
} }
// Get local certs of PSK // Restore local certificates if cert algorithm(s) present.
switch (Record.getInt8(buf)) { len = Record.getInt8(buf);
case 0: if (len == 0) {
break; this.localCerts = null;
case 1: } else {
// number of certs String[] certAlgs = new String[len];
len = buf.get(); int[] certCheckSums = new int[len];
this.localCerts = new X509Certificate[len];
i = 0; for (int i = 0; len > i; i++) {
while (len > i) { certAlgs[i] = new String(Record.getBytes8(buf));
b = new byte[buf.getInt()]; certCheckSums[i] = Record.getInt32(buf);
buf.get(b); }
SSLPossession pos = X509Authentication.createPossession(
hc, certAlgs);
boolean same = false;
if (pos instanceof X509Possession x509Pos
&& x509Pos.popCerts != null
&& x509Pos.popCerts.length == len) {
// Make sure we got the exact same cert chain.
for (int i = 0; i < x509Pos.popCerts.length; i++) {
try { try {
this.localCerts[i] = X509Factory.cachedGetX509Cert(b); byte[] encoded = x509Pos.popCerts[i].getEncoded();
String popAlg = x509Pos.popCerts[i]
.getPublicKey().getAlgorithm();
if (certCheckSums[i] == getChecksum(encoded)
&& certAlgs[i].equals(popAlg)) {
// Use certs from cache.
x509Pos.popCerts[i] =
X509Factory.cachedGetX509Cert(encoded);
same = true;
} else {
same = false;
break;
}
} catch (Exception e) { } catch (Exception e) {
throw new IOException(e); throw new IOException(e);
} }
i++;
} }
break; }
case 2:
// pre-shared key if (same) {
// Length of pre-shared key algorithm (one byte) this.localCerts = ((X509Possession) pos).popCerts;
b = Record.getBytes8(buf); if (SSLLogger.isOn && SSLLogger.isOn("ssl,session")) {
String alg = new String(b); SSLLogger.fine("Restored " + len
// Get encoding + " local certificates from session ticket"
b = Record.getBytes16(buf); + " for algorithms " + Arrays.toString(certAlgs));
this.preSharedKey = new SecretKeySpec(b, alg);
// Get identity len
i = Record.getInt8(buf);
if (i > 0) {
this.pskIdentity = Record.getBytes8(buf);
} else {
this.pskIdentity = null;
} }
break; } else {
default: this.localCerts = null;
throw new SSLException("Failed local certs of session."); this.invalidated = true;
if (SSLLogger.isOn && SSLLogger.isOn("ssl,session")) {
SSLLogger.warning("Local certificates can not be restored "
+ "from session ticket "
+ "for algorithms " + Arrays.toString(certAlgs));
}
}
} }
context = (SSLSessionContextImpl) this.context = (SSLSessionContextImpl)
hc.sslContext.engineGetServerSessionContext(); hc.sslContext.engineGetServerSessionContext();
this.lastUsedTime = System.currentTimeMillis(); this.lastUsedTime = System.currentTimeMillis();
} }
// Some situations we cannot provide a stateless ticket, but after it // Some situations we cannot provide a stateless ticket, but after it
// has been negotiated // has been negotiated
boolean isStatelessable() { boolean isStatelessable() {
@ -529,49 +509,25 @@ final class SSLSessionImpl extends ExtendedSSLSession {
hos.putInt16(s.id); hos.putInt16(s.id);
} }
// Peer Supported signature algorithms // PreSharedKey is only needed by TLSv1.3,
hos.putInt8(peerSupportedSignAlgs.size()); // masterSecret is only needed by pre-TLSv1.3.
for (SignatureScheme s : peerSupportedSignAlgs) { if (protocolVersion.useTLS13PlusSpec()) {
hos.putInt16(s.id); // PSK
} if (preSharedKey == null) {
hos.putInt16(0);
// PSK } else {
if (preSharedKey == null || hos.putBytes16(preSharedKey.getEncoded());
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 { } else {
hos.putInt8(pskIdentity.length); // Master Secret
hos.write(pskIdentity, 0, pskIdentity.length); if (getMasterSecret() == null) {
} hos.putInt16(0);
} else {
// Master Secret hos.putBytes16(masterSecret.getEncoded());
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); hos.putInt8(useExtendedMasterSecret ? 1 : 0);
}
// Identification Protocol // Identification Protocol
if (identificationProtocol == null) { if (identificationProtocol == null) {
@ -605,20 +561,11 @@ final class SSLSessionImpl extends ExtendedSSLSession {
hos.putInt32(maximumPacketSize); hos.putInt32(maximumPacketSize);
hos.putInt32(negotiatedMaxFragLen); hos.putInt32(negotiatedMaxFragLen);
// creation time // Creation time
ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
hos.writeBytes(buffer.putLong(creationTime).array()); hos.writeBytes(buffer.putLong(creationTime).array());
// Status Responses // Peer Host & Port
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) { if (host == null || host.length() == 0) {
hos.putInt8(0); hos.putInt8(0);
} else { } else {
@ -627,7 +574,7 @@ final class SSLSessionImpl extends ExtendedSSLSession {
} }
hos.putInt16(port); hos.putInt16(port);
// Peer cert // Peer certs.
if (peerCerts == null || peerCerts.length == 0) { if (peerCerts == null || peerCerts.length == 0) {
hos.putInt8(0); hos.putInt8(0);
} else { } else {
@ -639,34 +586,28 @@ final class SSLSessionImpl extends ExtendedSSLSession {
} }
} }
// Client identity // Local certificates' algorithms and checksums.
if (localCerts != null && localCerts.length > 0) { // We don't include the complete local certificates in a session ticket
// certificate based // to decrease the size of ClientHello message.
hos.putInt8(1); if (localCerts == null || localCerts.length == 0) {
hos.putInt8(0);
} else {
hos.putInt8(localCerts.length); hos.putInt8(localCerts.length);
for (X509Certificate c : localCerts) { for (X509Certificate c : localCerts) {
b = c.getEncoded(); hos.putBytes8(c.getPublicKey().getAlgorithm().getBytes());
hos.putInt32(b.length); hos.putInt32(getChecksum(c.getEncoded()));
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(); return hos.toByteArray();
} }
private static int getChecksum(byte[] input) {
Adler32 adler32 = new Adler32();
adler32.update(input);
return (int) adler32.getValue();
}
void setMasterSecret(SecretKey secret) { void setMasterSecret(SecretKey secret) {
masterSecret = secret; masterSecret = secret;
} }

View File

@ -25,12 +25,16 @@
package sun.security.ssl; package sun.security.ssl;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.Locale; import java.util.Locale;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.KeyGenerator; import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
@ -70,6 +74,9 @@ final class SessionTicketExtension {
new T12SHSessionTicketConsumer(); new T12SHSessionTicketConsumer();
static final SSLStringizer steStringizer = new SessionTicketStringizer(); static final SSLStringizer steStringizer = new SessionTicketStringizer();
// No need to compress a ticket if it can fit in a single packet.
// Besides, small buffers often end up to be larger when compressed.
static final int MIN_COMPRESS_SIZE = 600;
// Time in milliseconds until key is changed for encrypting session state // Time in milliseconds until key is changed for encrypting session state
private static final int TIMEOUT_DEFAULT = 3600 * 1000; private static final int TIMEOUT_DEFAULT = 3600 * 1000;
@ -196,7 +203,7 @@ final class SessionTicketExtension {
data = buf; data = buf;
} }
public byte[] encrypt(HandshakeContext hc, SSLSessionImpl session) { byte[] encrypt(HandshakeContext hc, SSLSessionImpl session) {
byte[] encrypted; byte[] encrypted;
if (!hc.statelessResumption || if (!hc.statelessResumption ||
@ -213,26 +220,34 @@ final class SessionTicketExtension {
Cipher c = Cipher.getInstance("AES/GCM/NoPadding"); Cipher c = Cipher.getInstance("AES/GCM/NoPadding");
c.init(Cipher.ENCRYPT_MODE, key.key, c.init(Cipher.ENCRYPT_MODE, key.key,
new GCMParameterSpec(GCM_TAG_LEN, iv)); 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)}
);
byte[] data = session.write(); byte[] data = session.write();
if (data.length == 0) { if (data.length == 0) {
return data; return data;
} }
// Compress the session before encryption if needed.
byte compressed = 0;
if (data.length >= MIN_COMPRESS_SIZE) {
data = compress(data);
compressed = 1;
}
ByteBuffer aad = ByteBuffer.allocate(Integer.BYTES + 1);
aad.putInt(key.num).put(compressed);
c.updateAAD(aad);
encrypted = c.doFinal(data); encrypted = c.doFinal(data);
byte[] result = new byte[encrypted.length + Integer.BYTES + byte[] result = new byte[encrypted.length + Integer.BYTES +
iv.length]; iv.length + 1];
result[0] = (byte)(key.num >>> 24); result[0] = (byte)(key.num >>> 24);
result[1] = (byte)(key.num >>> 16); result[1] = (byte)(key.num >>> 16);
result[2] = (byte)(key.num >>> 8); result[2] = (byte)(key.num >>> 8);
result[3] = (byte)(key.num); result[3] = (byte)(key.num);
System.arraycopy(iv, 0, result, Integer.BYTES, iv.length); System.arraycopy(iv, 0, result, Integer.BYTES, iv.length);
result[Integer.BYTES + iv.length] = compressed;
System.arraycopy(encrypted, 0, result, System.arraycopy(encrypted, 0, result,
Integer.BYTES + iv.length, encrypted.length); Integer.BYTES + iv.length + 1, encrypted.length);
return result; return result;
} catch (Exception e) { } catch (Exception e) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
@ -257,26 +272,67 @@ final class SessionTicketExtension {
Cipher c = Cipher.getInstance("AES/GCM/NoPadding"); Cipher c = Cipher.getInstance("AES/GCM/NoPadding");
c.init(Cipher.DECRYPT_MODE, key.key, c.init(Cipher.DECRYPT_MODE, key.key,
new GCMParameterSpec(GCM_TAG_LEN, iv)); new GCMParameterSpec(GCM_TAG_LEN, iv));
c.updateAAD(new byte[] {
(byte)(keyID >>> 24),
(byte)(keyID >>> 16),
(byte)(keyID >>> 8),
(byte)(keyID)}
);
ByteBuffer out; byte compressed = data.get();
out = ByteBuffer.allocate(data.remaining() - GCM_TAG_LEN / 8); ByteBuffer aad = ByteBuffer.allocate(Integer.BYTES + 1);
aad.putInt(keyID).put(compressed);
c.updateAAD(aad);
ByteBuffer out = ByteBuffer.allocate(
data.remaining() - GCM_TAG_LEN / 8);
c.doFinal(data, out); c.doFinal(data, out);
out.flip(); out.flip();
// Decompress the session after decryption if needed.
if (compressed == 1) {
out = decompress(out);
}
return out; return out;
} catch (Exception e) { } catch (Exception e) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
SSLLogger.fine("Decryption failed." + e.getMessage()); SSLLogger.fine("Decryption failed." + e.getMessage());
} }
} }
return null; return null;
} }
private static byte[] compress(byte[] input) throws IOException {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
GZIPOutputStream gos = new GZIPOutputStream(baos)) {
final int decompressedLen = input.length;
gos.write(input, 0, decompressedLen);
gos.finish();
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
SSLLogger.fine("decompressed bytes: " + decompressedLen
+ "; compressed bytes: " + baos.size());
}
return baos.toByteArray();
}
}
private static ByteBuffer decompress(ByteBuffer input)
throws IOException {
final int compressedLen = input.remaining();
byte[] bytes = new byte[compressedLen];
input.get(bytes);
try (GZIPInputStream gis = new GZIPInputStream(
new ByteArrayInputStream(bytes))) {
byte[] out = gis.readAllBytes();
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
SSLLogger.fine("compressed bytes: " + compressedLen
+ "; decompressed bytes: " + out.length);
}
return ByteBuffer.wrap(out);
}
}
byte[] getEncoded() { byte[] getEncoded() {
byte[] out = new byte[data.capacity()]; byte[] out = new byte[data.capacity()];
data.duplicate().get(out); data.duplicate().get(out);

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -26,6 +26,7 @@
* @bug 8206929 * @bug 8206929
* @summary ensure that server only resumes a session if certain properties * @summary ensure that server only resumes a session if certain properties
* of the session are compatible with the new connection * of the session are compatible with the new connection
* @modules java.base/sun.security.x509
* @library /javax/net/ssl/templates * @library /javax/net/ssl/templates
* @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=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=false ResumeChecksServer BASIC
@ -48,6 +49,7 @@ import java.io.*;
import java.security.*; import java.security.*;
import java.net.*; import java.net.*;
import java.util.*; import java.util.*;
import sun.security.x509.X509CertImpl;
public class ResumeChecksServer extends SSLContextTemplate { public class ResumeChecksServer extends SSLContextTemplate {
@ -57,7 +59,8 @@ public class ResumeChecksServer extends SSLContextTemplate {
VERSION_2_TO_3, VERSION_2_TO_3,
VERSION_3_TO_2, VERSION_3_TO_2,
CIPHER_SUITE, CIPHER_SUITE,
SIGNATURE_SCHEME SIGNATURE_SCHEME,
LOCAL_CERTS
} }
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
@ -71,6 +74,7 @@ public class ResumeChecksServer extends SSLContextTemplate {
} }
private void run() throws Exception { private void run() throws Exception {
SSLSession firstSession;
SSLSession secondSession = null; SSLSession secondSession = null;
SSLContext sslContext = createServerSSLContext(); SSLContext sslContext = createServerSSLContext();
@ -81,7 +85,7 @@ public class ResumeChecksServer extends SSLContextTemplate {
Client client = startClient(ssock.getLocalPort()); Client client = startClient(ssock.getLocalPort());
try { try {
connect(client, ssock, testMode, false); firstSession = connect(client, ssock, testMode, null);
} catch (Exception ex) { } catch (Exception ex) {
throw new RuntimeException(ex); throw new RuntimeException(ex);
} }
@ -89,7 +93,7 @@ public class ResumeChecksServer extends SSLContextTemplate {
long secondStartTime = System.currentTimeMillis(); long secondStartTime = System.currentTimeMillis();
Thread.sleep(10); Thread.sleep(10);
try { try {
secondSession = connect(client, ssock, testMode, true); secondSession = connect(client, ssock, testMode, firstSession);
} catch (SSLHandshakeException ex) { } catch (SSLHandshakeException ex) {
// this is expected // this is expected
} catch (Exception ex) { } catch (Exception ex) {
@ -105,6 +109,14 @@ public class ResumeChecksServer extends SSLContextTemplate {
if (secondSession.getCreationTime() > secondStartTime) { if (secondSession.getCreationTime() > secondStartTime) {
throw new RuntimeException("Session was not reused"); throw new RuntimeException("Session was not reused");
} }
// Fail if session's certificates are not restored correctly.
if (!java.util.Arrays.equals(
firstSession.getLocalCertificates(),
secondSession.getLocalCertificates())) {
throw new RuntimeException("Certificates do not match");
}
break; break;
case CLIENT_AUTH: case CLIENT_AUTH:
// throws an exception if the client is not authenticated // throws an exception if the client is not authenticated
@ -114,6 +126,7 @@ public class ResumeChecksServer extends SSLContextTemplate {
case VERSION_3_TO_2: case VERSION_3_TO_2:
case CIPHER_SUITE: case CIPHER_SUITE:
case SIGNATURE_SCHEME: case SIGNATURE_SCHEME:
case LOCAL_CERTS:
// fail if a new session is not created // fail if a new session is not created
if (secondSession.getCreationTime() <= secondStartTime) { if (secondSession.getCreationTime() <= secondStartTime) {
throw new RuntimeException("Existing session was used"); throw new RuntimeException("Existing session was used");
@ -153,7 +166,9 @@ public class ResumeChecksServer extends SSLContextTemplate {
} }
private static SSLSession connect(Client client, SSLServerSocket ssock, private static SSLSession connect(Client client, SSLServerSocket ssock,
TestMode mode, boolean second) throws Exception { TestMode mode, SSLSession firstSession) throws Exception {
boolean second = firstSession != null;
try { try {
client.signal(); client.signal();
@ -200,9 +215,22 @@ public class ResumeChecksServer extends SSLContextTemplate {
AlgorithmConstraints constraints = AlgorithmConstraints constraints =
params.getAlgorithmConstraints(); params.getAlgorithmConstraints();
if (second) { if (second) {
params.setAlgorithmConstraints(new NoSig("ecdsa")); params.setAlgorithmConstraints(
new NoSig("ecdsa_secp384r1_sha384"));
} else { } else {
params.setAlgorithmConstraints(new NoSig("rsa")); params.setAlgorithmConstraints(
new NoSig("ecdsa_secp521r1_sha512"));
}
break;
case LOCAL_CERTS:
if (second) {
// Add first session's certificate signature
// algorithm to constraints so local certificates
// can't be restored from the session ticket.
params.setAlgorithmConstraints(
new NoSig(X509CertImpl.toImpl((X509CertImpl)
firstSession.getLocalCertificates()[0])
.getSigAlgName()));
} }
break; break;
default: default:

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -23,9 +23,10 @@
/* /*
* @test * @test
* @bug 8211018 * @bug 8211018 8357033
* @summary ensure that server only resumes a session if certain properties * @summary ensure that server only resumes a session if certain properties
* of the session are compatible with the new connection * of the session are compatible with the new connection
* @modules java.base/sun.security.x509
* @library /javax/net/ssl/templates * @library /javax/net/ssl/templates
* @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 ResumeChecksServer BASIC * @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 -Djdk.tls.client.protocols=TLSv1.3 ResumeChecksServer CLIENT_AUTH
@ -33,4 +34,5 @@
* @run main/othervm ResumeChecksServer VERSION_3_TO_2 * @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 CIPHER_SUITE
* @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 ResumeChecksServer SIGNATURE_SCHEME * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 ResumeChecksServer SIGNATURE_SCHEME
* @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 ResumeChecksServer LOCAL_CERTS
*/ */