Skip to content

Commit 15c9397

Browse files
committedMay 31, 2024
8326643: JDK server does not send a dummy change_cipher_spec record after HelloRetryRequest message
Reviewed-by: phh, mbaesken Backport-of: cacc30b77cab0173ea14601094a5b5b3106ce2f5
1 parent 47fd86c commit 15c9397

File tree

3 files changed

+297
-11
lines changed

3 files changed

+297
-11
lines changed
 

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

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2015, 2019, 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
@@ -778,6 +778,15 @@ public byte[] produce(ConnectionContext context,
778778
hhrm.write(shc.handshakeOutput);
779779
shc.handshakeOutput.flush();
780780

781+
// In TLS1.3 middlebox compatibility mode the server sends a
782+
// dummy change_cipher_spec record immediately after its
783+
// first handshake message. This may either be after
784+
// a ServerHello or a HelloRetryRequest.
785+
// (RFC 8446, Appendix D.4)
786+
shc.conContext.outputRecord.changeWriteCiphers(
787+
SSLWriteCipher.nullTlsWriteCipher(),
788+
(clientHello.sessionId.length() != 0));
789+
781790
// Stateless, shall we clean up the handshake context as well?
782791
shc.handshakeHash.finish(); // forgot about the handshake hash
783792
shc.handshakeExtensions.clear();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
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
30+
*/
31+
32+
import java.nio.ByteBuffer;
33+
import javax.net.ssl.SSLEngineResult;
34+
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
35+
import javax.net.ssl.SSLException;
36+
import javax.net.ssl.SSLParameters;
37+
38+
public class EngineOutOfSeqCCS extends SSLEngineTemplate {
39+
40+
/*
41+
* Enables logging of the SSLEngine operations.
42+
*/
43+
private static final boolean logging = true;
44+
private static final boolean dumpBufs = true;
45+
46+
// Define a few basic TLS records we might need
47+
private static final int TLS_RECTYPE_CCS = 0x14;
48+
private static final int TLS_RECTYPE_ALERT = 0x15;
49+
private static final int TLS_RECTYPE_HANDSHAKE = 0x16;
50+
private static final int TLS_RECTYPE_APPDATA = 0x17;
51+
52+
SSLEngineResult clientResult, serverResult;
53+
54+
public EngineOutOfSeqCCS() throws Exception {
55+
super();
56+
}
57+
58+
public static void main(String[] args) throws Exception{
59+
new EngineOutOfSeqCCS().runDemo();
60+
}
61+
62+
private void runDemo() throws Exception {
63+
64+
// Client generates Client Hello
65+
clientResult = clientEngine.wrap(clientOut, cTOs);
66+
log("client wrap: ", clientResult);
67+
runDelegatedTasks(clientEngine);
68+
cTOs.flip();
69+
dumpByteBuffer("CLIENT-TO-SERVER", cTOs);
70+
71+
// Server consumes Client Hello
72+
serverResult = serverEngine.unwrap(cTOs, serverIn);
73+
log("server unwrap: ", serverResult);
74+
runDelegatedTasks(serverEngine);
75+
cTOs.compact();
76+
77+
// Server generates ServerHello/HelloRetryRequest
78+
serverResult = serverEngine.wrap(serverOut, sTOc);
79+
log("server wrap: ", serverResult);
80+
runDelegatedTasks(serverEngine);
81+
sTOc.flip();
82+
83+
dumpByteBuffer("SERVER-TO-CLIENT", sTOc);
84+
85+
// client consumes ServerHello/HelloRetryRequest
86+
clientResult = clientEngine.unwrap(sTOc, clientIn);
87+
log("client unwrap: ", clientResult);
88+
runDelegatedTasks(clientEngine);
89+
sTOc.compact();
90+
91+
// Server generates CCS
92+
serverResult = serverEngine.wrap(serverOut, sTOc);
93+
log("server wrap: ", serverResult);
94+
runDelegatedTasks(serverEngine);
95+
sTOc.flip();
96+
dumpByteBuffer("SERVER-TO-CLIENT", sTOc);
97+
98+
if (isTlsMessage(sTOc, TLS_RECTYPE_CCS)) {
99+
System.out.println("=========== CCS found ===========");
100+
} else {
101+
// In TLS1.3 middlebox compatibility mode the server sends a
102+
// dummy change_cipher_spec record immediately after its
103+
// first handshake message. This may either be after
104+
// a ServerHello or a HelloRetryRequest.
105+
// (RFC 8446, Appendix D.4)
106+
throw new SSLException(
107+
"Server should generate change_cipher_spec record");
108+
}
109+
clientEngine.closeOutbound();
110+
serverEngine.closeOutbound();
111+
}
112+
113+
/**
114+
* Look at an incoming TLS record and see if it is the desired
115+
* record type, and where appropriate the correct subtype.
116+
*
117+
* @param srcRecord The input TLS record to be evaluated. This
118+
* method will only look at the leading message if multiple
119+
* TLS handshake messages are coalesced into a single record.
120+
* @param reqRecType The requested TLS record type
121+
* @param recParams Zero or more integer sub type fields. For CCS
122+
* and ApplicationData, no params are used. For handshake records,
123+
* one value corresponding to the HandshakeType is required.
124+
* For Alerts, two values corresponding to AlertLevel and
125+
* AlertDescription are necessary.
126+
*
127+
* @return true if the proper handshake message is the first one
128+
* in the input record, false otherwise.
129+
*/
130+
private boolean isTlsMessage(ByteBuffer srcRecord, int reqRecType,
131+
int... recParams) {
132+
boolean foundMsg = false;
133+
134+
if (srcRecord.hasRemaining()) {
135+
srcRecord.mark();
136+
137+
// Grab the fields from the TLS Record
138+
int recordType = Byte.toUnsignedInt(srcRecord.get());
139+
byte ver_major = srcRecord.get();
140+
byte ver_minor = srcRecord.get();
141+
142+
if (recordType == reqRecType) {
143+
// For any zero-length recParams, making sure the requested
144+
// type is sufficient.
145+
if (recParams.length == 0) {
146+
foundMsg = true;
147+
} else {
148+
switch (recordType) {
149+
case TLS_RECTYPE_CCS:
150+
case TLS_RECTYPE_APPDATA:
151+
// We really shouldn't find ourselves here, but
152+
// if someone asked for these types and had more
153+
// recParams we can ignore them.
154+
foundMsg = true;
155+
break;
156+
case TLS_RECTYPE_ALERT:
157+
// Needs two params, AlertLevel and
158+
//AlertDescription
159+
if (recParams.length != 2) {
160+
throw new RuntimeException(
161+
"Test for Alert requires level and desc.");
162+
} else {
163+
int level = Byte.toUnsignedInt(
164+
srcRecord.get());
165+
int desc = Byte.toUnsignedInt(srcRecord.get());
166+
if (level == recParams[0] &&
167+
desc == recParams[1]) {
168+
foundMsg = true;
169+
}
170+
}
171+
break;
172+
case TLS_RECTYPE_HANDSHAKE:
173+
// Needs one parameter, HandshakeType
174+
if (recParams.length != 1) {
175+
throw new RuntimeException(
176+
"Test for Handshake requires only HS type");
177+
} else {
178+
// Go into the first handshake message in the
179+
// record and grab the handshake message header.
180+
// All we need to do is parse out the leading
181+
// byte.
182+
int msgHdr = srcRecord.getInt();
183+
int msgType = (msgHdr >> 24) & 0x000000FF;
184+
if (msgType == recParams[0]) {
185+
foundMsg = true;
186+
}
187+
}
188+
break;
189+
}
190+
}
191+
}
192+
193+
srcRecord.reset();
194+
}
195+
196+
return foundMsg;
197+
}
198+
199+
private static String tlsRecType(int type) {
200+
switch (type) {
201+
case 20:
202+
return "Change Cipher Spec";
203+
case 21:
204+
return "Alert";
205+
case 22:
206+
return "Handshake";
207+
case 23:
208+
return "Application Data";
209+
default:
210+
return ("Unknown (" + type + ")");
211+
}
212+
}
213+
214+
/*
215+
* Logging code
216+
*/
217+
private static boolean resultOnce = true;
218+
219+
private static void log(String str, SSLEngineResult result) {
220+
if (!logging) {
221+
return;
222+
}
223+
if (resultOnce) {
224+
resultOnce = false;
225+
System.out.println("The format of the SSLEngineResult is: \n" +
226+
"\t\"getStatus() / getHandshakeStatus()\" +\n" +
227+
"\t\"bytesConsumed() / bytesProduced()\"\n");
228+
}
229+
HandshakeStatus hsStatus = result.getHandshakeStatus();
230+
log(str +
231+
result.getStatus() + "/" + hsStatus + ", " +
232+
result.bytesConsumed() + "/" + result.bytesProduced() +
233+
" bytes");
234+
if (hsStatus == HandshakeStatus.FINISHED) {
235+
log("\t...ready for application data");
236+
}
237+
}
238+
239+
private static void log(String str) {
240+
if (logging) {
241+
System.out.println(str);
242+
}
243+
}
244+
245+
/**
246+
* Hex-dumps a ByteBuffer to stdout.
247+
*/
248+
private static void dumpByteBuffer(String header, ByteBuffer bBuf) {
249+
if (!dumpBufs) {
250+
return;
251+
}
252+
253+
int bufLen = bBuf.remaining();
254+
if (bufLen > 0) {
255+
bBuf.mark();
256+
257+
// We expect the position of the buffer to be at the
258+
// beginning of a TLS record. Get the type, version and length.
259+
int type = Byte.toUnsignedInt(bBuf.get());
260+
int ver_major = Byte.toUnsignedInt(bBuf.get());
261+
int ver_minor = Byte.toUnsignedInt(bBuf.get());
262+
263+
log("===== " + header + " (" + tlsRecType(type) + " / " +
264+
ver_major + "." + ver_minor + " / " +
265+
bufLen + " bytes) =====");
266+
bBuf.reset();
267+
for (int i = 0; i < bufLen; i++) {
268+
if (i != 0 && i % 16 == 0) {
269+
System.out.print("\n");
270+
}
271+
System.out.format("%02X ", bBuf.get(i));
272+
}
273+
log("\n===============================================");
274+
bBuf.reset();
275+
}
276+
}
277+
}

‎test/jdk/javax/net/ssl/templates/SSLEngineTemplate.java

+10-10
Original file line numberDiff line numberDiff line change
@@ -52,21 +52,21 @@
5252
* produced.
5353
*/
5454
public class SSLEngineTemplate implements SSLContextTemplate {
55-
private final SSLEngine clientEngine; // client Engine
56-
private final ByteBuffer clientOut; // write side of clientEngine
57-
private final ByteBuffer clientIn; // read side of clientEngine
55+
protected final SSLEngine clientEngine; // client Engine
56+
protected final ByteBuffer clientOut; // write side of clientEngine
57+
protected final ByteBuffer clientIn; // read side of clientEngine
5858

59-
private final SSLEngine serverEngine; // server Engine
60-
private final ByteBuffer serverOut; // write side of serverEngine
61-
private final ByteBuffer serverIn; // read side of serverEngine
59+
protected final SSLEngine serverEngine; // server Engine
60+
protected final ByteBuffer serverOut; // write side of serverEngine
61+
protected final ByteBuffer serverIn; // read side of serverEngine
6262

6363
// For data transport, this example uses local ByteBuffers. This
6464
// isn't really useful, but the purpose of this example is to show
6565
// SSLEngine concepts, not how to do network transport.
66-
private final ByteBuffer cTOs; // "reliable" transport client->server
67-
private final ByteBuffer sTOc; // "reliable" transport server->client
66+
protected final ByteBuffer cTOs; // "reliable" transport client->server
67+
protected final ByteBuffer sTOc; // "reliable" transport server->client
6868

69-
private SSLEngineTemplate() throws Exception {
69+
protected SSLEngineTemplate() throws Exception {
7070
serverEngine = configureServerEngine(
7171
createServerSSLContext().createSSLEngine());
7272

@@ -223,7 +223,7 @@ private static void log(String message) {
223223

224224
// If the result indicates that we have outstanding tasks to do,
225225
// go ahead and run them in this thread.
226-
private static void runDelegatedTasks(SSLEngine engine) throws Exception {
226+
protected static void runDelegatedTasks(SSLEngine engine) throws Exception {
227227
if (engine.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
228228
Runnable runnable;
229229
while ((runnable = engine.getDelegatedTask()) != null) {

1 commit comments

Comments
 (1)

openjdk-notifier[bot] commented on May 31, 2024

@openjdk-notifier[bot]
Please sign in to comment.