8326643: JDK server does not send a dummy change_cipher_spec record after HelloRetryRequest message

Reviewed-by: djelinski, coffeys, jjiang, ascarpino
This commit is contained in:
Prasadrao Koppula 2024-03-22 07:02:37 +00:00 committed by Sean Coffey
parent 9bc741d04f
commit d44aaa37f9
2 changed files with 294 additions and 1 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2022, 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
@ -794,6 +794,15 @@ final class ServerHello {
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();

View File

@ -0,0 +1,284 @@
/*
* 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 isHRRTest
* @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(args.length > 0 &&
args[0].equals("isHRRTest"));
}
private void runDemo(boolean isHRRTest) throws Exception {
if (isHRRTest) {
SSLParameters sslParams = new SSLParameters();
sslParams.setNamedGroups(new String[] {"secp384r1"});
serverEngine.setSSLParameters(sslParams);
}
// 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();
}
}
}