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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -794,6 +794,15 @@ final class ServerHello {
|
|||||||
hhrm.write(shc.handshakeOutput);
|
hhrm.write(shc.handshakeOutput);
|
||||||
shc.handshakeOutput.flush();
|
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?
|
// Stateless, shall we clean up the handshake context as well?
|
||||||
shc.handshakeHash.finish(); // forgot about the handshake hash
|
shc.handshakeHash.finish(); // forgot about the handshake hash
|
||||||
shc.handshakeExtensions.clear();
|
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