Skip to content

Commit

Permalink
8326643: JDK server does not send a dummy change_cipher_spec record a…
Browse files Browse the repository at this point in the history
…fter HelloRetryRequest message

Reviewed-by: phh, mbaesken
Backport-of: cacc30b77cab0173ea14601094a5b5b3106ce2f5
  • Loading branch information
TheRealMDoerr committed May 31, 2024
1 parent 8226f44 commit 5599af4
Show file tree
Hide file tree
Showing 3 changed files with 297 additions and 11 deletions.
11 changes: 10 additions & 1 deletion src/java.base/share/classes/sun/security/ssl/ServerHello.java
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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();
Expand Down
277 changes: 277 additions & 0 deletions test/jdk/javax/net/ssl/TLSv13/EngineOutOfSeqCCS.java
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
20 changes: 10 additions & 10 deletions test/jdk/javax/net/ssl/templates/SSLEngineTemplate.java
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit 5599af4

Please sign in to comment.