Skip to content

Commit 436dc68

Browse files
committed
8367059: DTLS: loss of NewSessionTicket message results in handshake failure
Reviewed-by: jnimeh, djelinski
1 parent 28f2591 commit 436dc68

File tree

6 files changed

+332
-45
lines changed

6 files changed

+332
-45
lines changed

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

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ Plaintext[] decode(ByteBuffer packet) throws SSLProtocolException {
170170

171171
// Buffer next epoch message if necessary.
172172
if (this.readEpoch < recordEpoch) {
173-
// Discard the record younger than the current epcoh if:
173+
// Discard the record younger than the current epoch if:
174174
// 1. it is not a handshake message, or
175175
// 3. it is not of next epoch.
176176
if ((contentType != ContentType.HANDSHAKE.id &&
@@ -1445,7 +1445,7 @@ boolean flightIsReady() {
14451445
//
14461446
if (expectCCSFlight) {
14471447
// Have the ChangeCipherSpec/Finished flight been received?
1448-
boolean isReady = hasFinishedMessage(bufferedFragments);
1448+
boolean isReady = hasFinishedMessage();
14491449
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) {
14501450
SSLLogger.fine(
14511451
"Has the final flight been received? " + isReady);
@@ -1492,7 +1492,7 @@ boolean flightIsReady() {
14921492
//
14931493
// an abbreviated handshake
14941494
//
1495-
if (hasFinishedMessage(bufferedFragments)) {
1495+
if (hasFinishedMessage()) {
14961496
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) {
14971497
SSLLogger.fine("It's an abbreviated handshake.");
14981498
}
@@ -1565,7 +1565,7 @@ boolean flightIsReady() {
15651565
}
15661566
}
15671567

1568-
if (!hasFinishedMessage(bufferedFragments)) {
1568+
if (!hasFinishedMessage()) {
15691569
// not yet have the ChangeCipherSpec/Finished messages
15701570
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) {
15711571
SSLLogger.fine(
@@ -1601,35 +1601,33 @@ boolean flightIsReady() {
16011601
return false;
16021602
}
16031603

1604-
// Looking for the ChangeCipherSpec and Finished messages.
1604+
// Looking for the ChangeCipherSpec, Finished and
1605+
// NewSessionTicket messages.
16051606
//
16061607
// As the cached Finished message should be a ciphertext, we don't
16071608
// exactly know a ciphertext is a Finished message or not. According
16081609
// to the spec of TLS/DTLS handshaking, a Finished message is always
16091610
// sent immediately after a ChangeCipherSpec message. The first
16101611
// ciphertext handshake message should be the expected Finished message.
1611-
private boolean hasFinishedMessage(Set<RecordFragment> fragments) {
1612-
1612+
private boolean hasFinishedMessage() {
16131613
boolean hasCCS = false;
16141614
boolean hasFin = false;
1615-
for (RecordFragment fragment : fragments) {
1615+
1616+
for (RecordFragment fragment : bufferedFragments) {
16161617
if (fragment.contentType == ContentType.CHANGE_CIPHER_SPEC.id) {
1617-
if (hasFin) {
1618-
return true;
1619-
}
16201618
hasCCS = true;
1621-
} else if (fragment.contentType == ContentType.HANDSHAKE.id) {
1622-
// Finished is the first expected message of a new epoch.
1623-
if (fragment.isCiphertext) {
1624-
if (hasCCS) {
1625-
return true;
1626-
}
1627-
hasFin = true;
1628-
}
1619+
} else if (fragment.contentType == ContentType.HANDSHAKE.id
1620+
&& fragment.isCiphertext) {
1621+
hasFin = true;
16291622
}
16301623
}
16311624

1632-
return false;
1625+
// NewSessionTicket message presence in the Finished flight
1626+
// should only be expected on the client side, and only
1627+
// if stateless resumption is enabled.
1628+
return hasCCS && hasFin && (!tc.sslConfig.isClientMode
1629+
|| !tc.handshakeContext.statelessResumption
1630+
|| hasCompleted(SSLHandshake.NEW_SESSION_TICKET.id));
16331631
}
16341632

16351633
// Is client CertificateVerify a mandatory message?
@@ -1674,7 +1672,7 @@ private boolean hasCompleted(
16741672
int presentMsgSeq, int endMsgSeq) {
16751673

16761674
// The caller should have checked the completion of the first
1677-
// present handshake message. Need not to check it again.
1675+
// present handshake message. Need not check it again.
16781676
for (RecordFragment rFrag : fragments) {
16791677
if ((rFrag.contentType != ContentType.HANDSHAKE.id) ||
16801678
rFrag.isCiphertext) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ enum SSLExtension implements SSLStringizer {
286286
ProtocolVersion.PROTOCOLS_10_12,
287287
SessionTicketExtension.shNetworkProducer,
288288
SessionTicketExtension.shOnLoadConsumer,
289-
null,
289+
SessionTicketExtension.shOnLoadAbsence,
290290
null,
291291
null,
292292
SessionTicketExtension.steStringizer),

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ final class SessionTicketExtension {
7272
new T12SHSessionTicketProducer();
7373
static final ExtensionConsumer shOnLoadConsumer =
7474
new T12SHSessionTicketConsumer();
75+
static final HandshakeAbsence shOnLoadAbsence =
76+
new T12SHSessionTicketOnLoadAbsence();
7577

7678
static final SSLStringizer steStringizer = new SessionTicketStringizer();
7779
// No need to compress a ticket if it can fit in a single packet.
@@ -529,4 +531,27 @@ public void consume(ConnectionContext context,
529531
chc.statelessResumption = true;
530532
}
531533
}
534+
535+
/**
536+
* The absence processing if a "session_ticket" extension is
537+
* not present in the ServerHello handshake message.
538+
*/
539+
private static final class T12SHSessionTicketOnLoadAbsence
540+
implements HandshakeAbsence {
541+
542+
@Override
543+
public void absent(ConnectionContext context,
544+
HandshakeMessage message) {
545+
ClientHandshakeContext chc = (ClientHandshakeContext) context;
546+
547+
// Disable stateless resumption if server doesn't send the extension.
548+
if (chc.statelessResumption) {
549+
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
550+
SSLLogger.info(
551+
"Server doesn't support stateless resumption");
552+
}
553+
chc.statelessResumption = false;
554+
}
555+
}
556+
}
532557
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright (c) 2025, 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 8367059
27+
* @summary DTLS: loss of NewSessionTicket message results in handshake failure
28+
* @modules java.base/sun.security.util
29+
* @library /test/lib
30+
* @build DTLSOverDatagram
31+
*
32+
* @comment Make sure client doesn't expect NewSessionTicket in the final
33+
* flight if server doesn't send the "session_ticket" extension with
34+
* ServerHello handshake message.
35+
*
36+
* @run main/othervm -Djdk.tls.client.enableSessionTicketExtension=false
37+
* DTLSNoNewSessionTicket
38+
* @run main/othervm -Djdk.tls.server.enableSessionTicketExtension=false
39+
* DTLSNoNewSessionTicket
40+
*/
41+
42+
public class DTLSNoNewSessionTicket extends DTLSOverDatagram {
43+
public static void main(String[] args) throws Exception {
44+
var testCase = new DTLSNoNewSessionTicket();
45+
testCase.runTest(testCase);
46+
}
47+
}

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

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@
2121
* questions.
2222
*/
2323

24-
// SunJSSE does not support dynamic system properties, no way to re-use
25-
// system properties in samevm/agentvm mode.
26-
2724
/*
2825
* @test
2926
* @bug 8043758
@@ -132,8 +129,15 @@ void doClientSide(DatagramSocket socket, InetSocketAddress serverSocketAddr)
132129
* The remainder is support stuff for DTLS operations.
133130
*/
134131
SSLEngine createSSLEngine(boolean isClient) throws Exception {
135-
SSLContext context = getDTLSContext();
136-
SSLEngine engine = context.createSSLEngine();
132+
SSLContext context =
133+
isClient ? getClientDTLSContext() : getServerDTLSContext();
134+
135+
// Note: client and server ports are not to be used for network
136+
// communication, but only to be set in client and server SSL engines.
137+
// We must use the same context, host and port for initial and resuming
138+
// sessions when testing session resumption (abbreviated handshake).
139+
SSLEngine engine = context.createSSLEngine("localhost",
140+
isClient ? 51111 : 52222);
137141

138142
SSLParameters paras = engine.getSSLParameters();
139143
paras.setMaximumPacketSize(MAXIMUM_PACKET_SIZE);
@@ -507,7 +511,7 @@ boolean onReceiveTimeout(SSLEngine engine, SocketAddress socketAddr,
507511
}
508512

509513
// get DTSL context
510-
SSLContext getDTLSContext() throws Exception {
514+
static SSLContext getDTLSContext() throws Exception {
511515
String passphrase = "passphrase";
512516
return SSLContextBuilder.builder()
513517
.trustStore(KeyStoreUtils.loadKeyStore(TRUST_FILENAME, passphrase))
@@ -517,6 +521,14 @@ SSLContext getDTLSContext() throws Exception {
517521
.build();
518522
}
519523

524+
protected SSLContext getServerDTLSContext() throws Exception {
525+
return getDTLSContext();
526+
}
527+
528+
protected SSLContext getClientDTLSContext() throws Exception {
529+
return getDTLSContext();
530+
}
531+
520532

521533
/*
522534
* =============================================================

0 commit comments

Comments
 (0)