Skip to content

Commit 18e2446

Browse files
author
Matthew Donovan
committed
8301381: Verify DTLS 1.0 cannot be negotiated
Reviewed-by: xuelei
1 parent b3cb82b commit 18e2446

File tree

1 file changed

+320
-0
lines changed

1 file changed

+320
-0
lines changed
Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
/*
2+
* Copyright (c) 2023, 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+
import jdk.test.lib.security.SecurityUtils;
25+
26+
import javax.net.ssl.*;
27+
import java.io.IOException;
28+
import java.net.*;
29+
import java.nio.ByteBuffer;
30+
import java.nio.file.Path;
31+
import java.util.ArrayList;
32+
import java.util.List;
33+
import java.util.concurrent.atomic.AtomicInteger;
34+
35+
/*
36+
* @test
37+
* @bug 8301381
38+
* @library /test/lib /javax/net/ssl/templates
39+
* @summary DTLSv10 is now disabled. This test verifies that the server will
40+
* not negotiate a connection if the client asks for it.
41+
* @run main/othervm DTLSWontNegotiateV10 DTLS
42+
* @run main/othervm DTLSWontNegotiateV10 DTLSv1.0
43+
*/
44+
public class DTLSWontNegotiateV10 {
45+
46+
private static final int MTU = 1024;
47+
private static final String DTLSV_1_0 = "DTLSv1.0";
48+
private static final String DTLS = "DTLS";
49+
private static final String DTLSV_1_2 = "DTLSv1.2";
50+
51+
public static void main(String[] args) throws Exception {
52+
if (args[0].equals(DTLSV_1_0)) {
53+
SecurityUtils.removeFromDisabledTlsAlgs(DTLSV_1_0);
54+
}
55+
56+
if (args.length > 1) {
57+
// running in client child process
58+
// args: protocol server-port
59+
try (DTLSClient client = new DTLSClient(args[0], Integer.parseInt(args[1]))) {
60+
client.run();
61+
}
62+
63+
} else {
64+
// server process
65+
// args: protocol
66+
try (DTLSServer server = new DTLSServer(args[0])) {
67+
List<String> command = List.of(
68+
Path.of(System.getProperty("java.home"), "bin", "java").toString(),
69+
"DTLSWontNegotiateV10",
70+
// if server is "DTLS" then the client should be v1.0 and vice versa
71+
args[0].equals(DTLS) ? DTLSV_1_0 : DTLS,
72+
Integer.toString(server.getListeningPortNumber())
73+
);
74+
75+
ProcessBuilder builder = new ProcessBuilder(command);
76+
Process p = builder.inheritIO().start();
77+
server.run();
78+
p.destroy();
79+
System.out.println("Success: DTLSv1.0 connection was not established.");
80+
}
81+
}
82+
}
83+
84+
private static class DTLSClient extends DTLSEndpoint {
85+
private final int remotePort;
86+
87+
private final DatagramSocket socket = new DatagramSocket();
88+
89+
public DTLSClient(String protocol, int portNumber) throws Exception {
90+
super(true, protocol);
91+
remotePort = portNumber;
92+
log("Enabled protocols: " + String.join(" ", engine.getEnabledProtocols()));
93+
}
94+
95+
@Override
96+
public void run() throws Exception {
97+
doHandshake(socket);
98+
log("Client done handshaking. Protocol: " + engine.getSession().getProtocol());
99+
}
100+
101+
@Override
102+
void setRemotePortNumber(int portNumber) {
103+
// don't do anything; we're using the one we already know
104+
}
105+
106+
@Override
107+
int getRemotePortNumber() {
108+
return remotePort;
109+
}
110+
111+
@Override
112+
public void close () {
113+
socket.close();
114+
}
115+
}
116+
117+
private abstract static class DTLSEndpoint extends SSLContextTemplate implements AutoCloseable {
118+
protected final SSLEngine engine;
119+
protected final SSLContext context;
120+
private final String protocol;
121+
protected final InetAddress LOCALHOST;
122+
123+
private final String tag;
124+
125+
public DTLSEndpoint(boolean useClientMode, String protocol) throws Exception {
126+
this.protocol = protocol;
127+
if (useClientMode) {
128+
tag = "client";
129+
context = createClientSSLContext();
130+
} else {
131+
tag = "server";
132+
context = createServerSSLContext();
133+
}
134+
engine = context.createSSLEngine();
135+
engine.setUseClientMode(useClientMode);
136+
SSLParameters params = engine.getSSLParameters();
137+
params.setMaximumPacketSize(MTU);
138+
engine.setSSLParameters(params);
139+
if (protocol.equals(DTLS)) {
140+
// make sure both versions are "enabled"; 1.0 should be
141+
// disabled by policy now and won't be negotiated.
142+
engine.setEnabledProtocols(new String[]{DTLSV_1_0, DTLSV_1_2});
143+
} else {
144+
engine.setEnabledProtocols(new String[]{DTLSV_1_0});
145+
}
146+
147+
LOCALHOST = InetAddress.getByName("localhost");
148+
}
149+
150+
@Override
151+
protected ContextParameters getServerContextParameters() {
152+
return new ContextParameters(protocol, "PKIX", "NewSunX509");
153+
}
154+
155+
@Override
156+
protected ContextParameters getClientContextParameters() {
157+
return new ContextParameters(protocol, "PKIX", "NewSunX509");
158+
}
159+
160+
161+
abstract void setRemotePortNumber(int portNumber);
162+
163+
abstract int getRemotePortNumber();
164+
165+
abstract void run() throws Exception;
166+
167+
private boolean runDelegatedTasks() {
168+
log("Running delegated tasks.");
169+
Runnable runnable;
170+
while ((runnable = engine.getDelegatedTask()) != null) {
171+
runnable.run();
172+
}
173+
174+
SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
175+
if (hs == SSLEngineResult.HandshakeStatus.NEED_TASK) {
176+
throw new RuntimeException(
177+
"Handshake shouldn't need additional tasks");
178+
}
179+
180+
return true;
181+
}
182+
183+
protected void doHandshake(DatagramSocket socket) throws Exception {
184+
boolean handshaking = true;
185+
engine.beginHandshake();
186+
while (handshaking) {
187+
log("Handshake status = " + engine.getHandshakeStatus());
188+
handshaking = switch (engine.getHandshakeStatus()) {
189+
case NEED_UNWRAP, NEED_UNWRAP_AGAIN -> readFromServer(socket);
190+
case NEED_WRAP -> sendHandshakePackets(socket);
191+
case NEED_TASK -> runDelegatedTasks();
192+
case NOT_HANDSHAKING, FINISHED -> false;
193+
};
194+
}
195+
}
196+
197+
private boolean readFromServer(DatagramSocket socket) throws IOException {
198+
log("Reading data from remote endpoint.");
199+
ByteBuffer iNet, iApp;
200+
if (engine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP) {
201+
byte[] buffer = new byte[MTU];
202+
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
203+
socket.receive(packet);
204+
setRemotePortNumber(packet.getPort());
205+
iNet = ByteBuffer.wrap(buffer, 0, packet.getLength());
206+
iApp = ByteBuffer.allocate(MTU);
207+
} else {
208+
iNet = ByteBuffer.allocate(0);
209+
iApp = ByteBuffer.allocate(MTU);
210+
}
211+
212+
SSLEngineResult engineResult;
213+
do {
214+
engineResult = engine.unwrap(iNet, iApp);
215+
} while (iNet.hasRemaining());
216+
217+
return switch (engineResult.getStatus()) {
218+
case CLOSED -> false;
219+
case OK -> true;
220+
case BUFFER_OVERFLOW -> throw new RuntimeException("Buffer overflow: "
221+
+ "incorrect server maximum fragment size");
222+
case BUFFER_UNDERFLOW -> throw new RuntimeException("Buffer underflow: "
223+
+ "incorrect server maximum fragment size");
224+
};
225+
}
226+
227+
private boolean sendHandshakePackets(DatagramSocket socket) throws Exception {
228+
List<DatagramPacket> packets = generateHandshakePackets();
229+
log("Sending handshake packets.");
230+
packets.forEach((p) -> {
231+
try {
232+
socket.send(p);
233+
} catch (IOException e) {
234+
throw new RuntimeException(e);
235+
}
236+
});
237+
238+
return true;
239+
}
240+
241+
private List<DatagramPacket> generateHandshakePackets() throws SSLException {
242+
log("Generating handshake packets.");
243+
List<DatagramPacket> packets = new ArrayList<>();
244+
ByteBuffer oNet = ByteBuffer.allocate(engine.getSession().getPacketBufferSize());
245+
ByteBuffer oApp = ByteBuffer.allocate(0);
246+
247+
while (engine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP) {
248+
SSLEngineResult result = engine.wrap(oApp, oNet);
249+
oNet.flip();
250+
251+
switch (result.getStatus()) {
252+
case BUFFER_UNDERFLOW -> {
253+
if (engine.getHandshakeStatus() != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
254+
throw new RuntimeException("Buffer underflow: "
255+
+ "incorrect server maximum fragment size");
256+
}
257+
}
258+
case BUFFER_OVERFLOW -> throw new RuntimeException("Buffer overflow: "
259+
+ "incorrect server maximum fragment size");
260+
case CLOSED -> throw new RuntimeException("SSLEngine has closed");
261+
}
262+
263+
if (oNet.hasRemaining()) {
264+
byte[] packetBuffer = new byte[oNet.remaining()];
265+
oNet.get(packetBuffer);
266+
packets.add(new DatagramPacket(packetBuffer, packetBuffer.length,
267+
LOCALHOST, getRemotePortNumber()));
268+
}
269+
270+
runDelegatedTasks();
271+
oNet.clear();
272+
}
273+
274+
log("Generated " + packets.size() + " packets.");
275+
return packets;
276+
}
277+
278+
protected void log(String msg) {
279+
System.out.println(tag + ": " + msg);
280+
}
281+
}
282+
283+
private static class DTLSServer extends DTLSEndpoint implements AutoCloseable {
284+
285+
private final AtomicInteger portNumber = new AtomicInteger(0);
286+
private final DatagramSocket socket = new DatagramSocket(0);
287+
288+
public DTLSServer(String protocol) throws Exception {
289+
super(false, protocol);
290+
log("Enabled protocols: " + String.join(" ", engine.getEnabledProtocols()));
291+
}
292+
293+
@Override
294+
public void run() throws Exception {
295+
doHandshake(socket);
296+
if (!engine.getSession().getProtocol().equals("NONE")) {
297+
throw new RuntimeException("Negotiated protocol: "
298+
+ engine.getSession().getProtocol() +
299+
". No protocol should be negotated.");
300+
}
301+
}
302+
303+
public int getListeningPortNumber() {
304+
return socket.getLocalPort();
305+
}
306+
307+
void setRemotePortNumber(int portNumber) {
308+
this.portNumber.compareAndSet(0, portNumber);
309+
}
310+
311+
int getRemotePortNumber() {
312+
return portNumber.get();
313+
}
314+
315+
@Override
316+
public void close() throws Exception {
317+
socket.close();
318+
}
319+
}
320+
}

0 commit comments

Comments
 (0)