Repository: wolfSSL/wolfssl
Pull request #8487: TLS EMS: Set haveEMS when we negotiate TLS 1.3
Author: login: julek-wolfssl
Created: 2025-02-21T12:45:27Z | Merged: 2025-04-09T15:27:33Z

- Move extended master secret testing to test_tls_ext.c


Diff:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 51d9ea427e..cdfe1d2561 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2591,6 +2591,7 @@ if(WOLFSSL_EXAMPLES)
         tests/api/test_dtls.c
         tests/api/test_ocsp.c
         tests/api/test_evp.c
+        tests/api/test_tls_ext.c
         tests/srp.c
         tests/suites.c
         tests/w64wrapper.c
diff --git a/src/ssl_sess.c b/src/ssl_sess.c
index 2752ddc82a..dda518c91b 100644
--- a/src/ssl_sess.c
+++ b/src/ssl_sess.c
@@ -3561,7 +3561,16 @@ void SetupSession(WOLFSSL* ssl)
     session->side = (byte)ssl->options.side;
     if (!IsAtLeastTLSv1_3(ssl->version) && ssl->arrays != NULL)
         XMEMCPY(session->masterSecret, ssl->arrays->masterSecret, SECRET_LEN);
-    session->haveEMS = ssl->options.haveEMS;
+    /* RFC8446 Appendix D.
+     *   implementations which support both TLS 1.3 and earlier versions SHOULD
+     *   indicate the use of the Extended Master Secret extension in their APIs
+     *   whenever TLS 1.3 is used.
+     * Set haveEMS so that we send the extension in subsequent connections that
+     * offer downgrades. */
+    if (IsAtLeastTLSv1_3(ssl->version))
+        session->haveEMS = 1;
+    else
+        session->haveEMS = ssl->options.haveEMS;
 #ifdef WOLFSSL_SESSION_ID_CTX
     /* If using compatibility layer then check for and copy over session context
      * id. */
diff --git a/tests/api.c b/tests/api.c
index 0a0913586f..ae6851dda4 100644
--- a/tests/api.c
+++ b/tests/api.c
@@ -323,6 +323,7 @@
 #include <tests/api/test_dtls.h>
 #include <tests/api/test_ocsp.h>
 #include <tests/api/test_evp.h>
+#include <tests/api/test_tls_ext.h>
 
 #if !defined(NO_FILESYSTEM) && !defined(NO_CERTS) && !defined(NO_TLS) && \
     !defined(NO_RSA)        && !defined(SINGLE_THREADED) && \
@@ -12864,31 +12865,6 @@ static int test_wolfSSL_set_alpn_protos(void)
 
 #endif /* HAVE_ALPN_PROTOS_SUPPORT */
 
-static int test_wolfSSL_DisableExtendedMasterSecret(void)
-{
-    EXPECT_DECLS;
-#if defined(HAVE_EXTENDED_MASTER) && !defined(NO_WOLFSSL_CLIENT) && \
-    !defined(NO_TLS)
-    WOLFSSL_CTX *ctx = wolfSSL_CTX_new(wolfSSLv23_client_method());
-    WOLFSSL     *ssl = wolfSSL_new(ctx);
-
-    ExpectNotNull(ctx);
-    ExpectNotNull(ssl);
-
-    /* error cases */
-    ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_DisableExtendedMasterSecret(NULL));
-    ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_DisableExtendedMasterSecret(NULL));
-
-    /* success cases */
-    ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_CTX_DisableExtendedMasterSecret(ctx));
-    ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_DisableExtendedMasterSecret(ssl));
-
-    wolfSSL_free(ssl);
-    wolfSSL_CTX_free(ctx);
-#endif
-    return EXPECT_RESULT();
-}
-
 static int test_wolfSSL_wolfSSL_UseSecureRenegotiation(void)
 {
     EXPECT_DECLS;
@@ -67648,6 +67624,7 @@ TEST_CASE testCases[] = {
     /* Uses Assert in handshake callback. */
     TEST_DECL(test_wolfSSL_set_alpn_protos),
 #endif
+    TEST_DECL(test_tls_ems_downgrade),
     TEST_DECL(test_wolfSSL_DisableExtendedMasterSecret),
     TEST_DECL(test_wolfSSL_wolfSSL_UseSecureRenegotiation),
     TEST_DECL(test_wolfSSL_SCR_Reconnect),
diff --git a/tests/api/include.am b/tests/api/include.am
index 8b9f863b46..15e28235c3 100644
--- a/tests/api/include.am
+++ b/tests/api/include.am
@@ -52,6 +52,7 @@ tests_unit_test_SOURCES += tests/api/test_dtls.c
 # TLS Feature
 tests_unit_test_SOURCES += tests/api/test_ocsp.c
 tests_unit_test_SOURCES += tests/api/test_evp.c
+tests_unit_test_SOURCES += tests/api/test_tls_ext.c
 endif
 
 EXTRA_DIST += tests/api/api.h
@@ -101,4 +102,5 @@ EXTRA_DIST += tests/api/test_ocsp.h
 EXTRA_DIST += tests/api/test_ocsp_test_blobs.h
 EXTRA_DIST += tests/api/create_ocsp_test_blobs.py
 EXTRA_DIST += tests/api/test_evp.h
+EXTRA_DIST += tests/api/test_tls_ext.h
 
diff --git a/tests/api/test_tls_ext.c b/tests/api/test_tls_ext.c
new file mode 100644
index 0000000000..f9338410de
--- /dev/null
+++ b/tests/api/test_tls_ext.c
@@ -0,0 +1,129 @@
+/* test_tls_ext.c
+ *
+ * Copyright (C) 2006-2025 wolfSSL Inc.
+ *
+ * This file is part of wolfSSL.
+ *
+ * wolfSSL is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSL is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
+ */
+
+#include <tests/unit.h>
+
+#ifdef NO_INLINE
+    #include <wolfssl/wolfcrypt/misc.h>
+#else
+    #define WOLFSSL_MISC_INCLUDED
+    #include <wolfcrypt/src/misc.c>
+#endif
+
+#include <wolfssl/internal.h>
+#include <tests/utils.h>
+#include <tests/api/test_tls_ext.h>
+
+int test_tls_ems_downgrade(void)
+{
+    EXPECT_DECLS;
+#if defined(WOLFSSL_TLS13) && !defined(WOLFSSL_NO_TLS12) && \
+        defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
+        defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB)
+    struct test_memio_ctx test_ctx;
+    WOLFSSL_CTX *ctx_c = NULL;
+    WOLFSSL_CTX *ctx_s = NULL;
+    WOLFSSL *ssl_c = NULL;
+    WOLFSSL *ssl_s = NULL;
+    WOLFSSL_SESSION* session = NULL;
+    /* TLS EMS extension in binary form */
+    const char ems_ext[] = { 0x00, 0x17, 0x00, 0x00 };
+    char data = 0;
+
+    XMEMSET(&test_ctx, 0, sizeof(test_ctx));
+
+    ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
+            wolfTLS_client_method, wolfTLS_server_method), 0);
+
+    ExpectIntEQ(wolfSSL_connect(ssl_c), -1);
+    ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ);
+
+    /* Verify that the EMS extension is present in Client's message */
+    ExpectNotNull(mymemmem(test_ctx.s_buff, test_ctx.s_len,
+            ems_ext, sizeof(ems_ext)));
+
+    ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
+    ExpectIntEQ(wolfSSL_version(ssl_c), TLS1_3_VERSION);
+
+    /* Do a round of reads to exchange the ticket message */
+    ExpectIntEQ(wolfSSL_read(ssl_s, &data, sizeof(data)), -1);
+    ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ);
+    ExpectIntEQ(wolfSSL_read(ssl_c, &data, sizeof(data)), -1);
+    ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ);
+
+    ExpectNotNull(session = wolfSSL_get1_session(ssl_c));
+    ExpectTrue(session->haveEMS);
+
+    wolfSSL_free(ssl_c);
+    ssl_c = NULL;
+    wolfSSL_free(ssl_s);
+    ssl_s = NULL;
+
+    ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
+            wolfTLS_client_method, wolfTLS_server_method), 0);
+
+    /* Resuming the connection */
+    ExpectIntEQ(wolfSSL_set_session(ssl_c, session), WOLFSSL_SUCCESS);
+    ExpectIntEQ(wolfSSL_connect(ssl_c), -1);
+    ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ);
+
+    /* Verify that the EMS extension is still present in the resumption CH
+     * even though we used TLS 1.3 */
+    ExpectNotNull(mymemmem(test_ctx.s_buff, test_ctx.s_len,
+            ems_ext, sizeof(ems_ext)));
+
+    ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
+    ExpectIntEQ(wolfSSL_version(ssl_c), TLS1_3_VERSION);
+
+    wolfSSL_SESSION_free(session);
+    wolfSSL_free(ssl_c);
+    wolfSSL_free(ssl_s);
+    wolfSSL_CTX_free(ctx_c);
+    wolfSSL_CTX_free(ctx_s);
+#endif
+    return EXPECT_RESULT();
+}
+
+
+int test_wolfSSL_DisableExtendedMasterSecret(void)
+{
+    EXPECT_DECLS;
+#if defined(HAVE_EXTENDED_MASTER) && !defined(NO_WOLFSSL_CLIENT) && \
+    !defined(NO_TLS)
+    WOLFSSL_CTX *ctx = wolfSSL_CTX_new(wolfSSLv23_client_method());
+    WOLFSSL     *ssl = wolfSSL_new(ctx);
+
+    ExpectNotNull(ctx);
+    ExpectNotNull(ssl);
+
+    /* error cases */
+    ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_DisableExtendedMasterSecret(NULL));
+    ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_DisableExtendedMasterSecret(NULL));
+
+    /* success cases */
+    ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_CTX_DisableExtendedMasterSecret(ctx));
+    ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_DisableExtendedMasterSecret(ssl));
+
+    wolfSSL_free(ssl);
+    wolfSSL_CTX_free(ctx);
+#endif
+    return EXPECT_RESULT();
+}
diff --git a/tests/api/test_tls_ext.h b/tests/api/test_tls_ext.h
new file mode 100644
index 0000000000..5fcc10471c
--- /dev/null
+++ b/tests/api/test_tls_ext.h
@@ -0,0 +1,28 @@
+/* test_tls_ext.h
+ *
+ * Copyright (C) 2006-2025 wolfSSL Inc.
+ *
+ * This file is part of wolfSSL.
+ *
+ * wolfSSL is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSL is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
+ */
+
+#ifndef TESTS_API_TEST_TLS_EMS_H
+#define TESTS_API_TEST_TLS_EMS_H
+
+int test_tls_ems_downgrade(void);
+int test_wolfSSL_DisableExtendedMasterSecret(void);
+
+#endif /* TESTS_API_TEST_TLS_EMS_H */


Reviews:
user: login: dgarske
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-03-03T21:58:30Z
user: login: SparkiDev
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T11:28:28Z
user: login: dgarske
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-06T19:19:21Z
user: login: dgarske
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-06T20:25:17Z
user: login: SparkiDev
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T23:09:42Z
user: login: dgarske
body: ```
./configure --with-liboqs --enable-experimental  && make && ./tests/unit.test

Stage 'PRB-liboqs.txt_0'

FAILURES:
   1018: test_tls_ems_downgrade
```

state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-07T18:23:34Z
user: login: dgarske
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-08T23:48:53Z
user: login: copilot-pull-request-reviewer[bot]
body: 

Copilot reviewed 4 out of 6 changed files in this pull request and generated no comments.

<details>
<summary>Files not reviewed (2)</summary>

* **CMakeLists.txt**: Language not supported
* **tests/api/include.am**: Language not supported
</details>

<details>
<summary>Comments suppressed due to low confidence (1)</summary>

**tests/api/test_tls_ext.h:1**
* [nitpick] The header file comment refers to 'test_tls_ems.h', which does not match the file name 'test_tls_ext.h'. Consider updating the comment for consistency.
```
/* test_tls_ems.h
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T23:49:15Z
user: login: dgarske
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-08T23:50:44Z
user: login: julek-wolfssl
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-09T12:37:12Z
user: login: dgarske
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-09T15:27:19Z

Review comments:
diff_hunk: @@ -0,0 +1,129 @@
+/* test_tls_ems.c
+ *
+ * Copyright (C) 2006-2025 wolfSSL Inc.
+ *
+ * This file is part of wolfSSL.
+ *
+ * wolfSSL is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSL is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
+ */
+
+#include <tests/unit.h>
+
+#ifdef NO_INLINE
+    #include <wolfssl/wolfcrypt/misc.h>
+#else
+    #define WOLFSSL_MISC_INCLUDED
+    #include <wolfcrypt/src/misc.c>
+#endif
+
+#include <wolfssl/internal.h>
+#include <tests/utils.h>
+#include <tests/api/test_tls_ext.h>
+
+int test_tls_ems_downgrade(void)
+{
+    EXPECT_DECLS;
+#if defined(WOLFSSL_TLS13) && !defined(WOLFSSL_NO_TLS12) && \
+        defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
+        defined(HAVE_SESSION_TICKET)
+    struct test_memio_ctx test_ctx;
+    WOLFSSL_CTX *ctx_c = NULL;
+    WOLFSSL_CTX *ctx_s = NULL;
+    WOLFSSL *ssl_c = NULL;
+    WOLFSSL *ssl_s = NULL;
+    WOLFSSL_SESSION* session = NULL;
+    /* TLS EMS extension in binary form */
+    const char ems_ext[] = { 0x00, 0x17, 0x00, 0x00 };
+    char data = 0;
+
+    XMEMSET(&test_ctx, 0, sizeof(test_ctx));
+
+    ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
+            wolfTLS_client_method, wolfTLS_server_method), 0);
+
+    ExpectIntEQ(wolfSSL_connect(ssl_c), -1);
+    ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ);
+
+    /* Verify that the EMS extension is present in Client's message */
+    ExpectNotNull(mymemmem(test_ctx.s_buff, test_ctx.s_len,
+            ems_ext, sizeof(ems_ext)));
+
+    ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
+    ExpectIntEQ(wolfSSL_version(ssl_c), TLS1_3_VERSION);
+
+    /* Do a round of reads to exchange the ticket message */
+    ExpectIntEQ(wolfSSL_read(ssl_s, &data, sizeof(data)), -1);
+    ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ);
+    ExpectIntEQ(wolfSSL_read(ssl_c, &data, sizeof(data)), -1);
+    ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ);
+
+    ExpectNotNull(session = wolfSSL_get1_session(ssl_c));
+    ExpectTrue(session->haveEMS);
path: tests/api/test_tls_ext.c
user: login: dgarske
body: ```
--with-liboqs --enable-experimental --enable-opensslall --enable-crl --enable-sessioncerts --enable-aesctr --enable-rsapss --enable-keygen --enable-des3 CPPFLAGS="-DRSA_MAX_SIZE=8196 -DFP_MAX_BITS=16384 -DNO_OLD_TIMEVAL_NAME -DWOLFSSL_MYSQL_COMPATIBLE -DWOLFSSL_AES_DIRECT -DKEEP_OUR_CERT -DHAVE_AES_ECB -DWOLFSSL_NO_DEF_TICKET_ENC_CB -DHAVE_EX_DATA"
```

```   988: test_tls_ems_downgrade                              :
ERROR - tests/api/test_tls_ext.c line 73 failed with:
    expected: session->haveEMS is true
    result:   session->haveEMS => FALSE
 failed (  0.00701)
ERROR - tests/api.c line 90675 failed with:
    expected: Test failed```
created_at: 2025-03-06T20:25:11Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,129 @@
+/* test_tls_ems.c
path: tests/api/test_tls_ext.c
user: login: dgarske
body: Copilot found a nit: name doesn't match.
created_at: 2025-04-08T23:50:38Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,129 @@
+/* test_tls_ems.c
path: tests/api/test_tls_ext.c
user: login: julek-wolfssl
body: Nice. Fixed.
created_at: 2025-04-09T12:37:12Z
author_association: MEMBER

PR comments:
user: login: dgarske
created_at: 2025-03-03T21:57:51Z
body: Retest this please: "hudson.remoting.RequestAbortedException exception:"
author_association: MEMBER
user: login: julek-wolfssl
created_at: 2025-03-03T22:27:56Z
body: Rebased to master to fix merge conflicts.
author_association: MEMBER
user: login: dgarske
created_at: 2025-03-06T19:18:54Z
body: Retest this please. History lost.
author_association: MEMBER
user: login: julek-wolfssl
created_at: 2025-04-03T17:40:45Z
body: Jenkins failure not relevant.
author_association: MEMBER
user: login: dgarske
created_at: 2025-04-07T17:02:52Z
body: Retest this please. History lost.
author_association: MEMBER
user: login: julek-wolfssl
created_at: 2025-04-09T14:15:40Z
body: Retest this please
author_association: MEMBER