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 2e7d99ee293a9..f5482391186ec 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java @@ -30,6 +30,7 @@ import java.math.BigInteger; 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; @@ -311,113 +312,90 @@ final class SSLSessionImpl extends ExtendedSSLSession { SSLSessionImpl(HandshakeContext hc, ByteBuffer buf) throws IOException { boundValues = new ConcurrentHashMap<>(); this.protocolVersion = - ProtocolVersion.valueOf(Short.toUnsignedInt(buf.getShort())); + ProtocolVersion.valueOf(Record.getInt16(buf)); // The CH session id may reset this if it's provided this.sessionId = new SessionId(true, hc.sslContext.getSecureRandom()); this.cipherSuite = - CipherSuite.valueOf(Short.toUnsignedInt(buf.getShort())); + CipherSuite.valueOf(Record.getInt16(buf)); // Local Supported signature algorithms ArrayList list = new ArrayList<>(); - int i = Byte.toUnsignedInt(buf.get()); + int i = Record.getInt8(buf); while (i-- > 0) { list.add(SignatureScheme.valueOf( - Short.toUnsignedInt(buf.getShort()))); + Record.getInt16(buf))); } this.localSupportedSignAlgs = Collections.unmodifiableCollection(list); // Peer Supported signature algorithms - i = Byte.toUnsignedInt(buf.get()); + i = Record.getInt8(buf); list.clear(); while (i-- > 0) { list.add(SignatureScheme.valueOf( - Short.toUnsignedInt(buf.getShort()))); + Record.getInt16(buf))); } this.peerSupportedSignAlgs = Collections.unmodifiableCollection(list); // PSK - byte[] b; - i = Short.toUnsignedInt(buf.getShort()); - if (i > 0) { - b = new byte[i]; - // Get algorithm string - buf.get(b, 0, i); - // Encoded length - i = Short.toUnsignedInt(buf.getShort()); - // Encoded SecretKey - b = new byte[i]; - buf.get(b); + byte[] b = Record.getBytes16(buf); + if (b.length > 0) { + b = Record.getBytes16(buf); this.preSharedKey = new SecretKeySpec(b, "TlsMasterSecret"); } else { this.preSharedKey = null; } // PSK identity - i = buf.get(); - if (i > 0) { - b = new byte[i]; - buf.get(b); + b = Record.getBytes8(buf); + if (b.length > 0) { this.pskIdentity = b; } else { this.pskIdentity = null; } // Master secret length of secret key algorithm (one byte) - i = buf.get(); - if (i > 0) { - b = new byte[i]; - // Get algorithm string - buf.get(b, 0, i); - // Encoded length - i = Short.toUnsignedInt(buf.getShort()); - // Encoded SecretKey - b = new byte[i]; - buf.get(b); + b = Record.getBytes8(buf); + if (b.length > 0) { + b = Record.getBytes16(buf); this.masterSecret = new SecretKeySpec(b, "TlsMasterSecret"); } else { this.masterSecret = null; } + // Use extended master secret - this.useExtendedMasterSecret = (buf.get() != 0); + this.useExtendedMasterSecret = (Record.getInt8(buf) != 0); // Identification Protocol - i = buf.get(); - if (i == 0) { + b = Record.getBytes8(buf); + if (b.length == 0) { identificationProtocol = null; } else { - b = new byte[i]; - buf.get(b); identificationProtocol = new String(b); } // SNI - i = buf.get(); // length - if (i == 0) { + b = Record.getBytes8(buf); + if (b.length == 0) { serverNameIndication = null; } else { - b = new byte[i]; - buf.get(b, 0, b.length); serverNameIndication = new SNIHostName(b); } // List of SNIServerName - int len = Short.toUnsignedInt(buf.getShort()); + int len = Record.getInt16(buf); if (len == 0) { this.requestedServerNames = Collections.emptyList(); } else { requestedServerNames = new ArrayList<>(); while (len > 0) { - int l = buf.get(); - b = new byte[l]; - buf.get(b, 0, l); + b = Record.getBytes8(buf); requestedServerNames.add(new SNIHostName(new String(b))); len--; } } - maximumPacketSize = buf.getInt(); negotiatedMaxFragLen = buf.getInt(); @@ -427,31 +405,28 @@ final class SSLSessionImpl extends ExtendedSSLSession { // Get Buffer sizes // Status Response - len = Short.toUnsignedInt(buf.getShort()); + len = Record.getInt16(buf); if (len == 0) { statusResponses = Collections.emptyList(); } else { statusResponses = new ArrayList<>(); } while (len-- > 0) { - b = new byte[Short.toUnsignedInt(buf.getShort())]; - buf.get(b); + b = Record.getBytes16(buf); statusResponses.add(b); } // Get Peer host & port - i = Byte.toUnsignedInt(buf.get()); - if (i == 0) { + b = Record.getBytes8(buf); + if (b.length == 0) { this.host = ""; } else { - b = new byte[i]; - buf.get(b, 0, i); this.host = new String(b); } - this.port = Short.toUnsignedInt(buf.getShort()); + this.port = Record.getInt16(buf); // Peer certs - i = buf.get(); + i = Record.getInt8(buf); if (i == 0) { this.peerCerts = null; } else { @@ -470,7 +445,7 @@ final class SSLSessionImpl extends ExtendedSSLSession { } // Get local certs of PSK - switch (buf.get()) { + switch (Record.getInt8(buf)) { case 0: break; case 1: @@ -492,21 +467,15 @@ final class SSLSessionImpl extends ExtendedSSLSession { case 2: // pre-shared key // Length of pre-shared key algorithm (one byte) - i = buf.get(); - b = new byte[i]; - buf.get(b, 0, i); + b = Record.getBytes8(buf); String alg = new String(b); - // Get length of encoding - i = Short.toUnsignedInt(buf.getShort()); // Get encoding - b = new byte[i]; - buf.get(b); + b = Record.getBytes16(buf); this.preSharedKey = new SecretKeySpec(b, alg); // Get identity len - i = buf.get(); + i = Record.getInt8(buf); if (i > 0) { - this.pskIdentity = new byte[buf.get()]; - buf.get(pskIdentity); + this.pskIdentity = Record.getBytes8(buf); } else { this.pskIdentity = null; } @@ -520,6 +489,7 @@ final class SSLSessionImpl extends ExtendedSSLSession { this.lastUsedTime = System.currentTimeMillis(); } + // Some situations we cannot provide a stateless ticket, but after it // has been negotiated boolean isStatelessable() { diff --git a/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeClientTLS12withSNI.java b/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeClientTLS12withSNI.java new file mode 100644 index 0000000000000..b3c66eb9bcdd6 --- /dev/null +++ b/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeClientTLS12withSNI.java @@ -0,0 +1,367 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8350830 + * @summary TLS 1.2 Client session resumption having ServerNameIndication + * @modules java.base/sun.security.tools.keytool + * @run main/othervm -Djavax.net.debug=all ResumeClientTLS12withSNI + */ + +import javax.net.ssl.*; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.util.*; + +public class ResumeClientTLS12withSNI { + + /* + * Enables logging of the SSLEngine operations. + */ + private static final boolean logging = true; + + private static SSLContext sslc; + + private SSLEngine clientEngine; // client Engine + private ByteBuffer clientOut; // write side of clientEngine + private ByteBuffer clientIn; // read side of clientEngine + + private SSLEngine serverEngine; // server Engine + private ByteBuffer serverOut; // write side of serverEngine + private ByteBuffer serverIn; // read side of serverEngine + + /* + * For data transport, this example uses local ByteBuffers. + */ + private ByteBuffer cTOs; // "reliable" transport client->server + private ByteBuffer sTOc; // "reliable" transport server->client + + /* + * The following is to set up the keystores. + */ + private static final String pathToStores = System.getProperty("test.src", "."); + private static final String keyStoreFile = "ks_san.p12"; + private static final String trustStoreFile = "ks_san.p12"; + private static final char[] passphrase = "123456".toCharArray(); + + private static final String keyFilename = + pathToStores + "/" + keyStoreFile; + private static final String trustFilename = + pathToStores + "/" + trustStoreFile; + + private static final String HOST_NAME = "arf.yak.foo.localhost123456.localhost123456.localhost123456.localhost123456.localhost123456.localhost123456." + + "localhost123456.localhost123456.localhost123456.localhost123456.localhost123456.localhost123456"; + private static final SNIMatcher SNI_MATCHER = SNIHostName.createSNIMatcher("arf\\.yak\\.foo.*"); + + /* + * Main entry point for this test. + */ + public static void main(String args[]) throws Exception { + Files.deleteIfExists(Path.of(keyFilename)); + + sun.security.tools.keytool.Main.main( + ("-keystore " + keyFilename + " -storepass 123456 -keypass 123456 -dname" + + " CN=test" + " -alias ks_san -genkeypair -keyalg rsa -ext " + + "san=dns:localhost123.localhost123.localhost123.localhost123." + + "localhost123.localhost123.localhost123.localhost123.localhost123." + + "localhost123.localhost123.localhost123.localhost123.localhost123." + + "localhost123.localhost123.localhost123.localhost123.localhost123.com," + + "dns:localhost456").split(" ")); + final ResumeClientTLS12withSNI clientSession = new ResumeClientTLS12withSNI("TLSv1.2"); + for (int i = 0; i < 2; i++) { + clientSession.runTest(); + } + + Files.deleteIfExists(Path.of(keyFilename)); + } + + public ResumeClientTLS12withSNI(final String sslProtocol) throws Exception { + + KeyManagerFactory kmf = makeKeyManagerFactory(keyFilename, + passphrase); + TrustManagerFactory tmf = makeTrustManagerFactory(trustFilename, + passphrase); + + SSLContext sslCtx = SSLContext.getInstance(sslProtocol); + + sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + + sslc = sslCtx; + } + + private static KeyManagerFactory makeKeyManagerFactory(String ksPath, + char[] pass) throws GeneralSecurityException, IOException { + KeyManagerFactory kmf; + KeyStore ks = KeyStore.getInstance("JKS"); + + try (FileInputStream fsIn = new FileInputStream(ksPath)) { + ks.load(fsIn, pass); + kmf = KeyManagerFactory.getInstance("SunX509"); + kmf.init(ks, pass); + } + return kmf; + } + + private static TrustManagerFactory makeTrustManagerFactory(String tsPath, + char[] pass) throws GeneralSecurityException, IOException { + TrustManagerFactory tmf; + KeyStore ts = KeyStore.getInstance("JKS"); + + try (FileInputStream fsIn = new FileInputStream(tsPath)) { + ts.load(fsIn, pass); + tmf = TrustManagerFactory.getInstance("SunX509"); + tmf.init(ts); + } + return tmf; + } + + /* + * Run the test. + * + * Sit in a tight loop, both engines calling wrap/unwrap regardless + * of whether data is available or not. We do this until both engines + * report back they are closed. + * + * The main loop handles all of the I/O phases of the SSLEngine's + * lifetime: + * + * initial handshaking + * application data transfer + * engine closing + * + */ + private void runTest() throws Exception { + boolean dataDone = false; + + createSSLEngines(); + createBuffers(); + + SSLEngineResult clientResult; // results from client's last operation + SSLEngineResult serverResult; // results from server's last operation + + /* + * Examining the SSLEngineResults could be much more involved, + * and may alter the overall flow of the application. + * + * For example, if we received a BUFFER_OVERFLOW when trying + * to write to the output pipe, we could reallocate a larger + * pipe, but instead we wait for the peer to drain it. + */ + while (!isEngineClosed(clientEngine) || + !isEngineClosed(serverEngine)) { + + log("================"); + + clientResult = clientEngine.wrap(clientOut, cTOs); + log("client wrap: ", clientResult); + runDelegatedTasks(clientResult, clientEngine); + + serverResult = serverEngine.wrap(serverOut, sTOc); + log("server wrap: ", serverResult); + runDelegatedTasks(serverResult, serverEngine); + + cTOs.flip(); + sTOc.flip(); + + log("-------"); + + clientResult = clientEngine.unwrap(sTOc, clientIn); + log("client unwrap: ", clientResult); + runDelegatedTasks(clientResult, clientEngine); + + serverResult = serverEngine.unwrap(cTOs, serverIn); + log("server unwrap: ", serverResult); + runDelegatedTasks(serverResult, serverEngine); + + cTOs.compact(); + sTOc.compact(); + + /* + * After we've transfered all application data between the client + * and server, we close the clientEngine's outbound stream. + * This generates a close_notify handshake message, which the + * server engine receives and responds by closing itself. + * + * In normal operation, each SSLEngine should call + * closeOutbound(). To protect against truncation attacks, + * SSLEngine.closeInbound() should be called whenever it has + * determined that no more input data will ever be + * available (say a closed input stream). + */ + if (!dataDone && (clientOut.limit() == serverIn.position()) && + (serverOut.limit() == clientIn.position())) { + + /* + * A sanity check to ensure we got what was sent. + */ + checkTransfer(serverOut, clientIn); + checkTransfer(clientOut, serverIn); + + log("\tClosing clientEngine's *OUTBOUND*..."); + clientEngine.closeOutbound(); + // serverEngine.closeOutbound(); + dataDone = true; + } + } + } + + /* + * Using the SSLContext created during object creation, + * create/configure the SSLEngines we'll use for this test. + */ + private void createSSLEngines() throws Exception { + /* + * Configure the serverEngine to act as a server in the SSL/TLS + * handshake. + */ + serverEngine = sslc.createSSLEngine(); + serverEngine.setUseClientMode(false); + serverEngine.setNeedClientAuth(false); + SSLParameters servSSLParams = serverEngine.getSSLParameters(); + servSSLParams.setSNIMatchers(List.of(SNI_MATCHER)); + serverEngine.setSSLParameters(servSSLParams); + + /* + * Similar to above, but using client mode instead. + */ + clientEngine = sslc.createSSLEngine(HOST_NAME, 80); + clientEngine.setUseClientMode(true); + SSLParameters cliSSLParams = clientEngine.getSSLParameters(); + clientEngine.setSSLParameters(cliSSLParams); + } + + /* + * Create and size the buffers appropriately. + */ + private void createBuffers() { + + /* + * We'll assume the buffer sizes are the same + * between client and server. + */ + SSLSession session = clientEngine.getSession(); + int appBufferMax = session.getApplicationBufferSize(); + int netBufferMax = session.getPacketBufferSize(); + + /* + * We'll make the input buffers a bit bigger than the max needed + * size, so that unwrap()s following a successful data transfer + * won't generate BUFFER_OVERFLOWS. + * + * We'll use a mix of direct and indirect ByteBuffers for + * tutorial purposes only. In reality, only use direct + * ByteBuffers when they give a clear performance enhancement. + */ + clientIn = ByteBuffer.allocate(appBufferMax + 50); + serverIn = ByteBuffer.allocate(appBufferMax + 50); + + cTOs = ByteBuffer.allocateDirect(netBufferMax); + sTOc = ByteBuffer.allocateDirect(netBufferMax); + + clientOut = ByteBuffer.wrap("Hi Server, I'm Client".getBytes()); + serverOut = ByteBuffer.wrap("Hello Client, I'm Server".getBytes()); + } + + /* + * If the result indicates that we have outstanding tasks to do, + * go ahead and run them in this thread. + */ + private static void runDelegatedTasks(SSLEngineResult result, + SSLEngine engine) throws Exception { + + if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) { + Runnable runnable; + while ((runnable = engine.getDelegatedTask()) != null) { + log("\trunning delegated task..."); + runnable.run(); + } + HandshakeStatus hsStatus = engine.getHandshakeStatus(); + if (hsStatus == HandshakeStatus.NEED_TASK) { + throw new Exception( + "handshake shouldn't need additional tasks"); + } + log("\tnew HandshakeStatus: " + hsStatus); + } + } + + private static boolean isEngineClosed(SSLEngine engine) { + return (engine.isOutboundDone() && engine.isInboundDone()); + } + + /* + * Simple check to make sure everything came across as expected. + */ + private static void checkTransfer(ByteBuffer a, ByteBuffer b) + throws Exception { + a.flip(); + b.flip(); + + if (!a.equals(b)) { + throw new Exception("Data didn't transfer cleanly"); + } else { + log("\tData transferred cleanly"); + } + + a.position(a.limit()); + b.position(b.limit()); + a.limit(a.capacity()); + b.limit(b.capacity()); + } + + /* + * Logging code + */ + private static boolean resultOnce = true; + + private static void log(String str, SSLEngineResult result) { + if (!logging) { + return; + } + if (resultOnce) { + resultOnce = false; + System.out.println("The format of the SSLEngineResult is: \n" + + "\t\"getStatus() / getHandshakeStatus()\" +\n" + + "\t\"bytesConsumed() / bytesProduced()\"\n"); + } + HandshakeStatus hsStatus = result.getHandshakeStatus(); + log(str + + result.getStatus() + "/" + hsStatus + ", " + + result.bytesConsumed() + "/" + result.bytesProduced() + + " bytes"); + if (hsStatus == HandshakeStatus.FINISHED) { + log("\t...ready for application data"); + } + } + + private static void log(String str) { + if (logging) { + System.out.println(str); + } + } +}