diff --git a/src/java.base/share/classes/sun/security/ssl/ServerHello.java b/src/java.base/share/classes/sun/security/ssl/ServerHello.java index 412b65329d..812ec896d2 100644 --- a/src/java.base/share/classes/sun/security/ssl/ServerHello.java +++ b/src/java.base/share/classes/sun/security/ssl/ServerHello.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2024, 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 @@ -778,6 +778,15 @@ public byte[] produce(ConnectionContext context, hhrm.write(shc.handshakeOutput); shc.handshakeOutput.flush(); + // In TLS1.3 middlebox compatibility mode the server sends a + // dummy change_cipher_spec record immediately after its + // first handshake message. This may either be after + // a ServerHello or a HelloRetryRequest. + // (RFC 8446, Appendix D.4) + shc.conContext.outputRecord.changeWriteCiphers( + SSLWriteCipher.nullTlsWriteCipher(), + (clientHello.sessionId.length() != 0)); + // Stateless, shall we clean up the handshake context as well? shc.handshakeHash.finish(); // forgot about the handshake hash shc.handshakeExtensions.clear(); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlConfiguration.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlConfiguration.java index 99e6d0a41a..333721d570 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlConfiguration.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlConfiguration.java @@ -661,7 +661,7 @@ public boolean process(String opt, List args) { return true; } }, - new Option(resources, "--legal-notices", 1) { + new XOption(resources, "--legal-notices", 1) { @Override public boolean process(String opt, List args) { legalnotices = args.get(0); diff --git a/test/jdk/javax/net/ssl/TLSv13/EngineOutOfSeqCCS.java b/test/jdk/javax/net/ssl/TLSv13/EngineOutOfSeqCCS.java new file mode 100644 index 0000000000..94a2c0fe31 --- /dev/null +++ b/test/jdk/javax/net/ssl/TLSv13/EngineOutOfSeqCCS.java @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2024, 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 8326643 + * @summary Test for out-of-sequence change_cipher_spec in TLSv1.3 + * @library /javax/net/ssl/templates + * @run main/othervm EngineOutOfSeqCCS + */ + +import java.nio.ByteBuffer; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLParameters; + +public class EngineOutOfSeqCCS extends SSLEngineTemplate { + + /* + * Enables logging of the SSLEngine operations. + */ + private static final boolean logging = true; + private static final boolean dumpBufs = true; + + // Define a few basic TLS records we might need + private static final int TLS_RECTYPE_CCS = 0x14; + private static final int TLS_RECTYPE_ALERT = 0x15; + private static final int TLS_RECTYPE_HANDSHAKE = 0x16; + private static final int TLS_RECTYPE_APPDATA = 0x17; + + SSLEngineResult clientResult, serverResult; + + public EngineOutOfSeqCCS() throws Exception { + super(); + } + + public static void main(String[] args) throws Exception{ + new EngineOutOfSeqCCS().runDemo(); + } + + private void runDemo() throws Exception { + + // Client generates Client Hello + clientResult = clientEngine.wrap(clientOut, cTOs); + log("client wrap: ", clientResult); + runDelegatedTasks(clientEngine); + cTOs.flip(); + dumpByteBuffer("CLIENT-TO-SERVER", cTOs); + + // Server consumes Client Hello + serverResult = serverEngine.unwrap(cTOs, serverIn); + log("server unwrap: ", serverResult); + runDelegatedTasks(serverEngine); + cTOs.compact(); + + // Server generates ServerHello/HelloRetryRequest + serverResult = serverEngine.wrap(serverOut, sTOc); + log("server wrap: ", serverResult); + runDelegatedTasks(serverEngine); + sTOc.flip(); + + dumpByteBuffer("SERVER-TO-CLIENT", sTOc); + + // client consumes ServerHello/HelloRetryRequest + clientResult = clientEngine.unwrap(sTOc, clientIn); + log("client unwrap: ", clientResult); + runDelegatedTasks(clientEngine); + sTOc.compact(); + + // Server generates CCS + serverResult = serverEngine.wrap(serverOut, sTOc); + log("server wrap: ", serverResult); + runDelegatedTasks(serverEngine); + sTOc.flip(); + dumpByteBuffer("SERVER-TO-CLIENT", sTOc); + + if (isTlsMessage(sTOc, TLS_RECTYPE_CCS)) { + System.out.println("=========== CCS found ==========="); + } else { + // In TLS1.3 middlebox compatibility mode the server sends a + // dummy change_cipher_spec record immediately after its + // first handshake message. This may either be after + // a ServerHello or a HelloRetryRequest. + // (RFC 8446, Appendix D.4) + throw new SSLException( + "Server should generate change_cipher_spec record"); + } + clientEngine.closeOutbound(); + serverEngine.closeOutbound(); + } + + /** + * Look at an incoming TLS record and see if it is the desired + * record type, and where appropriate the correct subtype. + * + * @param srcRecord The input TLS record to be evaluated. This + * method will only look at the leading message if multiple + * TLS handshake messages are coalesced into a single record. + * @param reqRecType The requested TLS record type + * @param recParams Zero or more integer sub type fields. For CCS + * and ApplicationData, no params are used. For handshake records, + * one value corresponding to the HandshakeType is required. + * For Alerts, two values corresponding to AlertLevel and + * AlertDescription are necessary. + * + * @return true if the proper handshake message is the first one + * in the input record, false otherwise. + */ + private boolean isTlsMessage(ByteBuffer srcRecord, int reqRecType, + int... recParams) { + boolean foundMsg = false; + + if (srcRecord.hasRemaining()) { + srcRecord.mark(); + + // Grab the fields from the TLS Record + int recordType = Byte.toUnsignedInt(srcRecord.get()); + byte ver_major = srcRecord.get(); + byte ver_minor = srcRecord.get(); + + if (recordType == reqRecType) { + // For any zero-length recParams, making sure the requested + // type is sufficient. + if (recParams.length == 0) { + foundMsg = true; + } else { + switch (recordType) { + case TLS_RECTYPE_CCS: + case TLS_RECTYPE_APPDATA: + // We really shouldn't find ourselves here, but + // if someone asked for these types and had more + // recParams we can ignore them. + foundMsg = true; + break; + case TLS_RECTYPE_ALERT: + // Needs two params, AlertLevel and + //AlertDescription + if (recParams.length != 2) { + throw new RuntimeException( + "Test for Alert requires level and desc."); + } else { + int level = Byte.toUnsignedInt( + srcRecord.get()); + int desc = Byte.toUnsignedInt(srcRecord.get()); + if (level == recParams[0] && + desc == recParams[1]) { + foundMsg = true; + } + } + break; + case TLS_RECTYPE_HANDSHAKE: + // Needs one parameter, HandshakeType + if (recParams.length != 1) { + throw new RuntimeException( + "Test for Handshake requires only HS type"); + } else { + // Go into the first handshake message in the + // record and grab the handshake message header. + // All we need to do is parse out the leading + // byte. + int msgHdr = srcRecord.getInt(); + int msgType = (msgHdr >> 24) & 0x000000FF; + if (msgType == recParams[0]) { + foundMsg = true; + } + } + break; + } + } + } + + srcRecord.reset(); + } + + return foundMsg; + } + + private static String tlsRecType(int type) { + switch (type) { + case 20: + return "Change Cipher Spec"; + case 21: + return "Alert"; + case 22: + return "Handshake"; + case 23: + return "Application Data"; + default: + return ("Unknown (" + type + ")"); + } + } + + /* + * 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); + } + } + + /** + * Hex-dumps a ByteBuffer to stdout. + */ + private static void dumpByteBuffer(String header, ByteBuffer bBuf) { + if (!dumpBufs) { + return; + } + + int bufLen = bBuf.remaining(); + if (bufLen > 0) { + bBuf.mark(); + + // We expect the position of the buffer to be at the + // beginning of a TLS record. Get the type, version and length. + int type = Byte.toUnsignedInt(bBuf.get()); + int ver_major = Byte.toUnsignedInt(bBuf.get()); + int ver_minor = Byte.toUnsignedInt(bBuf.get()); + + log("===== " + header + " (" + tlsRecType(type) + " / " + + ver_major + "." + ver_minor + " / " + + bufLen + " bytes) ====="); + bBuf.reset(); + for (int i = 0; i < bufLen; i++) { + if (i != 0 && i % 16 == 0) { + System.out.print("\n"); + } + System.out.format("%02X ", bBuf.get(i)); + } + log("\n==============================================="); + bBuf.reset(); + } + } +} diff --git a/test/jdk/javax/net/ssl/templates/SSLEngineTemplate.java b/test/jdk/javax/net/ssl/templates/SSLEngineTemplate.java index 24ff8e0844..48a7c19df7 100644 --- a/test/jdk/javax/net/ssl/templates/SSLEngineTemplate.java +++ b/test/jdk/javax/net/ssl/templates/SSLEngineTemplate.java @@ -52,21 +52,21 @@ * produced. */ public class SSLEngineTemplate implements SSLContextTemplate { - private final SSLEngine clientEngine; // client Engine - private final ByteBuffer clientOut; // write side of clientEngine - private final ByteBuffer clientIn; // read side of clientEngine + protected final SSLEngine clientEngine; // client Engine + protected final ByteBuffer clientOut; // write side of clientEngine + protected final ByteBuffer clientIn; // read side of clientEngine - private final SSLEngine serverEngine; // server Engine - private final ByteBuffer serverOut; // write side of serverEngine - private final ByteBuffer serverIn; // read side of serverEngine + protected final SSLEngine serverEngine; // server Engine + protected final ByteBuffer serverOut; // write side of serverEngine + protected final ByteBuffer serverIn; // read side of serverEngine // For data transport, this example uses local ByteBuffers. This // isn't really useful, but the purpose of this example is to show // SSLEngine concepts, not how to do network transport. - private final ByteBuffer cTOs; // "reliable" transport client->server - private final ByteBuffer sTOc; // "reliable" transport server->client + protected final ByteBuffer cTOs; // "reliable" transport client->server + protected final ByteBuffer sTOc; // "reliable" transport server->client - private SSLEngineTemplate() throws Exception { + protected SSLEngineTemplate() throws Exception { serverEngine = configureServerEngine( createServerSSLContext().createSSLEngine()); @@ -223,7 +223,7 @@ private static void log(String message) { // If the result indicates that we have outstanding tasks to do, // go ahead and run them in this thread. - private static void runDelegatedTasks(SSLEngine engine) throws Exception { + protected static void runDelegatedTasks(SSLEngine engine) throws Exception { if (engine.getHandshakeStatus() == HandshakeStatus.NEED_TASK) { Runnable runnable; while ((runnable = engine.getDelegatedTask()) != null) {