Merge pull request #6787 from yuhaoth/pr/workaround-gnutls_anti_replay_fail

TLS 1.3: EarlyData: Workaround anti replay fail from GnuTLS
diff --git a/ChangeLog.d/workaround_gnutls_anti_replay_fail.txt b/ChangeLog.d/workaround_gnutls_anti_replay_fail.txt
new file mode 100644
index 0000000..cebc2b7
--- /dev/null
+++ b/ChangeLog.d/workaround_gnutls_anti_replay_fail.txt
@@ -0,0 +1,7 @@
+Bugfix
+    * In TLS 1.3, when using a ticket for session resumption, tweak its age
+      calculation on the client side. It prevents a server with more accurate
+      ticket timestamps (typically timestamps in milliseconds) compared to the
+      Mbed TLS ticket timestamps (in seconds) to compute a ticket age smaller
+      than the age computed and transmitted by the client and thus potentially
+      reject the ticket. Fix #6623.
diff --git a/library/ssl_tls13_client.c b/library/ssl_tls13_client.c
index f07c8b6..025183d 100644
--- a/library/ssl_tls13_client.c
+++ b/library/ssl_tls13_client.c
@@ -946,6 +946,21 @@
         uint32_t obfuscated_ticket_age =
                                 (uint32_t)( now - session->ticket_received );
 
+        /*
+         * The ticket timestamp is in seconds but the ticket age is in
+         * milliseconds. If the ticket was received at the end of a second and
+         * re-used here just at the beginning of the next second, the computed
+         * age `now - session->ticket_received` is equal to 1s thus 1000 ms
+         * while the actual age could be just a few milliseconds or tens of
+         * milliseconds. If the server has more accurate ticket timestamps
+         * (typically timestamps in milliseconds), as part of the processing of
+         * the ClientHello, it may compute a ticket lifetime smaller than the
+         * one computed here and potentially reject the ticket. To avoid that,
+         * remove one second to the ticket age if possible.
+         */
+        if( obfuscated_ticket_age > 0 )
+            obfuscated_ticket_age -= 1;
+
         obfuscated_ticket_age *= 1000;
         obfuscated_ticket_age += session->ticket_age_add;
 
diff --git a/programs/ssl/ssl_client2.c b/programs/ssl/ssl_client2.c
index 02ee7cf..304d10c 100644
--- a/programs/ssl/ssl_client2.c
+++ b/programs/ssl/ssl_client2.c
@@ -424,7 +424,7 @@
     "    reconnect=%%d        number of reconnections using session resumption\n" \
     "                        default: 0 (disabled)\n"       \
     "    reco_server_name=%%s  default: NULL\n"             \
-    "    reco_delay=%%d       default: 0 seconds\n"         \
+    "    reco_delay=%%d       default: 0 millionseconds\n"         \
     "    reco_mode=%%d        0: copy session, 1: serialize session\n" \
     "                        default: 1\n"      \
     "    reconnect_hard=%%d   default: 0 (disabled)\n"      \
@@ -3184,7 +3184,7 @@
 
 #if defined(MBEDTLS_TIMING_C)
         if( opt.reco_delay > 0 )
-            mbedtls_net_usleep( 1000000 * opt.reco_delay );
+            mbedtls_net_usleep( 1000 * opt.reco_delay );
 #endif
 
         mbedtls_printf( "  . Reconnecting with saved session..." );
diff --git a/tests/opt-testcases/tls13-misc.sh b/tests/opt-testcases/tls13-misc.sh
index 710fb34..3aaf3f3 100755
--- a/tests/opt-testcases/tls13-misc.sh
+++ b/tests/opt-testcases/tls13-misc.sh
@@ -264,9 +264,6 @@
             0 \
             -s "key exchange mode: ephemeral$"
 
-# skip the basic check now cause it will randomly trigger the anti-replay protection in gnutls_server
-# Add it back once we fix the issue
-skip_next_test
 requires_gnutls_tls1_3
 requires_config_enabled MBEDTLS_DEBUG_C
 requires_config_enabled MBEDTLS_SSL_CLI_C
@@ -277,7 +274,7 @@
                              MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_ENABLED
 run_test    "TLS 1.3 m->G: EarlyData: basic check, good" \
             "$G_NEXT_SRV -d 10 --priority=NORMAL:-VERS-ALL:+VERS-TLS1.3:+CIPHER-ALL:+ECDHE-PSK:+PSK --earlydata --disable-client-cert" \
-            "$P_CLI debug_level=4 early_data=1 reco_mode=1 reconnect=1 reco_delay=2" \
+            "$P_CLI debug_level=4 early_data=1 reco_mode=1 reconnect=1 reco_delay=900" \
             1 \
             -c "Reconnecting with saved session" \
             -c "NewSessionTicket: early_data(42) extension received." \
@@ -298,7 +295,7 @@
                              MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_ENABLED
 run_test    "TLS 1.3 m->G: EarlyData: no early_data in NewSessionTicket, good" \
             "$G_NEXT_SRV -d 10 --priority=NORMAL:-VERS-ALL:+VERS-TLS1.3:+CIPHER-ALL:+ECDHE-PSK:+PSK --disable-client-cert" \
-            "$P_CLI debug_level=4 early_data=1 reco_mode=1 reconnect=1 reco_delay=2" \
+            "$P_CLI debug_level=4 early_data=1 reco_mode=1 reconnect=1" \
             0 \
             -c "Reconnecting with saved session" \
             -C "NewSessionTicket: early_data(42) extension received." \
diff --git a/tests/ssl-opt.sh b/tests/ssl-opt.sh
index fdd3662..c206283 100755
--- a/tests/ssl-opt.sh
+++ b/tests/ssl-opt.sh
@@ -3643,7 +3643,7 @@
 requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2
 run_test    "Session resume using tickets: timeout" \
             "$P_SRV debug_level=3 tickets=1 cache_max=0 ticket_timeout=1" \
-            "$P_CLI debug_level=3 tickets=1 reconnect=1 reco_delay=2" \
+            "$P_CLI debug_level=3 tickets=1 reconnect=1 reco_delay=2000" \
             0 \
             -c "client hello, adding session ticket extension" \
             -s "found session ticket extension" \
@@ -3953,7 +3953,7 @@
 requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2
 run_test    "Session resume using tickets, DTLS: timeout" \
             "$P_SRV debug_level=3 dtls=1 tickets=1 cache_max=0 ticket_timeout=1" \
-            "$P_CLI debug_level=3 dtls=1 tickets=1 reconnect=1 skip_close_notify=1 reco_delay=2" \
+            "$P_CLI debug_level=3 dtls=1 tickets=1 reconnect=1 skip_close_notify=1 reco_delay=2000" \
             0 \
             -c "client hello, adding session ticket extension" \
             -s "found session ticket extension" \
@@ -4077,7 +4077,7 @@
 requires_config_enabled MBEDTLS_SSL_CACHE_C
 run_test    "Session resume using cache: timeout < delay" \
             "$P_SRV debug_level=3 tickets=0 cache_timeout=1" \
-            "$P_CLI debug_level=3 tickets=0 reconnect=1 reco_delay=2" \
+            "$P_CLI debug_level=3 tickets=0 reconnect=1 reco_delay=2000" \
             0 \
             -S "session successfully restored from cache" \
             -S "session successfully restored from ticket" \
@@ -4088,7 +4088,7 @@
 requires_config_enabled MBEDTLS_SSL_CACHE_C
 run_test    "Session resume using cache: no timeout" \
             "$P_SRV debug_level=3 tickets=0 cache_timeout=0" \
-            "$P_CLI debug_level=3 tickets=0 reconnect=1 reco_delay=2" \
+            "$P_CLI debug_level=3 tickets=0 reconnect=1 reco_delay=2000" \
             0 \
             -s "session successfully restored from cache" \
             -S "session successfully restored from ticket" \
@@ -4224,7 +4224,7 @@
 requires_config_enabled MBEDTLS_SSL_CACHE_C
 run_test    "Session resume using cache, DTLS: timeout < delay" \
             "$P_SRV dtls=1 debug_level=3 tickets=0 cache_timeout=1" \
-            "$P_CLI dtls=1 debug_level=3 tickets=0 reconnect=1 skip_close_notify=1 reco_delay=2" \
+            "$P_CLI dtls=1 debug_level=3 tickets=0 reconnect=1 skip_close_notify=1 reco_delay=2000" \
             0 \
             -S "session successfully restored from cache" \
             -S "session successfully restored from ticket" \
@@ -4235,7 +4235,7 @@
 requires_config_enabled MBEDTLS_SSL_CACHE_C
 run_test    "Session resume using cache, DTLS: no timeout" \
             "$P_SRV dtls=1 debug_level=3 tickets=0 cache_timeout=0" \
-            "$P_CLI dtls=1 debug_level=3 tickets=0 reconnect=1 skip_close_notify=1 reco_delay=2" \
+            "$P_CLI dtls=1 debug_level=3 tickets=0 reconnect=1 skip_close_notify=1 reco_delay=2000" \
             0 \
             -s "session successfully restored from cache" \
             -S "session successfully restored from ticket" \
@@ -9891,7 +9891,7 @@
              key_file=data_files/server8.key \
              hs_timeout=10000-60000 \
              force_ciphersuite=TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256 \
-             mtu=1450 reconnect=1 skip_close_notify=1 reco_delay=1" \
+             mtu=1450 reconnect=1 skip_close_notify=1 reco_delay=1000" \
             0 \
             -S "autoreduction" \
             -s "found fragmented DTLS handshake message" \