8307383: Enhance DTLS connections
Co-authored-by: Jamil Nimeh <jnimeh@openjdk.org> Reviewed-by: rhalade, mschoene, ascarpino
This commit is contained in:
parent
498a58244d
commit
dadcee1b89
src/java.base/share/classes/sun/security/ssl
test/jdk/javax/net/ssl
@ -213,8 +213,6 @@ final class ClientHello {
|
|||||||
// ignore cookie
|
// ignore cookie
|
||||||
hos.putBytes16(getEncodedCipherSuites());
|
hos.putBytes16(getEncodedCipherSuites());
|
||||||
hos.putBytes8(compressionMethod);
|
hos.putBytes8(compressionMethod);
|
||||||
extensions.send(hos); // In TLS 1.3, use of certain
|
|
||||||
// extensions is mandatory.
|
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
// unlikely
|
// unlikely
|
||||||
}
|
}
|
||||||
@ -1426,6 +1424,9 @@ final class ClientHello {
|
|||||||
shc.handshakeProducers.put(SSLHandshake.SERVER_HELLO.id,
|
shc.handshakeProducers.put(SSLHandshake.SERVER_HELLO.id,
|
||||||
SSLHandshake.SERVER_HELLO);
|
SSLHandshake.SERVER_HELLO);
|
||||||
|
|
||||||
|
// Reset the ClientHello non-zero offset fragment allowance
|
||||||
|
shc.acceptCliHelloFragments = false;
|
||||||
|
|
||||||
//
|
//
|
||||||
// produce
|
// produce
|
||||||
//
|
//
|
||||||
|
@ -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
|
||||||
@ -40,12 +40,23 @@ import sun.security.ssl.SSLCipher.SSLReadCipher;
|
|||||||
final class DTLSInputRecord extends InputRecord implements DTLSRecord {
|
final class DTLSInputRecord extends InputRecord implements DTLSRecord {
|
||||||
private DTLSReassembler reassembler = null;
|
private DTLSReassembler reassembler = null;
|
||||||
private int readEpoch;
|
private int readEpoch;
|
||||||
|
private SSLContextImpl sslContext;
|
||||||
|
|
||||||
DTLSInputRecord(HandshakeHash handshakeHash) {
|
DTLSInputRecord(HandshakeHash handshakeHash) {
|
||||||
super(handshakeHash, SSLReadCipher.nullDTlsReadCipher());
|
super(handshakeHash, SSLReadCipher.nullDTlsReadCipher());
|
||||||
this.readEpoch = 0;
|
this.readEpoch = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Method to set TransportContext
|
||||||
|
public void setTransportContext(TransportContext tc) {
|
||||||
|
this.tc = tc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to set SSLContext
|
||||||
|
public void setSSLContext(SSLContextImpl sslContext) {
|
||||||
|
this.sslContext = sslContext;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void changeReadCiphers(SSLReadCipher readCipher) {
|
void changeReadCiphers(SSLReadCipher readCipher) {
|
||||||
this.readCipher = readCipher;
|
this.readCipher = readCipher;
|
||||||
@ -537,6 +548,27 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn a sufficiently-large initial ClientHello fragment into one that
|
||||||
|
* stops immediately after the compression methods. This is only used
|
||||||
|
* for the initial CH message fragment at offset 0.
|
||||||
|
*
|
||||||
|
* @param srcFrag the fragment actually received by the DTLSReassembler
|
||||||
|
* @param limit the size of the new, cloned/truncated handshake fragment
|
||||||
|
*
|
||||||
|
* @return a truncated handshake fragment that is sized to look like a
|
||||||
|
* complete message, but actually contains only up to the compression
|
||||||
|
* methods (no extensions)
|
||||||
|
*/
|
||||||
|
private static HandshakeFragment truncateChFragment(HandshakeFragment srcFrag,
|
||||||
|
int limit) {
|
||||||
|
return new HandshakeFragment(Arrays.copyOf(srcFrag.fragment, limit),
|
||||||
|
srcFrag.contentType, srcFrag.majorVersion,
|
||||||
|
srcFrag.minorVersion, srcFrag.recordEnS, srcFrag.recordEpoch,
|
||||||
|
srcFrag.recordSeq, srcFrag.handshakeType, limit,
|
||||||
|
srcFrag.messageSeq, srcFrag.fragmentOffset, limit);
|
||||||
|
}
|
||||||
|
|
||||||
private static final class HoleDescriptor {
|
private static final class HoleDescriptor {
|
||||||
int offset; // fragment_offset
|
int offset; // fragment_offset
|
||||||
int limit; // fragment_offset + fragment_length
|
int limit; // fragment_offset + fragment_length
|
||||||
@ -640,10 +672,17 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord {
|
|||||||
// Queue up a handshake message.
|
// Queue up a handshake message.
|
||||||
void queueUpHandshake(HandshakeFragment hsf) throws SSLProtocolException {
|
void queueUpHandshake(HandshakeFragment hsf) throws SSLProtocolException {
|
||||||
if (!isDesirable(hsf)) {
|
if (!isDesirable(hsf)) {
|
||||||
// Not a dedired record, discard it.
|
// Not a desired record, discard it.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hsf.handshakeType == SSLHandshake.CLIENT_HELLO.id) {
|
||||||
|
// validate the first or subsequent ClientHello message
|
||||||
|
if ((hsf = valHello(hsf, hsf.messageSeq == 0)) == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Clean up the retransmission messages if necessary.
|
// Clean up the retransmission messages if necessary.
|
||||||
cleanUpRetransmit(hsf);
|
cleanUpRetransmit(hsf);
|
||||||
|
|
||||||
@ -769,6 +808,100 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private HandshakeFragment valHello(HandshakeFragment hsf,
|
||||||
|
boolean firstHello) {
|
||||||
|
ServerHandshakeContext shc =
|
||||||
|
(ServerHandshakeContext) tc.handshakeContext;
|
||||||
|
// Drop any fragment that is not a zero offset until we've received
|
||||||
|
// a second (or possibly later) CH message that passes the cookie
|
||||||
|
// check.
|
||||||
|
if (shc == null || !shc.acceptCliHelloFragments) {
|
||||||
|
if (hsf.fragmentOffset != 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Let this fragment through to the DTLSReassembler as-is
|
||||||
|
return hsf;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ByteBuffer fragmentData = ByteBuffer.wrap(hsf.fragment);
|
||||||
|
|
||||||
|
ProtocolVersion pv = ProtocolVersion.valueOf(
|
||||||
|
Record.getInt16(fragmentData));
|
||||||
|
if (!pv.isDTLS) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Read the random (32 bytes)
|
||||||
|
if (fragmentData.remaining() < 32) {
|
||||||
|
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) {
|
||||||
|
SSLLogger.fine("Rejected client hello fragment (bad random len) " +
|
||||||
|
"fo=" + hsf.fragmentOffset + " fl=" + hsf.fragmentLength);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
fragmentData.position(fragmentData.position() + 32);
|
||||||
|
|
||||||
|
// SessionID
|
||||||
|
byte[] sessId = Record.getBytes8(fragmentData);
|
||||||
|
if (sessId.length > 0 &&
|
||||||
|
!SSLConfiguration.enableDtlsResumeCookie) {
|
||||||
|
// If we are in a resumption it is possible that the cookie
|
||||||
|
// exchange will be skipped. This is a server-side setting
|
||||||
|
// and it is NOT the default. If enableDtlsResumeCookie is
|
||||||
|
// false though, then we will buffer fragments since there
|
||||||
|
// is no cookie exchange to execute prior to performing
|
||||||
|
// reassembly.
|
||||||
|
return hsf;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cookie
|
||||||
|
byte[] cookie = Record.getBytes8(fragmentData);
|
||||||
|
if (firstHello && cookie.length != 0) {
|
||||||
|
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) {
|
||||||
|
SSLLogger.fine("Rejected initial client hello fragment (bad cookie len) " +
|
||||||
|
"fo=" + hsf.fragmentOffset + " fl=" + hsf.fragmentLength);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// CipherSuites
|
||||||
|
Record.getBytes16(fragmentData);
|
||||||
|
// Compression methods
|
||||||
|
Record.getBytes8(fragmentData);
|
||||||
|
|
||||||
|
// If it's the first fragment, we'll truncate it and push it
|
||||||
|
// through the reassembler.
|
||||||
|
if (firstHello) {
|
||||||
|
return truncateChFragment(hsf, fragmentData.position());
|
||||||
|
} else {
|
||||||
|
HelloCookieManager hcMgr = sslContext.
|
||||||
|
getHelloCookieManager(ProtocolVersion.DTLS10);
|
||||||
|
ByteBuffer msgFragBuf = ByteBuffer.wrap(hsf.fragment, 0,
|
||||||
|
fragmentData.position());
|
||||||
|
ClientHello.ClientHelloMessage chMsg =
|
||||||
|
new ClientHello.ClientHelloMessage(shc, msgFragBuf, null);
|
||||||
|
if (!hcMgr.isCookieValid(shc, chMsg, cookie)) {
|
||||||
|
// Bad cookie check, truncate it and let the ClientHello
|
||||||
|
// consumer recheck, fail and take the appropriate action.
|
||||||
|
return truncateChFragment(hsf, fragmentData.position());
|
||||||
|
} else {
|
||||||
|
// It's a good cookie, return the original handshake
|
||||||
|
// fragment and let it go into the DTLSReassembler like
|
||||||
|
// any other fragment so we can wait for the rest of
|
||||||
|
// the CH message.
|
||||||
|
shc.acceptCliHelloFragments = true;
|
||||||
|
return hsf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) {
|
||||||
|
SSLLogger.fine("Rejected client hello fragment " +
|
||||||
|
"fo=" + hsf.fragmentOffset + " fl=" + hsf.fragmentLength);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Queue up a ChangeCipherSpec message
|
// Queue up a ChangeCipherSpec message
|
||||||
void queueUpChangeCipherSpec(RecordFragment rf)
|
void queueUpChangeCipherSpec(RecordFragment rf)
|
||||||
throws SSLProtocolException {
|
throws SSLProtocolException {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2018, 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
|
||||||
@ -55,6 +55,7 @@ class ServerHandshakeContext extends HandshakeContext {
|
|||||||
CertificateMessage.CertificateEntry currentCertEntry;
|
CertificateMessage.CertificateEntry currentCertEntry;
|
||||||
private static final long DEFAULT_STATUS_RESP_DELAY = 5000L;
|
private static final long DEFAULT_STATUS_RESP_DELAY = 5000L;
|
||||||
final long statusRespTimeout;
|
final long statusRespTimeout;
|
||||||
|
boolean acceptCliHelloFragments = false;
|
||||||
|
|
||||||
|
|
||||||
ServerHandshakeContext(SSLContextImpl sslContext,
|
ServerHandshakeContext(SSLContextImpl sslContext,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2018, 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
|
||||||
@ -156,6 +156,11 @@ final class TransportContext implements ConnectionContext {
|
|||||||
|
|
||||||
this.acc = AccessController.getContext();
|
this.acc = AccessController.getContext();
|
||||||
this.consumers = new HashMap<>();
|
this.consumers = new HashMap<>();
|
||||||
|
|
||||||
|
if (inputRecord instanceof DTLSInputRecord dtlsInputRecord) {
|
||||||
|
dtlsInputRecord.setTransportContext(this);
|
||||||
|
dtlsInputRecord.setSSLContext(this.sslContext);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dispatch plaintext to a specific consumer.
|
// Dispatch plaintext to a specific consumer.
|
||||||
|
@ -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
|
||||||
@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* @test
|
* @test
|
||||||
* @bug 8043758
|
* @bug 8043758 8307383
|
||||||
* @summary Datagram Transport Layer Security (DTLS)
|
* @summary Datagram Transport Layer Security (DTLS)
|
||||||
* @modules java.base/sun.security.util
|
* @modules java.base/sun.security.util
|
||||||
* @library /test/lib
|
* @library /test/lib
|
||||||
@ -36,6 +36,7 @@
|
|||||||
|
|
||||||
import java.net.DatagramPacket;
|
import java.net.DatagramPacket;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -73,11 +74,34 @@ public class InvalidRecords extends DTLSOverDatagram {
|
|||||||
// ClientHello with cookie
|
// ClientHello with cookie
|
||||||
needInvalidRecords.set(false);
|
needInvalidRecords.set(false);
|
||||||
System.out.println("invalidate ClientHello message");
|
System.out.println("invalidate ClientHello message");
|
||||||
if (ba[ba.length - 1] == (byte)0xFF) {
|
// We will alter the compression method field in order to make the cookie
|
||||||
ba[ba.length - 1] = (byte)0xFE;
|
// check fail.
|
||||||
|
ByteBuffer chRec = ByteBuffer.wrap(ba);
|
||||||
|
// Skip 59 bytes past the record header (13), the handshake header (12),
|
||||||
|
// the protocol version (2), and client random (32)
|
||||||
|
chRec.position(59);
|
||||||
|
// Jump past the session ID
|
||||||
|
int len = Byte.toUnsignedInt(chRec.get());
|
||||||
|
chRec.position(chRec.position() + len);
|
||||||
|
// Skip the cookie
|
||||||
|
len = Byte.toUnsignedInt(chRec.get());
|
||||||
|
chRec.position(chRec.position() + len);
|
||||||
|
// Skip past cipher suites
|
||||||
|
len = Short.toUnsignedInt(chRec.getShort());
|
||||||
|
chRec.position(chRec.position() + len);
|
||||||
|
// Read the data on the compression methods, should be at least 1
|
||||||
|
len = Byte.toUnsignedInt(chRec.get());
|
||||||
|
if (len >= 1) {
|
||||||
|
System.out.println("Detected compression methods (count = " + len + ")");
|
||||||
} else {
|
} else {
|
||||||
ba[ba.length - 1] = (byte)0xFF;
|
ba[ba.length - 1] = (byte)0xFF;
|
||||||
|
throw new RuntimeException("Got zero length comp methods");
|
||||||
}
|
}
|
||||||
|
// alter the first comp method.
|
||||||
|
int compMethodVal = Byte.toUnsignedInt(chRec.get(chRec.position()));
|
||||||
|
System.out.println("Changing value at position " + chRec.position() +
|
||||||
|
" from " + compMethodVal + " to " + ++compMethodVal);
|
||||||
|
chRec.put(chRec.position(), (byte)compMethodVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.createHandshakePacket(ba, socketAddr);
|
return super.createHandshakePacket(ba, socketAddr);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2015, 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
|
||||||
@ -34,7 +34,15 @@ public class MFLNTest extends SSLEngineTestCase {
|
|||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
setUpAndStartKDCIfNeeded();
|
setUpAndStartKDCIfNeeded();
|
||||||
System.setProperty("jsse.enableMFLNExtension", "true");
|
System.setProperty("jsse.enableMFLNExtension", "true");
|
||||||
for (int mfl = 4096; mfl >= 256; mfl /= 2) {
|
String testMode = System.getProperty("test.mode", "norm");
|
||||||
|
int mflLen;
|
||||||
|
if (testMode.equals("norm_sni")) {
|
||||||
|
mflLen = 512;
|
||||||
|
} else {
|
||||||
|
mflLen = 256;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int mfl = 4096; mfl >= mflLen; mfl /= 2) {
|
||||||
System.out.println("=============================================="
|
System.out.println("=============================================="
|
||||||
+ "==============");
|
+ "==============");
|
||||||
System.out.printf("Testsing DTLS handshake with MFL = %d%n", mfl);
|
System.out.printf("Testsing DTLS handshake with MFL = %d%n", mfl);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user