JDK-8263571 : Final CCS and Finished DTLS messages can't be re-transmitted
  • Type: Bug
  • Component: security-libs
  • Sub-Component: javax.net.ssl
  • Affected Version: 17
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • Submitted: 2021-03-08
  • Updated: 2022-12-06
Related Reports
Relates :  
Relates :  
Description
ADDITIONAL SYSTEM INFORMATION :
Tested against jdk 9,14,15,17 with same result

A DESCRIPTION OF THE PROBLEM :
The final CCC and finished message cannot be retransmitted.
It is an implementation bug.  Every handshake message should be able to get retransmitted.

The bug has been addressed, but not fixed by:

"JDK-8167680 : DTLS implementation bugs" (https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8167680) was implemented to address the issue of retransmitting Final CCS and Finished DTLS messages addressing "JDK-8163419 : Final CCS and Finished DTLS messages can't be re-transmitted" (https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8163419) 

The implementation is now sending an extra set of CCS and Finished packets regardless if there is packet loss or not, which makes the test-case work (https://github.com/AdoptOpenJDK/openjdk-jdk/blob/master/test/jdk/javax/net/ssl/DTLS/PacketLossRetransmission.java) due to the test only dropping one packets, but it cannot recover if the extra set of CCS and Finished are also lost. In addition always sending an extra set of CCS and Finished is adding unnecessary overhead.

Example

    Client                 Server
               ....          
       -- ClientKeyExchange -->
       -- ChangeCipherSpec  -->
       -- Finished          -->


       X <-- ChangeCipherSpec --
       X <-- Finished         --
       X <-- ChangeCipherSpec --
       X <-- Finished         --

Client repeatedly sends last flight
       -----    ...        --->
       -- ClientKeyExchange -->
       -- ChangeCipherSpec  -->
       -- Finished          -->

Server ignores because handshake it complete

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
A modified version of PacketLossRetransmission has been added, which takes the number of packets to drop as parameter. "Actual Result" shows the result of PacketLossRetransmission being run with scenario "drop finished packets 2 times"  
Params : -Djdk.tls.client.enableSessionTicketExtension=false server 20 2

DTLSOverDatagram was modified (not included) to print content type and handshake types.
Server socket timeout was increased from 10s to 20s to allow for client retransmission 
Line 565: serverSocket.setSoTimeout(SOCKET_TIMEOUT*2);

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
After receiving the client retransmission the server sends:
Server: ----produce handshake packet(99, OK, NEED_WRAP)----CHANGE_CIPHER_SPEC 
Server: ----produce handshake packet(98, OK, NEED_WRAP)----HANDSHAKE FINISHED
ACTUAL -
Server: =======handshake(199, NEED_UNWRAP)=======
Server: Receive DTLS records, handshake status is NEED_UNWRAP
Client: =======handshake(199, NEED_WRAP)=======
Client: ----produce handshake packet(99, OK, NEED_UNWRAP)----HANDSHAKE CLIENT_HELLO
Client: Produced 1 packets
Client: =======handshake(198, NEED_UNWRAP)=======
Client: Receive DTLS records, handshake status is NEED_UNWRAP
Server: =======handshake(198, NEED_TASK)=======
Server: =======handshake(197, NEED_WRAP)=======
Server: ----produce handshake packet(99, OK, NEED_UNWRAP)----HANDSHAKE HELLO_VERIFY_REQUEST
Server: Produced 1 packets
Server: =======handshake(196, NEED_UNWRAP)=======
Server: Receive DTLS records, handshake status is NEED_UNWRAP
Client: =======handshake(197, NEED_TASK)=======
Client: =======handshake(196, NEED_WRAP)=======
Client: ----produce handshake packet(99, OK, NEED_UNWRAP)----HANDSHAKE CLIENT_HELLO
Client: Produced 1 packets
Client: =======handshake(195, NEED_UNWRAP)=======
Client: Receive DTLS records, handshake status is NEED_UNWRAP
Server: =======handshake(195, NEED_TASK)=======
Server: =======handshake(194, NEED_WRAP)=======
Server: ----produce handshake packet(99, OK, NEED_WRAP)----HANDSHAKE SERVER_HELLO
Server: ----produce handshake packet(98, OK, NEED_WRAP)----HANDSHAKE CERTIFICATE
Server: ----produce handshake packet(97, OK, NEED_WRAP)----HANDSHAKE SERVER_KEY_EXCHANGE
Server: ----produce handshake packet(96, OK, NEED_UNWRAP)----HANDSHAKE SERVER_HELLO_DONE
Server: Produced 4 packets
Server: =======handshake(193, NEED_UNWRAP)=======
Server: Receive DTLS records, handshake status is NEED_UNWRAP
Client: =======handshake(194, NEED_UNWRAP)=======
Client: Receive DTLS records, handshake status is NEED_UNWRAP
Client: =======handshake(193, NEED_UNWRAP)=======
Client: Receive DTLS records, handshake status is NEED_UNWRAP
Client: =======handshake(192, NEED_UNWRAP)=======
Client: Receive DTLS records, handshake status is NEED_UNWRAP
Client: =======handshake(191, NEED_TASK)=======
Client: =======handshake(190, NEED_UNWRAP_AGAIN)=======
Client: Receive DTLS records, handshake status is NEED_UNWRAP_AGAIN
Client: =======handshake(189, NEED_TASK)=======
Client: =======handshake(188, NEED_UNWRAP_AGAIN)=======
Client: Receive DTLS records, handshake status is NEED_UNWRAP_AGAIN
Client: =======handshake(187, NEED_TASK)=======
Client: =======handshake(186, NEED_UNWRAP_AGAIN)=======
Client: Receive DTLS records, handshake status is NEED_UNWRAP_AGAIN
Client: =======handshake(185, NEED_TASK)=======
Client: =======handshake(184, NEED_WRAP)=======
Client: ----produce handshake packet(99, OK, NEED_WRAP)----HANDSHAKE CLIENT_KEY_EXCHANGE
Client: ----produce handshake packet(98, OK, NEED_WRAP)----CHANGE_CIPHER_SPEC 
Client: ----produce handshake packet(97, OK, NEED_UNWRAP)----HANDSHAKE FINISHED
Client: Produced 3 packets
Client: =======handshake(183, NEED_UNWRAP)=======
Client: Receive DTLS records, handshake status is NEED_UNWRAP
Server: =======handshake(192, NEED_UNWRAP)=======
Server: Receive DTLS records, handshake status is NEED_UNWRAP
Server: =======handshake(191, NEED_UNWRAP)=======
Server: Receive DTLS records, handshake status is NEED_UNWRAP
Server: =======handshake(190, NEED_TASK)=======
Server: =======handshake(189, NEED_UNWRAP_AGAIN)=======
Server: Receive DTLS records, handshake status is NEED_UNWRAP_AGAIN
Server: =======handshake(188, NEED_UNWRAP_AGAIN)=======
Server: Receive DTLS records, handshake status is NEED_UNWRAP_AGAIN
Server: =======handshake(187, NEED_WRAP)=======
Server: ----produce handshake packet(99, OK, NEED_WRAP)----CHANGE_CIPHER_SPEC 
Server: ----produce handshake packet(98, OK, NEED_WRAP)----HANDSHAKE FINISHED
Server: ----produce handshake packet(97, OK, NEED_WRAP)----CHANGE_CIPHER_SPEC 
Server: ----produce handshake packet(96, OK, FINISHED)----HANDSHAKE FINISHED
Server: Produce handshake packets: Handshake status is FINISHED, finish the loop
Loss a packet of handshake message 
Loss a packet of handshake message 
Server: Produced 2 packets
Server: Handshake status is FINISHED after producing handshake packets, finish the loop
Server: Handshake finished, status is NOT_HANDSHAKING
Client: =======handshake(182, NEED_UNWRAP)=======
Client: Receive DTLS records, handshake status is NEED_UNWRAP
Server: Negotiated protocol is DTLSv1.2
Client: =======handshake(181, NEED_UNWRAP)=======
Server: Negotiated cipher suite is TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
Client: Receive DTLS records, handshake status is NEED_UNWRAP
Client: Warning: java.net.SocketTimeoutException: Receive timed out
Client: ----produce handshake packet(99, OK, NEED_WRAP)----HANDSHAKE CLIENT_KEY_EXCHANGE
Client: ----produce handshake packet(98, OK, NEED_WRAP)----CHANGE_CIPHER_SPEC 
Client: ----produce handshake packet(97, OK, NEED_UNWRAP)----HANDSHAKE FINISHED
Client: Reproduced 3 packets
Reproduced packet
Reproduced packet
Reproduced packet
Client: New handshake status is NEED_UNWRAP
Client: =======handshake(180, NEED_UNWRAP)=======
Client: Receive DTLS records, handshake status is NEED_UNWRAP
Server: Received packet no data - Engine status is Status = OK HandshakeStatus = NOT_HANDSHAKING
bytesConsumed = 58 bytesProduced = 0 packet type: HANDSHAKE
Server: Received packet no data - Engine status is Status = OK HandshakeStatus = NOT_HANDSHAKING
bytesConsumed = 14 bytesProduced = 0 packet type: CHANGE_CIPHER_SPEC
Server: Received packet no data - Engine status is Status = OK HandshakeStatus = NOT_HANDSHAKING
bytesConsumed = 61 bytesProduced = 0 packet type: HANDSHAKE
Client: Warning: java.net.SocketTimeoutException: Receive timed out
Client: ----produce handshake packet(99, OK, NEED_WRAP)----HANDSHAKE CLIENT_KEY_EXCHANGE
Client: ----produce handshake packet(98, OK, NEED_WRAP)----CHANGE_CIPHER_SPEC 
Client: ----produce handshake packet(97, OK, NEED_UNWRAP)----HANDSHAKE FINISHED
Client: Reproduced 3 packets
Reproduced packet
Reproduced packet
Reproduced packet
Client: New handshake status is NEED_UNWRAP
Client: =======handshake(179, NEED_UNWRAP)=======
Server: Received packet no data - Engine status is Status = OK HandshakeStatus = NOT_HANDSHAKING
bytesConsumed = 58 bytesProduced = 0 packet type: HANDSHAKE
Client: Receive DTLS records, handshake status is NEED_UNWRAP
Server: Received packet no data - Engine status is Status = OK HandshakeStatus = NOT_HANDSHAKING
bytesConsumed = 14 bytesProduced = 0 packet type: CHANGE_CIPHER_SPEC
Server: Received packet no data - Engine status is Status = OK HandshakeStatus = NOT_HANDSHAKING
bytesConsumed = 61 bytesProduced = 0 packet type: HANDSHAKE
Client: Warning: java.net.SocketTimeoutException: Receive timed out
Client: ----produce handshake packet(99, OK, NEED_WRAP)----HANDSHAKE CLIENT_KEY_EXCHANGE
Client: ----produce handshake packet(98, OK, NEED_WRAP)----CHANGE_CIPHER_SPEC 
Client: ----produce handshake packet(97, OK, NEED_UNWRAP)----HANDSHAKE FINISHED
Client: Reproduced 3 packets
Reproduced packet
Reproduced packet
Reproduced packet
Client: New handshake status is NEED_UNWRAP
Server: Received packet no data - Engine status is Status = OK HandshakeStatus = NOT_HANDSHAKING
bytesConsumed = 58 bytesProduced = 0 packet type: HANDSHAKE
Client: =======handshake(178, NEED_UNWRAP)=======
Client: Receive DTLS records, handshake status is NEED_UNWRAP
Server: Received packet no data - Engine status is Status = OK HandshakeStatus = NOT_HANDSHAKING
bytesConsumed = 14 bytesProduced = 0 packet type: CHANGE_CIPHER_SPEC
Server: Received packet no data - Engine status is Status = OK HandshakeStatus = NOT_HANDSHAKING
bytesConsumed = 61 bytesProduced = 0 packet type: HANDSHAKE


---------- BEGIN SOURCE ----------
public class PacketLossRetransmission extends DTLSOverDatagram {
    private static boolean isClient;
    private static byte handshakeType;

    private static int packetsToDrop;

    public static void main(String[] args) throws Exception {
        isClient = args[0].equals("client");
        handshakeType = Byte.valueOf(args[1]);
        packetsToDrop = Integer.parseInt(args[2]);

        PacketLossRetransmission testCase = new PacketLossRetransmission();
        testCase.runTest(testCase);
    }

    @Override
    boolean produceHandshakePackets(SSLEngine engine, SocketAddress socketAddr,
                                    String side, List<DatagramPacket> packets) throws Exception {

        boolean finished = super.produceHandshakePackets(
                engine, socketAddr, side, packets);

        Iterator<DatagramPacket> packetIterator = packets.iterator();
        while (packetIterator.hasNext()) {
            DatagramPacket packet = packetIterator.next();
            if ((packetsToDrop > 0) && (!(isClient ^ engine.getUseClientMode()))) {
                packet = getPacket(Collections.singletonList(packet), handshakeType);
                if (packet != null) {
                    packetsToDrop--;
                    System.out.println("Loss a packet of handshake message ");
                    packetIterator.remove();
                }
            }
        }

        return finished;
    }
}
---------- END SOURCE ----------

FREQUENCY : always



Comments
The observation on Windows 10: Command: c:\jdk-17eab6\bin\java -jar c:\jtreg\lib\jtreg.jar -v -a test\jdk\javax\net\ssl\DTLS\PacketLossRetransmission.java Results: JDK 17ea+6: Failed, on @run main/othervm PacketLossRetransmission server 20 2 finished @run main/othervm PacketLossRetransmission server -1 2 change_cipher_spec
19-03-2021

Additional information from the submitter: The bug is reproduced. jtreg seems to stop on the first failing test, if you switch the order you will see it failing on @run main/othervm PacketLossRetransmission server -1 2 change_cipher_spec also.
19-03-2021

Additional information from the submitter: I got the source from https://github.com/openjdk/jdk Apply the patch and run the following command: bash jtreg -verbose:summary test/jdk/javax/net/ssl/DTLS/PacketLossRetransmission.java It fails on @run main/othervm PacketLossRetransmission server 20 2 finished and @run main/othervm PacketLossRetransmission server -1 2 change_cipher_spec
15-03-2021

Requested more details of the reproducer from the submitter.
09-03-2021