diff --git a/src/java.base/share/classes/sun/security/ssl/PreSharedKeyExtension.java b/src/java.base/share/classes/sun/security/ssl/PreSharedKeyExtension.java index f9c78774bc9..76bb64a66c3 100644 --- a/src/java.base/share/classes/sun/security/ssl/PreSharedKeyExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/PreSharedKeyExtension.java @@ -443,12 +443,6 @@ final class PreSharedKeyExtension { 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. if (result && (shc.sslConfig.clientAuthType == CLIENT_AUTH_REQUIRED)) { @@ -464,7 +458,9 @@ final class PreSharedKeyExtension { 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 sessionSigAlgs = s.getLocalSupportedSignatureSchemes(); if (result && diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java index f5e173c636a..032aa7de5b3 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java @@ -24,34 +24,33 @@ */ package sun.security.ssl; -import sun.security.provider.X509Factory; - import java.io.IOException; import java.net.InetAddress; import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; import java.security.Principal; import java.security.PrivateKey; 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; import java.util.List; +import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.locks.ReentrantLock; +import java.util.zip.Adler32; 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.SSLSessionBindingEvent; import javax.net.ssl.SSLSessionBindingListener; 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 @@ -256,59 +255,51 @@ final class SSLSessionImpl extends ExtendedSSLSession { } /** + * Reassemble new session ticket. + *

* < 2 bytes > protocolVersion * < 2 bytes > cipherSuite * < 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 - * < 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 + * select (protocolVersion) + * case TLS13Plus: + * < 2 bytes > preSharedKey length + * < length in bytes > preSharedKey + * case non-TLS13Plus: + * < 2 bytes > masterSecretKey length + * < length in bytes> masterSecretKey + * < 1 byte > useExtendedMasterSecret * < 1 byte > identificationProtocol length - * < length in bytes > identificationProtocol + * < length in bytes > identificationProtocol * < 1 byte > serverNameIndication length - * < length in bytes > serverNameIndication + * < length in bytes > serverNameIndication * < 1 byte > Number of requestedServerNames entries - * < 1 byte > ServerName length - * < length in bytes > ServerName + * For each entry { + * < 1 byte > ServerName length + * < length in bytes > ServerName + * } * < 4 bytes > maximumPacketSize * < 4 bytes > negotiatedMaxFragSize * < 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 - * < 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 > + * < 1 byte > Number of Peer Certificate entries + * For each entry { + * < 4 bytes > Peer certificate length + * < length in bytes> Peer certificate + * } + * < 1 byte > Number of Local Certificate entries + * For each entry { + * < 1 byte > Local Certificate algorithm length + * < length in bytes> Local Certificate algorithm + * < 4 bytes > Certificate checksum + * } */ SSLSessionImpl(HandshakeContext hc, ByteBuffer buf) throws IOException { + int len; + byte[] b; boundValues = new ConcurrentHashMap<>(); this.protocolVersion = ProtocolVersion.valueOf(Record.getInt16(buf)); @@ -321,51 +312,36 @@ final class SSLSessionImpl extends ExtendedSSLSession { CipherSuite.valueOf(Record.getInt16(buf)); // Local Supported signature algorithms - ArrayList list = new ArrayList<>(); - int i = Record.getInt8(buf); - while (i-- > 0) { + List list = new ArrayList<>(); + len = Record.getInt8(buf); + while (len-- > 0) { list.add(SignatureScheme.valueOf( Record.getInt16(buf))); } this.localSupportedSignAlgs = Collections.unmodifiableCollection(list); - // Peer Supported signature algorithms - i = Record.getInt8(buf); - 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) { + if (protocolVersion.useTLS13PlusSpec()) { + // PSK b = Record.getBytes16(buf); - this.preSharedKey = new SecretKeySpec(b, "TlsMasterSecret"); - } else { - this.preSharedKey = null; - } + if (b.length > 0) { + this.preSharedKey = new SecretKeySpec(b, "TlsMasterSecret"); + } else { + this.preSharedKey = null; + } - // PSK identity - b = Record.getBytes8(buf); - if (b.length > 0) { - this.pskIdentity = b; + this.useExtendedMasterSecret = false; } else { - this.pskIdentity = null; - } - - // Master secret length of secret key algorithm (one byte) - b = Record.getBytes8(buf); - if (b.length > 0) { + // Master secret b = Record.getBytes16(buf); - this.masterSecret = new SecretKeySpec(b, "TlsMasterSecret"); - } else { - this.masterSecret = null; - } + if (b.length > 0) { + this.masterSecret = new SecretKeySpec(b, "TlsMasterSecret"); + } else { + this.masterSecret = null; + } - // Use extended master secret - this.useExtendedMasterSecret = (Record.getInt8(buf) != 0); + // Extended master secret usage. + this.useExtendedMasterSecret = (Record.getInt8(buf) != 0); + } // Identification Protocol b = Record.getBytes8(buf); @@ -384,7 +360,7 @@ final class SSLSessionImpl extends ExtendedSSLSession { } // List of SNIServerName - int len = Record.getInt16(buf); + len = Record.getInt16(buf); if (len == 0) { this.requestedServerNames = Collections.emptyList(); } else { @@ -401,20 +377,6 @@ final class SSLSessionImpl extends ExtendedSSLSession { // Get creation time 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 b = Record.getBytes8(buf); if (b.length == 0) { @@ -424,71 +386,89 @@ final class SSLSessionImpl extends ExtendedSSLSession { } this.port = Record.getInt16(buf); - // Peer certs - i = Record.getInt8(buf); - if (i == 0) { + // Peer certs. + len = Record.getInt8(buf); + if (len == 0) { this.peerCerts = null; } else { - this.peerCerts = new X509Certificate[i]; - int j = 0; - while (i > j) { + this.peerCerts = new X509Certificate[len]; + for (int i = 0; len > i; i++) { b = new byte[buf.getInt()]; buf.get(b); try { - this.peerCerts[j] = X509Factory.cachedGetX509Cert(b); + this.peerCerts[i] = X509Factory.cachedGetX509Cert(b); } catch (Exception e) { throw new IOException(e); } - j++; } } - // Get local certs of PSK - switch (Record.getInt8(buf)) { - 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); + // Restore local certificates if cert algorithm(s) present. + len = Record.getInt8(buf); + if (len == 0) { + this.localCerts = null; + } else { + String[] certAlgs = new String[len]; + int[] certCheckSums = new int[len]; + + for (int i = 0; len > i; i++) { + certAlgs[i] = new String(Record.getBytes8(buf)); + certCheckSums[i] = Record.getInt32(buf); + } + + 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 { - 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) { throw new IOException(e); } - i++; } - break; - case 2: - // pre-shared key - // Length of pre-shared key algorithm (one byte) - b = Record.getBytes8(buf); - String alg = new String(b); - // Get encoding - b = Record.getBytes16(buf); - 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; + } + + if (same) { + this.localCerts = ((X509Possession) pos).popCerts; + if (SSLLogger.isOn && SSLLogger.isOn("ssl,session")) { + SSLLogger.fine("Restored " + len + + " local certificates from session ticket" + + " for algorithms " + Arrays.toString(certAlgs)); } - break; - default: - throw new SSLException("Failed local certs of session."); + } else { + this.localCerts = null; + 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(); this.lastUsedTime = System.currentTimeMillis(); } - // Some situations we cannot provide a stateless ticket, but after it // has been negotiated boolean isStatelessable() { @@ -529,49 +509,25 @@ final class SSLSessionImpl extends ExtendedSSLSession { hos.putInt16(s.id); } - // Peer Supported signature algorithms - hos.putInt8(peerSupportedSignAlgs.size()); - for (SignatureScheme s : peerSupportedSignAlgs) { - 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()); + // PreSharedKey is only needed by TLSv1.3, + // masterSecret is only needed by pre-TLSv1.3. + if (protocolVersion.useTLS13PlusSpec()) { + // PSK + if (preSharedKey == null) { + hos.putInt16(0); + } else { + hos.putBytes16(preSharedKey.getEncoded()); } - 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()); + // Master Secret + if (getMasterSecret() == null) { + hos.putInt16(0); + } else { + hos.putBytes16(masterSecret.getEncoded()); } - 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 if (identificationProtocol == null) { @@ -605,20 +561,11 @@ final class SSLSessionImpl extends ExtendedSSLSession { hos.putInt32(maximumPacketSize); hos.putInt32(negotiatedMaxFragLen); - // creation time + // Creation time ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); hos.writeBytes(buffer.putLong(creationTime).array()); - // Status Responses - List list = getStatusResponses(); - int l = list.size(); - hos.putInt16(l); - for (byte[] e : list) { - hos.putInt16(e.length); - hos.write(e); - } - - // peer Host & Port + // Peer Host & Port if (host == null || host.length() == 0) { hos.putInt8(0); } else { @@ -627,7 +574,7 @@ final class SSLSessionImpl extends ExtendedSSLSession { } hos.putInt16(port); - // Peer cert + // Peer certs. if (peerCerts == null || peerCerts.length == 0) { hos.putInt8(0); } else { @@ -639,34 +586,28 @@ final class SSLSessionImpl extends ExtendedSSLSession { } } - // Client identity - if (localCerts != null && localCerts.length > 0) { - // certificate based - hos.putInt8(1); + // Local certificates' algorithms and checksums. + // We don't include the complete local certificates in a session ticket + // to decrease the size of ClientHello message. + if (localCerts == null || localCerts.length == 0) { + hos.putInt8(0); + } else { hos.putInt8(localCerts.length); for (X509Certificate c : localCerts) { - b = c.getEncoded(); - hos.putInt32(b.length); - hos.writeBytes(b); + hos.putBytes8(c.getPublicKey().getAlgorithm().getBytes()); + hos.putInt32(getChecksum(c.getEncoded())); } - } 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(); } + private static int getChecksum(byte[] input) { + Adler32 adler32 = new Adler32(); + adler32.update(input); + return (int) adler32.getValue(); + } + void setMasterSecret(SecretKey secret) { masterSecret = secret; } diff --git a/src/java.base/share/classes/sun/security/ssl/SessionTicketExtension.java b/src/java.base/share/classes/sun/security/ssl/SessionTicketExtension.java index 6cf930619f7..c0d2bea77ca 100644 --- a/src/java.base/share/classes/sun/security/ssl/SessionTicketExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/SessionTicketExtension.java @@ -25,12 +25,16 @@ package sun.security.ssl; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.text.MessageFormat; import java.util.Locale; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; @@ -70,6 +74,9 @@ final class SessionTicketExtension { new T12SHSessionTicketConsumer(); 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 private static final int TIMEOUT_DEFAULT = 3600 * 1000; @@ -196,7 +203,7 @@ final class SessionTicketExtension { data = buf; } - public byte[] encrypt(HandshakeContext hc, SSLSessionImpl session) { + byte[] encrypt(HandshakeContext hc, SSLSessionImpl session) { byte[] encrypted; if (!hc.statelessResumption || @@ -213,26 +220,34 @@ final class SessionTicketExtension { 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)} - ); + byte[] data = session.write(); if (data.length == 0) { 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); byte[] result = new byte[encrypted.length + Integer.BYTES + - iv.length]; + iv.length + 1]; 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); + result[Integer.BYTES + iv.length] = compressed; System.arraycopy(encrypted, 0, result, - Integer.BYTES + iv.length, encrypted.length); + Integer.BYTES + iv.length + 1, encrypted.length); return result; } catch (Exception e) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { @@ -257,26 +272,67 @@ final class SessionTicketExtension { 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)} - ); - ByteBuffer out; - out = ByteBuffer.allocate(data.remaining() - GCM_TAG_LEN / 8); + byte compressed = data.get(); + 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); out.flip(); + + // Decompress the session after decryption if needed. + if (compressed == 1) { + out = decompress(out); + } + return out; } catch (Exception e) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Decryption failed." + e.getMessage()); } } + 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[] out = new byte[data.capacity()]; data.duplicate().get(out); diff --git a/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksServer.java b/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksServer.java index 0d5413d1fa2..341dfb11d77 100644 --- a/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksServer.java +++ b/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksServer.java @@ -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. * * This code is free software; you can redistribute it and/or modify it @@ -26,6 +26,7 @@ * @bug 8206929 * @summary ensure that server only resumes a session if certain properties * of the session are compatible with the new connection + * @modules java.base/sun.security.x509 * @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=true -Djdk.tls.client.enableSessionTicketExtension=false ResumeChecksServer BASIC @@ -48,6 +49,7 @@ import java.io.*; import java.security.*; import java.net.*; import java.util.*; +import sun.security.x509.X509CertImpl; public class ResumeChecksServer extends SSLContextTemplate { @@ -57,7 +59,8 @@ public class ResumeChecksServer extends SSLContextTemplate { VERSION_2_TO_3, VERSION_3_TO_2, CIPHER_SUITE, - SIGNATURE_SCHEME + SIGNATURE_SCHEME, + LOCAL_CERTS } public static void main(String[] args) throws Exception { @@ -71,6 +74,7 @@ public class ResumeChecksServer extends SSLContextTemplate { } private void run() throws Exception { + SSLSession firstSession; SSLSession secondSession = null; SSLContext sslContext = createServerSSLContext(); @@ -81,7 +85,7 @@ public class ResumeChecksServer extends SSLContextTemplate { Client client = startClient(ssock.getLocalPort()); try { - connect(client, ssock, testMode, false); + firstSession = connect(client, ssock, testMode, null); } catch (Exception ex) { throw new RuntimeException(ex); } @@ -89,7 +93,7 @@ public class ResumeChecksServer extends SSLContextTemplate { long secondStartTime = System.currentTimeMillis(); Thread.sleep(10); try { - secondSession = connect(client, ssock, testMode, true); + secondSession = connect(client, ssock, testMode, firstSession); } catch (SSLHandshakeException ex) { // this is expected } catch (Exception ex) { @@ -105,6 +109,14 @@ public class ResumeChecksServer extends SSLContextTemplate { if (secondSession.getCreationTime() > secondStartTime) { 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; case CLIENT_AUTH: // throws an exception if the client is not authenticated @@ -114,6 +126,7 @@ public class ResumeChecksServer extends SSLContextTemplate { case VERSION_3_TO_2: case CIPHER_SUITE: case SIGNATURE_SCHEME: + case LOCAL_CERTS: // fail if a new session is not created if (secondSession.getCreationTime() <= secondStartTime) { throw new RuntimeException("Existing session was used"); @@ -153,7 +166,9 @@ public class ResumeChecksServer extends SSLContextTemplate { } 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 { client.signal(); @@ -200,9 +215,22 @@ public class ResumeChecksServer extends SSLContextTemplate { AlgorithmConstraints constraints = params.getAlgorithmConstraints(); if (second) { - params.setAlgorithmConstraints(new NoSig("ecdsa")); + params.setAlgorithmConstraints( + new NoSig("ecdsa_secp384r1_sha384")); } 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; default: diff --git a/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksServerStateless.java b/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksServerStateless.java index 8071bb1ced8..f68af24ea93 100644 --- a/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksServerStateless.java +++ b/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksServerStateless.java @@ -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. * * This code is free software; you can redistribute it and/or modify it @@ -23,9 +23,10 @@ /* * @test - * @bug 8211018 + * @bug 8211018 8357033 * @summary ensure that server only resumes a session if certain properties * of the session are compatible with the new connection + * @modules java.base/sun.security.x509 * @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 CLIENT_AUTH @@ -33,4 +34,5 @@ * @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.3 ResumeChecksServer LOCAL_CERTS */