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:
parent
9bc741d04f
commit
d44aaa37f9
@ -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();
|
||||
|
284
test/jdk/javax/net/ssl/TLSv13/EngineOutOfSeqCCS.java
Normal file
284
test/jdk/javax/net/ssl/TLSv13/EngineOutOfSeqCCS.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user