Skip to content

Commit d719385

Browse files
Alexey BakhtinRealCLanger
authored andcommitted
8307383: Enhance DTLS connections
Reviewed-by: mbaesken, andrew Backport-of: 362dbbaa952b3d4a5270c6bfae879a12e9bdf4d1
1 parent 2247747 commit d719385

File tree

6 files changed

+185
-19
lines changed

6 files changed

+185
-19
lines changed

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,6 @@ byte[] getHelloCookieBytes() {
217217
// ignore cookie
218218
hos.putBytes16(getEncodedCipherSuites());
219219
hos.putBytes8(compressionMethod);
220-
extensions.send(hos); // In TLS 1.3, use of certain
221-
// extensions is mandatory.
222220
} catch (IOException ioe) {
223221
// unlikely
224222
}
@@ -1427,6 +1425,9 @@ public void consume(ConnectionContext context,
14271425
shc.handshakeProducers.put(SSLHandshake.SERVER_HELLO.id,
14281426
SSLHandshake.SERVER_HELLO);
14291427

1428+
// Reset the ClientHello non-zero offset fragment allowance
1429+
shc.acceptCliHelloFragments = false;
1430+
14301431
//
14311432
// produce
14321433
//

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

Lines changed: 136 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2015, 2020, 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
@@ -28,13 +28,7 @@
2828
import java.io.IOException;
2929
import java.nio.ByteBuffer;
3030
import java.security.GeneralSecurityException;
31-
import java.util.Collections;
32-
import java.util.HashMap;
33-
import java.util.Iterator;
34-
import java.util.LinkedList;
35-
import java.util.List;
36-
import java.util.Set;
37-
import java.util.TreeSet;
31+
import java.util.*;
3832
import javax.crypto.BadPaddingException;
3933
import javax.net.ssl.SSLException;
4034
import javax.net.ssl.SSLProtocolException;
@@ -46,12 +40,23 @@
4640
final class DTLSInputRecord extends InputRecord implements DTLSRecord {
4741
private DTLSReassembler reassembler = null;
4842
private int readEpoch;
43+
private SSLContextImpl sslContext;
4944

5045
DTLSInputRecord(HandshakeHash handshakeHash) {
5146
super(handshakeHash, SSLReadCipher.nullDTlsReadCipher());
5247
this.readEpoch = 0;
5348
}
5449

50+
// Method to set TransportContext
51+
public void setTransportContext(TransportContext tc) {
52+
this.tc = tc;
53+
}
54+
55+
// Method to set SSLContext
56+
public void setSSLContext(SSLContextImpl sslContext) {
57+
this.sslContext = sslContext;
58+
}
59+
5560
@Override
5661
void changeReadCiphers(SSLReadCipher readCipher) {
5762
this.readCipher = readCipher;
@@ -544,6 +549,27 @@ public int compareTo(RecordFragment o) {
544549
}
545550
}
546551

552+
/**
553+
* Turn a sufficiently-large initial ClientHello fragment into one that
554+
* stops immediately after the compression methods. This is only used
555+
* for the initial CH message fragment at offset 0.
556+
*
557+
* @param srcFrag the fragment actually received by the DTLSReassembler
558+
* @param limit the size of the new, cloned/truncated handshake fragment
559+
*
560+
* @return a truncated handshake fragment that is sized to look like a
561+
* complete message, but actually contains only up to the compression
562+
* methods (no extensions)
563+
*/
564+
private static HandshakeFragment truncateChFragment(HandshakeFragment srcFrag,
565+
int limit) {
566+
return new HandshakeFragment(Arrays.copyOf(srcFrag.fragment, limit),
567+
srcFrag.contentType, srcFrag.majorVersion,
568+
srcFrag.minorVersion, srcFrag.recordEnS, srcFrag.recordEpoch,
569+
srcFrag.recordSeq, srcFrag.handshakeType, limit,
570+
srcFrag.messageSeq, srcFrag.fragmentOffset, limit);
571+
}
572+
547573
private static final class HoleDescriptor {
548574
int offset; // fragment_offset
549575
int limit; // fragment_offset + fragment_length
@@ -647,10 +673,17 @@ void expectingFinishFlight() {
647673
// Queue up a handshake message.
648674
void queueUpHandshake(HandshakeFragment hsf) throws SSLProtocolException {
649675
if (!isDesirable(hsf)) {
650-
// Not a dedired record, discard it.
676+
// Not a desired record, discard it.
651677
return;
652678
}
653679

680+
if (hsf.handshakeType == SSLHandshake.CLIENT_HELLO.id) {
681+
// validate the first or subsequent ClientHello message
682+
if ((hsf = valHello(hsf, hsf.messageSeq == 0)) == null) {
683+
return;
684+
}
685+
}
686+
654687
// Clean up the retransmission messages if necessary.
655688
cleanUpRetransmit(hsf);
656689

@@ -783,6 +816,100 @@ void queueUpHandshake(HandshakeFragment hsf) throws SSLProtocolException {
783816
}
784817
}
785818

819+
private HandshakeFragment valHello(HandshakeFragment hsf,
820+
boolean firstHello) {
821+
ServerHandshakeContext shc =
822+
(ServerHandshakeContext) tc.handshakeContext;
823+
// Drop any fragment that is not a zero offset until we've received
824+
// a second (or possibly later) CH message that passes the cookie
825+
// check.
826+
if (shc == null || !shc.acceptCliHelloFragments) {
827+
if (hsf.fragmentOffset != 0) {
828+
return null;
829+
}
830+
} else {
831+
// Let this fragment through to the DTLSReassembler as-is
832+
return hsf;
833+
}
834+
835+
try {
836+
ByteBuffer fragmentData = ByteBuffer.wrap(hsf.fragment);
837+
838+
ProtocolVersion pv = ProtocolVersion.valueOf(
839+
Record.getInt16(fragmentData));
840+
if (!pv.isDTLS) {
841+
return null;
842+
}
843+
// Read the random (32 bytes)
844+
if (fragmentData.remaining() < 32) {
845+
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) {
846+
SSLLogger.fine("Rejected client hello fragment (bad random len) " +
847+
"fo=" + hsf.fragmentOffset + " fl=" + hsf.fragmentLength);
848+
}
849+
return null;
850+
}
851+
fragmentData.position(fragmentData.position() + 32);
852+
853+
// SessionID
854+
byte[] sessId = Record.getBytes8(fragmentData);
855+
if (sessId.length > 0 &&
856+
!SSLConfiguration.enableDtlsResumeCookie) {
857+
// If we are in a resumption it is possible that the cookie
858+
// exchange will be skipped. This is a server-side setting
859+
// and it is NOT the default. If enableDtlsResumeCookie is
860+
// false though, then we will buffer fragments since there
861+
// is no cookie exchange to execute prior to performing
862+
// reassembly.
863+
return hsf;
864+
}
865+
866+
// Cookie
867+
byte[] cookie = Record.getBytes8(fragmentData);
868+
if (firstHello && cookie.length != 0) {
869+
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) {
870+
SSLLogger.fine("Rejected initial client hello fragment (bad cookie len) " +
871+
"fo=" + hsf.fragmentOffset + " fl=" + hsf.fragmentLength);
872+
}
873+
return null;
874+
}
875+
// CipherSuites
876+
Record.getBytes16(fragmentData);
877+
// Compression methods
878+
Record.getBytes8(fragmentData);
879+
880+
// If it's the first fragment, we'll truncate it and push it
881+
// through the reassembler.
882+
if (firstHello) {
883+
return truncateChFragment(hsf, fragmentData.position());
884+
} else {
885+
HelloCookieManager hcMgr = sslContext.
886+
getHelloCookieManager(ProtocolVersion.DTLS10);
887+
ByteBuffer msgFragBuf = ByteBuffer.wrap(hsf.fragment, 0,
888+
fragmentData.position());
889+
ClientHello.ClientHelloMessage chMsg =
890+
new ClientHello.ClientHelloMessage(shc, msgFragBuf, null);
891+
if (!hcMgr.isCookieValid(shc, chMsg, cookie)) {
892+
// Bad cookie check, truncate it and let the ClientHello
893+
// consumer recheck, fail and take the appropriate action.
894+
return truncateChFragment(hsf, fragmentData.position());
895+
} else {
896+
// It's a good cookie, return the original handshake
897+
// fragment and let it go into the DTLSReassembler like
898+
// any other fragment so we can wait for the rest of
899+
// the CH message.
900+
shc.acceptCliHelloFragments = true;
901+
return hsf;
902+
}
903+
}
904+
} catch (IOException ioe) {
905+
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) {
906+
SSLLogger.fine("Rejected client hello fragment " +
907+
"fo=" + hsf.fragmentOffset + " fl=" + hsf.fragmentLength);
908+
}
909+
return null;
910+
}
911+
}
912+
786913
// Queue up a ChangeCipherSpec message
787914
void queueUpChangeCipherSpec(RecordFragment rf)
788915
throws SSLProtocolException {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 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
@@ -55,6 +55,7 @@ class ServerHandshakeContext extends HandshakeContext {
5555
CertificateMessage.CertificateEntry currentCertEntry;
5656
private static final long DEFAULT_STATUS_RESP_DELAY = 5000L;
5757
final long statusRespTimeout;
58+
boolean acceptCliHelloFragments = false;
5859

5960

6061
ServerHandshakeContext(SSLContextImpl sslContext,

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 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
@@ -156,6 +156,11 @@ private TransportContext(SSLContextImpl sslContext, SSLTransport transport,
156156

157157
this.acc = AccessController.getContext();
158158
this.consumers = new HashMap<>();
159+
160+
if (inputRecord instanceof DTLSInputRecord dtlsInputRecord) {
161+
dtlsInputRecord.setTransportContext(this);
162+
dtlsInputRecord.setSSLContext(this.sslContext);
163+
}
159164
}
160165

161166
// Dispatch plaintext to a specific consumer.

test/jdk/javax/net/ssl/DTLS/InvalidRecords.java

Lines changed: 28 additions & 4 deletions
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
@@ -26,7 +26,7 @@
2626

2727
/*
2828
* @test
29-
* @bug 8043758
29+
* @bug 8043758 8307383
3030
* @summary Datagram Transport Layer Security (DTLS)
3131
* @modules java.base/sun.security.util
3232
* @library /test/lib
@@ -36,6 +36,7 @@
3636

3737
import java.net.DatagramPacket;
3838
import java.net.SocketAddress;
39+
import java.nio.ByteBuffer;
3940
import java.util.concurrent.atomic.AtomicBoolean;
4041

4142
/**
@@ -73,11 +74,34 @@ DatagramPacket createHandshakePacket(byte[] ba, SocketAddress socketAddr) {
7374
// ClientHello with cookie
7475
needInvalidRecords.set(false);
7576
System.out.println("invalidate ClientHello message");
76-
if (ba[ba.length - 1] == (byte)0xFF) {
77-
ba[ba.length - 1] = (byte)0xFE;
77+
// We will alter the compression method field in order to make the cookie
78+
// check fail.
79+
ByteBuffer chRec = ByteBuffer.wrap(ba);
80+
// Skip 59 bytes past the record header (13), the handshake header (12),
81+
// the protocol version (2), and client random (32)
82+
chRec.position(59);
83+
// Jump past the session ID
84+
int len = Byte.toUnsignedInt(chRec.get());
85+
chRec.position(chRec.position() + len);
86+
// Skip the cookie
87+
len = Byte.toUnsignedInt(chRec.get());
88+
chRec.position(chRec.position() + len);
89+
// Skip past cipher suites
90+
len = Short.toUnsignedInt(chRec.getShort());
91+
chRec.position(chRec.position() + len);
92+
// Read the data on the compression methods, should be at least 1
93+
len = Byte.toUnsignedInt(chRec.get());
94+
if (len >= 1) {
95+
System.out.println("Detected compression methods (count = " + len + ")");
7896
} else {
7997
ba[ba.length - 1] = (byte)0xFF;
98+
throw new RuntimeException("Got zero length comp methods");
8099
}
100+
// alter the first comp method.
101+
int compMethodVal = Byte.toUnsignedInt(chRec.get(chRec.position()));
102+
System.out.println("Changing value at position " + chRec.position() +
103+
" from " + compMethodVal + " to " + ++compMethodVal);
104+
chRec.put(chRec.position(), (byte)compMethodVal);
81105
}
82106

83107
return super.createHandshakePacket(ba, socketAddr);

test/jdk/javax/net/ssl/TLSCommon/MFLNTest.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2015, 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
@@ -34,7 +34,15 @@ public class MFLNTest extends SSLEngineTestCase {
3434
public static void main(String[] args) {
3535
setUpAndStartKDCIfNeeded();
3636
System.setProperty("jsse.enableMFLNExtension", "true");
37-
for (int mfl = 4096; mfl >= 256; mfl /= 2) {
37+
String testMode = System.getProperty("test.mode", "norm");
38+
int mflLen;
39+
if (testMode.equals("norm_sni")) {
40+
mflLen = 512;
41+
} else {
42+
mflLen = 256;
43+
}
44+
45+
for (int mfl = 4096; mfl >= mflLen; mfl /= 2) {
3846
System.out.println("=============================================="
3947
+ "==============");
4048
System.out.printf("Testsing DTLS handshake with MFL = %d%n", mfl);

0 commit comments

Comments
 (0)