]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
Reject TLS 1.3 early data when PSK ciphersuite differs master
authorMounir IDRASSI <mounir.idrassi@idrix.fr>
Sat, 6 Jun 2026 13:08:42 +0000 (22:08 +0900)
committerNorbert Pocs <norbertp@openssl.org>
Wed, 5 Aug 2026 08:16:38 +0000 (10:16 +0200)
TLS 1.3 PSK resumption can use any ciphersuite with the same hash, but 0-RTT
acceptance requires the selected ciphersuite to match the one associated with
the selected PSK.

Keep same-hash PSK resumption working and reject only early data on a
ciphersuite mismatch. Document the user-visible fix in CHANGES.md.

Also updated RFC8446 references in ssl/*.[ch] code comments to corresponding
RFC9846 locations.

Fixes #31803

Reviewed-by: Bob Beck <beck@openssl.org>
Reviewed-by: Norbert Pocs <norbertp@openssl.org>
MergeDate: Wed Aug  5 08:16:57 2026
(Merged from https://github.com/openssl/openssl/pull/32032)

CHANGES.md
ssl/statem/extensions_srvr.c
ssl/statem/statem_clnt.c
ssl/statem/statem_lib.c
ssl/statem/statem_local.h
ssl/statem/statem_srvr.c
ssl/t1_lib.c
test/sslapitest.c

index 4e5b021968460e711da2ae1b546a0e0c931f134d..fb46f8afea9b9186351e7a178b695d07bdae1803 100644 (file)
@@ -101,6 +101,12 @@ OpenSSL Releases
 
    *Jakub Zelenka*
 
+ * Fixed TLS 1.3 servers to reject early data when the selected ciphersuite
+   differs from the ciphersuite associated with the selected PSK. Same-hash
+   PSK resumption can still continue without accepting 0-RTT data.
+
+   *Mounir IDRASSI*
+
  * Added support for Ed25519 and Ed448 certificates in DTLS 1.2. Previously,
    these certificate types were only supported in TLS 1.2 and TLS 1.3.
 
index a597085e1b6cfd4d32c1ffaf1c418625aeccb8df..802081045e222056b78826d1999650f385a302b1 100644 (file)
@@ -702,7 +702,7 @@ static KS_EXTRACTION_RESULT extract_keyshares(SSL_CONNECTION *s, PACKET *key_sha
 
         /*
          * Check if this share is in supported_groups sent from client
-         * RFC 8446 also mandates that clients send keyshares in the same
+         * RFC 9846 also mandates that clients send keyshares in the same
          * order as listed in the supported groups extension, but its not
          * required that the server check that, and some clients violate this
          * so instead of failing the connection when that occurs, log a trace
@@ -714,7 +714,7 @@ static KS_EXTRACTION_RESULT extract_keyshares(SSL_CONNECTION *s, PACKET *key_sha
         }
 
         if (key_share_pos < previous_key_share_pos)
-            OSSL_TRACE1(TLS, "key share group id %d is out of RFC 8446 order\n", group_id);
+            OSSL_TRACE1(TLS, "key share group id %d is out of RFC 9846 order\n", group_id);
 
         previous_key_share_pos = key_share_pos;
 
@@ -1511,6 +1511,13 @@ int tls_parse_ctos_psk(SSL_CONNECTION *s, PACKET *pkt, unsigned int context,
             s->ext.ticket_expected = 1;
             continue;
         }
+        /*
+         * Same-hash ciphersuite changes are allowed for TLSv1.3 PSK
+         * resumption, but RFC 9846 Section 4.3.10 requires the selected
+         * ciphersuite to match the selected PSK before accepting early data.
+         */
+        if (sess->cipher->id != s->s3.tmp.new_cipher->id)
+            s->ext.early_data_ok = 0;
         break;
     }
 
@@ -1525,7 +1532,7 @@ int tls_parse_ctos_psk(SSL_CONNECTION *s, PACKET *pkt, unsigned int context,
         }
         /*
          * decrypt_error here to keep the alert the same as if the binder
-         * failed. See RFC8446 Appendix E.6. Note we make no attempt to do this
+         * failed. See RFC9846 Appendix F.6. Note we make no attempt to do this
          * in constant time compared to verifying the binder. None of this code
          * is constant time anyway.
          */
index d719ef8a8455e5ace4d7887e32828b7d1fba4324..dc99eccea8ad356eaeacd7816b6b78e2b284e113 100644 (file)
@@ -3179,7 +3179,7 @@ MSG_PROCESS_RETURN tls_process_new_session_ticket(SSL_CONNECTION *s,
         PACKET extpkt;
 
         /*
-         * Fulfilling RFC8446:4.6.1 requirement: Clients MUST NOT cache
+         * Fulfilling RFC9846:4.7.1 requirement: Clients MUST NOT cache
          * tickets for longer than 7 days.
          */
         if (ticket_lifetime_hint > 604800) {
index 465d2eff33c97b5c8cf5aa97b80f92885df0d37e..ed71defac1ad0c5747cb97d4c6da90294ea21029 100644 (file)
@@ -1126,7 +1126,7 @@ int tls_process_rpk(SSL_CONNECTION *sc, PACKET *pkt, EVP_PKEY **peer_rpk)
      * ----------------------------
      * TLS 1.3 Certificate message:
      * ----------------------------
-     * https://datatracker.ietf.org/doc/html/rfc8446#section-4.4.2
+     * https://datatracker.ietf.org/doc/html/rfc9846#section-4.5.1
      *
      *   enum {
      *       X509(0),
@@ -1961,7 +1961,7 @@ static int is_tls13_capable(const SSL_CONNECTION *s)
         /*
          * Prior to TLSv1.3 sig algs allowed any curve to be used. TLSv1.3 is
          * more restrictive so check that our sig algs are consistent with this
-         * EC cert. See section 4.2.3 of RFC8446.
+         * EC cert. See section 4.3.3 of RFC9846.
          */
         curve = ssl_get_EC_curve_nid(s->cert->pkeys[SSL_PKEY_ECC].privatekey);
         if (tls_check_sigalg_curve(s, curve))
index de9ff299be5f663b547a00d1613fc0b3cb14cb41..ffb1d428fd8fad61080496145d5ccff33218fadc 100644 (file)
 #define KEY_UPDATE_MAX_LENGTH 1
 #define CCS_MAX_LENGTH 1
 
-/* Max ServerHello size permitted by RFC 8446 */
+/* Max ServerHello size permitted by RFC 9846 */
 #define SERVER_HELLO_MAX_LENGTH 65607
 
-/* Max CertificateVerify size permitted by RFC 8446 */
+/* Max CertificateVerify size permitted by RFC 9846 */
 #define CERTIFICATE_VERIFY_MAX_LENGTH 65539
 
 /* Max should actually be 36 but we are generous */
index fc3769a017b738bd6c3944b4469dea385c591ced..11b8df52ee8139c38d9ca8ea8f245ee171f597e7 100644 (file)
@@ -701,7 +701,7 @@ static WRITE_TRAN ossl_statem_server13_write_transition(SSL_CONNECTION *s)
          * parameters such as new_session_count = 0 or resumption_count = 0, is
          * effectively signaling no interest in session tickets or resumption.
          *
-         * RFC 8446 section 4.2.9: Servers MUST NOT select a key exchange mode
+         * RFC 9846 section 4.3.9: Servers MUST NOT select a key exchange mode
          * that is not listed by the client. This extension also restricts the
          * modes for use with PSK resumption. Servers SHOULD NOT send
          * NewSessionTicket with tickets that are not compatible with the
@@ -4243,7 +4243,7 @@ static int create_ticket_prequel(SSL_CONNECTION *s, WPACKET *pkt,
     /*
      * Ticket lifetime hint:
      * In TLSv1.3 we reset the "time" field above, and always specify the
-     * timeout, limited to a 1 week period per RFC8446.
+     * timeout, limited to a 1 week period per RFC9846.
      * For TLSv1.2 this is advisory only and we leave this unspecified for
      * resumed session (for simplicity).
      */
index f8056c82da96dafec07ca246d628ebd5045b4259..15a4e0959154f8e001ef1bae69140ad979c3aea9 100644 (file)
@@ -4080,7 +4080,7 @@ static int tls1_check_sig_alg(SSL_CONNECTION *s, X509 *x, int default_nid)
     size_t sigalgslen;
 
     /*-
-     * RFC 8446, section 4.2.3:
+     * RFC 9846, section 4.3.3:
      *
      * The signatures on certificates that are self-signed or certificates
      * that are trust anchors are not validated, since they begin a
index 1c7620165401fa098fc9e2115bf11ef4bdaa0bec..b6081be702841be762fdcf25dbea222dda9db1fe 100644 (file)
@@ -5290,6 +5290,76 @@ end:
     return testresult;
 }
 
+/*
+ * Locks in the requirement that a resumed PSK's exact ciphersuite, not
+ * merely a shared digest, must match the negotiated one before 0-RTT data
+ * is accepted: the client encrypts its early data with the AEAD bound to
+ * its own PSK session before it can know what cipher the server will
+ * negotiate, so a same-digest-but-different-cipher negotiation must fall
+ * back to an ordinary connection rather than attempt decryption with the
+ * wrong cipher.
+ */
+static int test_early_data_psk_cipher_mismatch(void)
+{
+#if !defined(OPENSSL_NO_CHACHA) && !defined(OPENSSL_NO_POLY1305)
+    SSL_CTX *cctx = NULL, *sctx = NULL;
+    SSL *clientssl = NULL, *serverssl = NULL;
+    int testresult = 0;
+    SSL_SESSION *sess = NULL;
+    unsigned char buf[20];
+    size_t readbytes, written;
+
+    if (is_fips) {
+        testresult = TEST_skip("CHACHA is not supported in FIPS");
+        return 1;
+    }
+
+    if (!TEST_true(setupearly_data_test(&cctx, &sctx, &clientssl,
+            &serverssl, &sess, 2, SHA256_DIGEST_LENGTH)))
+        goto end;
+
+    /*
+     * The PSK is bound to AES-128-GCM (SHA256 digest), but both ends can
+     * only negotiate ChaCha20-Poly1305 -- same digest, different cipher.
+     */
+    if (!TEST_true(SSL_set_ciphersuites(clientssl,
+            "TLS_CHACHA20_POLY1305_SHA256"))
+        || !TEST_true(SSL_set_ciphersuites(serverssl,
+            "TLS_CHACHA20_POLY1305_SHA256")))
+        goto end;
+
+    SSL_set_connect_state(clientssl);
+    if (!TEST_true(SSL_write_early_data(clientssl, MSG1, strlen(MSG1),
+            &written)))
+        goto end;
+
+    if (!TEST_int_eq(SSL_read_early_data(serverssl, buf, sizeof(buf),
+                         &readbytes),
+            SSL_READ_EARLY_DATA_FINISH)
+        || !TEST_int_eq(SSL_get_early_data_status(serverssl),
+            SSL_EARLY_DATA_REJECTED))
+        goto end;
+
+    if (!TEST_true(create_ssl_connection(serverssl, clientssl,
+            SSL_ERROR_NONE)))
+        goto end;
+
+    testresult = 1;
+end:
+    SSL_SESSION_free(sess);
+    SSL_SESSION_free(clientpsk);
+    SSL_SESSION_free(serverpsk);
+    clientpsk = serverpsk = NULL;
+    SSL_free(serverssl);
+    SSL_free(clientssl);
+    SSL_CTX_free(sctx);
+    SSL_CTX_free(cctx);
+    return testresult;
+#else
+    return 1;
+#endif
+}
+
 /*
  * Test that a server that doesn't try to read early data can handle a
  * client sending some.
@@ -15537,6 +15607,7 @@ int setup_tests(void)
     ADD_ALL_TESTS(test_early_data_not_sent, 3);
     ADD_ALL_TESTS(test_early_data_psk, 8);
     ADD_ALL_TESTS(test_early_data_psk_with_all_ciphers, 7);
+    ADD_TEST(test_early_data_psk_cipher_mismatch);
     ADD_ALL_TESTS(test_early_data_not_expected, 3);
 #ifndef OPENSSL_NO_TLS1_2
     ADD_ALL_TESTS(test_early_data_tls1_2, 3);