Skip to content

Commit 8334890

Browse files
Prasadrao Koppulapull[bot]
Prasadrao Koppula
authored andcommitted
8326643: JDK server does not send a dummy change_cipher_spec record after HelloRetryRequest message
Reviewed-by: djelinski, coffeys, jjiang, ascarpino
1 parent 40d9c79 commit 8334890

File tree

2 files changed

+294
-1
lines changed

2 files changed

+294
-1
lines changed

src/java.base/share/classes/sun/security/ssl/ServerHello.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -794,6 +794,15 @@ public byte[] produce(ConnectionContext context,
794794
hhrm.write(shc.handshakeOutput);
795795
shc.handshakeOutput.flush();
796796

797+
// In TLS1.3 middlebox compatibility mode the server sends a
798+
// dummy change_cipher_spec record immediately after its
799+
// first handshake message. This may either be after
800+
// a ServerHello or a HelloRetryRequest.
801+
// (RFC 8446, Appendix D.4)
802+
shc.conContext.outputRecord.changeWriteCiphers(
803+
SSLWriteCipher.nullTlsWriteCipher(),
804+
(clientHello.sessionId.length() != 0));
805+
797806
// Stateless, shall we clean up the handshake context as well?
798807
shc.handshakeHash.finish(); // forgot about the handshake hash
799808
shc.handshakeExtensions.clear();
Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
/*
2+
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
* @test
26+
* @bug 8326643
27+
* @summary Test for out-of-sequence change_cipher_spec in TLSv1.3
28+
* @library /javax/net/ssl/templates
29+
* @run main/othervm EngineOutOfSeqCCS isHRRTest
30+
* @run main/othervm EngineOutOfSeqCCS
31+
*/
32+
33+
import java.nio.ByteBuffer;
34+
import javax.net.ssl.SSLEngineResult;
35+
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
36+
import javax.net.ssl.SSLException;
37+
import javax.net.ssl.SSLParameters;
38+
39+
public class EngineOutOfSeqCCS extends SSLEngineTemplate {
40+
41+
/*
42+
* Enables logging of the SSLEngine operations.
43+
*/
44+
private static final boolean logging = true;
45+
private static final boolean dumpBufs = true;
46+
47+
// Define a few basic TLS records we might need
48+
private static final int TLS_RECTYPE_CCS = 0x14;
49+
private static final int TLS_RECTYPE_ALERT = 0x15;
50+
private static final int TLS_RECTYPE_HANDSHAKE = 0x16;
51+
private static final int TLS_RECTYPE_APPDATA = 0x17;
52+
53+
SSLEngineResult clientResult, serverResult;
54+
55+
public EngineOutOfSeqCCS() throws Exception {
56+
super();
57+
}
58+
59+
public static void main(String[] args) throws Exception{
60+
new EngineOutOfSeqCCS().runDemo(args.length > 0 &&
61+
args[0].equals("isHRRTest"));
62+
}
63+
64+
private void runDemo(boolean isHRRTest) throws Exception {
65+
66+
if (isHRRTest) {
67+
SSLParameters sslParams = new SSLParameters();
68+
sslParams.setNamedGroups(new String[] {"secp384r1"});
69+
serverEngine.setSSLParameters(sslParams);
70+
}
71+
// Client generates Client Hello
72+
clientResult = clientEngine.wrap(clientOut, cTOs);
73+
log("client wrap: ", clientResult);
74+
runDelegatedTasks(clientEngine);
75+
cTOs.flip();
76+
dumpByteBuffer("CLIENT-TO-SERVER", cTOs);
77+
78+
// Server consumes Client Hello
79+
serverResult = serverEngine.unwrap(cTOs, serverIn);
80+
log("server unwrap: ", serverResult);
81+
runDelegatedTasks(serverEngine);
82+
cTOs.compact();
83+
84+
// Server generates ServerHello/HelloRetryRequest
85+
serverResult = serverEngine.wrap(serverOut, sTOc);
86+
log("server wrap: ", serverResult);
87+
runDelegatedTasks(serverEngine);
88+
sTOc.flip();
89+
90+
dumpByteBuffer("SERVER-TO-CLIENT", sTOc);
91+
92+
// client consumes ServerHello/HelloRetryRequest
93+
clientResult = clientEngine.unwrap(sTOc, clientIn);
94+
log("client unwrap: ", clientResult);
95+
runDelegatedTasks(clientEngine);
96+
sTOc.compact();
97+
98+
// Server generates CCS
99+
serverResult = serverEngine.wrap(serverOut, sTOc);
100+
log("server wrap: ", serverResult);
101+
runDelegatedTasks(serverEngine);
102+
sTOc.flip();
103+
dumpByteBuffer("SERVER-TO-CLIENT", sTOc);
104+
105+
if (isTlsMessage(sTOc, TLS_RECTYPE_CCS)) {
106+
System.out.println("=========== CCS found ===========");
107+
} else {
108+
// In TLS1.3 middlebox compatibility mode the server sends a
109+
// dummy change_cipher_spec record immediately after its
110+
// first handshake message. This may either be after
111+
// a ServerHello or a HelloRetryRequest.
112+
// (RFC 8446, Appendix D.4)
113+
throw new SSLException(
114+
"Server should generate change_cipher_spec record");
115+
}
116+
clientEngine.closeOutbound();
117+
serverEngine.closeOutbound();
118+
}
119+
120+
/**
121+
* Look at an incoming TLS record and see if it is the desired
122+
* record type, and where appropriate the correct subtype.
123+
*
124+
* @param srcRecord The input TLS record to be evaluated. This
125+
* method will only look at the leading message if multiple
126+
* TLS handshake messages are coalesced into a single record.
127+
* @param reqRecType The requested TLS record type
128+
* @param recParams Zero or more integer sub type fields. For CCS
129+
* and ApplicationData, no params are used. For handshake records,
130+
* one value corresponding to the HandshakeType is required.
131+
* For Alerts, two values corresponding to AlertLevel and
132+
* AlertDescription are necessary.
133+
*
134+
* @return true if the proper handshake message is the first one
135+
* in the input record, false otherwise.
136+
*/
137+
private boolean isTlsMessage(ByteBuffer srcRecord, int reqRecType,
138+
int... recParams) {
139+
boolean foundMsg = false;
140+
141+
if (srcRecord.hasRemaining()) {
142+
srcRecord.mark();
143+
144+
// Grab the fields from the TLS Record
145+
int recordType = Byte.toUnsignedInt(srcRecord.get());
146+
byte ver_major = srcRecord.get();
147+
byte ver_minor = srcRecord.get();
148+
149+
if (recordType == reqRecType) {
150+
// For any zero-length recParams, making sure the requested
151+
// type is sufficient.
152+
if (recParams.length == 0) {
153+
foundMsg = true;
154+
} else {
155+
switch (recordType) {
156+
case TLS_RECTYPE_CCS:
157+
case TLS_RECTYPE_APPDATA:
158+
// We really shouldn't find ourselves here, but
159+
// if someone asked for these types and had more
160+
// recParams we can ignore them.
161+
foundMsg = true;
162+
break;
163+
case TLS_RECTYPE_ALERT:
164+
// Needs two params, AlertLevel and
165+
//AlertDescription
166+
if (recParams.length != 2) {
167+
throw new RuntimeException(
168+
"Test for Alert requires level and desc.");
169+
} else {
170+
int level = Byte.toUnsignedInt(
171+
srcRecord.get());
172+
int desc = Byte.toUnsignedInt(srcRecord.get());
173+
if (level == recParams[0] &&
174+
desc == recParams[1]) {
175+
foundMsg = true;
176+
}
177+
}
178+
break;
179+
case TLS_RECTYPE_HANDSHAKE:
180+
// Needs one parameter, HandshakeType
181+
if (recParams.length != 1) {
182+
throw new RuntimeException(
183+
"Test for Handshake requires only HS type");
184+
} else {
185+
// Go into the first handshake message in the
186+
// record and grab the handshake message header.
187+
// All we need to do is parse out the leading
188+
// byte.
189+
int msgHdr = srcRecord.getInt();
190+
int msgType = (msgHdr >> 24) & 0x000000FF;
191+
if (msgType == recParams[0]) {
192+
foundMsg = true;
193+
}
194+
}
195+
break;
196+
}
197+
}
198+
}
199+
200+
srcRecord.reset();
201+
}
202+
203+
return foundMsg;
204+
}
205+
206+
private static String tlsRecType(int type) {
207+
switch (type) {
208+
case 20:
209+
return "Change Cipher Spec";
210+
case 21:
211+
return "Alert";
212+
case 22:
213+
return "Handshake";
214+
case 23:
215+
return "Application Data";
216+
default:
217+
return ("Unknown (" + type + ")");
218+
}
219+
}
220+
221+
/*
222+
* Logging code
223+
*/
224+
private static boolean resultOnce = true;
225+
226+
private static void log(String str, SSLEngineResult result) {
227+
if (!logging) {
228+
return;
229+
}
230+
if (resultOnce) {
231+
resultOnce = false;
232+
System.out.println("The format of the SSLEngineResult is: \n" +
233+
"\t\"getStatus() / getHandshakeStatus()\" +\n" +
234+
"\t\"bytesConsumed() / bytesProduced()\"\n");
235+
}
236+
HandshakeStatus hsStatus = result.getHandshakeStatus();
237+
log(str +
238+
result.getStatus() + "/" + hsStatus + ", " +
239+
result.bytesConsumed() + "/" + result.bytesProduced() +
240+
" bytes");
241+
if (hsStatus == HandshakeStatus.FINISHED) {
242+
log("\t...ready for application data");
243+
}
244+
}
245+
246+
private static void log(String str) {
247+
if (logging) {
248+
System.out.println(str);
249+
}
250+
}
251+
252+
/**
253+
* Hex-dumps a ByteBuffer to stdout.
254+
*/
255+
private static void dumpByteBuffer(String header, ByteBuffer bBuf) {
256+
if (!dumpBufs) {
257+
return;
258+
}
259+
260+
int bufLen = bBuf.remaining();
261+
if (bufLen > 0) {
262+
bBuf.mark();
263+
264+
// We expect the position of the buffer to be at the
265+
// beginning of a TLS record. Get the type, version and length.
266+
int type = Byte.toUnsignedInt(bBuf.get());
267+
int ver_major = Byte.toUnsignedInt(bBuf.get());
268+
int ver_minor = Byte.toUnsignedInt(bBuf.get());
269+
270+
log("===== " + header + " (" + tlsRecType(type) + " / " +
271+
ver_major + "." + ver_minor + " / " +
272+
bufLen + " bytes) =====");
273+
bBuf.reset();
274+
for (int i = 0; i < bufLen; i++) {
275+
if (i != 0 && i % 16 == 0) {
276+
System.out.print("\n");
277+
}
278+
System.out.format("%02X ", bBuf.get(i));
279+
}
280+
log("\n===============================================");
281+
bBuf.reset();
282+
}
283+
}
284+
}

0 commit comments

Comments
 (0)