Merge pull request #1306 from davidhorstmann-arm/pkcs7-padding-side-channel-fix-3.6

[Backport 3.6] Fix side channel in PKCS7 padding
diff --git a/.pylintrc b/.pylintrc
index f9c97d5..4a1b6e5 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -70,6 +70,17 @@
 # Don't diplay statistics. Just the facts.
 reports=no
 
+[STRING]
+# Complain about
+# ```
+# list_of_strings = [
+#    'foo' # <-- missing comma
+#    'bar',
+#    'corge',
+# ]
+# ```
+check-str-concat-over-line-jumps=yes
+
 [VARIABLES]
 # Allow unused variables if their name starts with an underscore.
 # [unused-argument]
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
index 2b10f86..cf83384 100644
--- a/.readthedocs.yaml
+++ b/.readthedocs.yaml
@@ -17,7 +17,7 @@
     python: "3.9"
   jobs:
     pre_build:
-    - ./scripts/apidoc_full.sh
+    - ./framework/scripts/apidoc_full.sh
     - breathe-apidoc -o docs/api apidoc/xml
     post_build:
     - |
diff --git a/3rdparty/everest/include/everest/kremlin/c_endianness.h b/3rdparty/everest/include/everest/kremlin/c_endianness.h
index 5cfde5d..1b0d0eb 100644
--- a/3rdparty/everest/include/everest/kremlin/c_endianness.h
+++ b/3rdparty/everest/include/everest/kremlin/c_endianness.h
@@ -7,6 +7,8 @@
 #include <string.h>
 #include <inttypes.h>
 
+#include "kremlin/internal/callconv.h"
+
 /******************************************************************************/
 /* Implementing C.fst (part 2: endian-ness macros)                            */
 /******************************************************************************/
diff --git a/3rdparty/everest/include/everest/kremlin/internal/callconv.h b/3rdparty/everest/include/everest/kremlin/internal/callconv.h
index bf631ff..8ff8ca5 100644
--- a/3rdparty/everest/include/everest/kremlin/internal/callconv.h
+++ b/3rdparty/everest/include/everest/kremlin/internal/callconv.h
@@ -27,8 +27,10 @@
 /* Since KreMLin emits the inline keyword unconditionally, we follow the
  * guidelines at https://gcc.gnu.org/onlinedocs/gcc/Inline.html and make this
  * __inline__ to ensure the code compiles with -std=c90 and earlier. */
-#ifdef __GNUC__
+#if defined(__GNUC__)
 #  define inline __inline__
+#elif defined(_MSC_VER)
+#  define inline __inline
 #endif
 
 /* GCC-specific attribute syntax; everyone else gets the standard C inline
diff --git a/3rdparty/everest/include/everest/vs2013/inttypes.h b/3rdparty/everest/include/everest/vs2013/inttypes.h
deleted file mode 100644
index 77003be..0000000
--- a/3rdparty/everest/include/everest/vs2013/inttypes.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- *  Custom inttypes.h for VS2010 KreMLin requires these definitions,
- *  but VS2010 doesn't provide them.
- *
- *  Copyright 2016-2018 INRIA and Microsoft Corporation
- *  SPDX-License-Identifier: Apache-2.0
- *
- *  Licensed under the Apache License, Version 2.0 (the "License"); you may
- *  not use this file except in compliance with the License.
- *  You may obtain a copy of the License at
- *
- *  http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- *  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- *
- *  This file is part of Mbed TLS (https://tls.mbed.org)
- */
-
-#ifndef _INTTYPES_H_VS2010
-#define _INTTYPES_H_VS2010
-
-#include <stdint.h>
-
-#ifdef _MSC_VER
-#define inline __inline
-#endif
-
-/* VS2010 unsigned long == 8 bytes */
-
-#define PRIu64 "I64u"
-
-#endif
diff --git a/3rdparty/everest/include/everest/vs2013/stdbool.h b/3rdparty/everest/include/everest/vs2013/stdbool.h
deleted file mode 100644
index dcae6d8..0000000
--- a/3rdparty/everest/include/everest/vs2013/stdbool.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- *  Custom stdbool.h for VS2010 KreMLin requires these definitions,
- *  but VS2010 doesn't provide them.
- *
- *  Copyright 2016-2018 INRIA and Microsoft Corporation
- *  SPDX-License-Identifier: Apache-2.0
- *
- *  Licensed under the Apache License, Version 2.0 (the "License"); you may
- *  not use this file except in compliance with the License.
- *  You may obtain a copy of the License at
- *
- *  http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- *  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- *
- *  This file is part of Mbed TLS (https://tls.mbed.org)
- */
-
-#ifndef _STDBOOL_H_VS2010
-#define _STDBOOL_H_VS2010
-
-typedef int bool;
-
-static bool true = 1;
-static bool false = 0;
-
-#endif
diff --git a/BRANCHES.md b/BRANCHES.md
index cf86a9d..93bcbbd 100644
--- a/BRANCHES.md
+++ b/BRANCHES.md
@@ -107,9 +107,10 @@
 - [`development`](https://github.com/Mbed-TLS/mbedtls/)
 - [`mbedtls-3.6`](https://github.com/Mbed-TLS/mbedtls/tree/mbedtls-3.6)
  maintained until March 2027, see
-  <https://github.com/Mbed-TLS/mbedtls/releases/tag/v3.6.2>.
-- [`mbedtls-2.28`](https://github.com/Mbed-TLS/mbedtls/tree/mbedtls-2.28)
- maintained until the end of 2024, see
-  <https://github.com/Mbed-TLS/mbedtls/releases/tag/v2.28.9>.
+  <https://github.com/Mbed-TLS/mbedtls/releases/tag/v3.6.3>.
+
+> Note: [**`mbedtls-2.28.10`**](https://github.com/Mbed-TLS/mbedtls/releases/tag/v2.28.10)
+is the last release of the 2.28 LTS and won't receive bug fixes or security fixes anymore.
+Users are advised to upgrade to a maintained version.
 
 Users are urged to always use the latest version of a maintained branch.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c6d31dc..c981ef7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -40,12 +40,12 @@
 if(TEST_CPP)
     project("Mbed TLS"
         LANGUAGES C CXX
-        VERSION 3.6.2
+        VERSION 3.6.3
     )
 else()
     project("Mbed TLS"
         LANGUAGES C
-        VERSION 3.6.2
+        VERSION 3.6.3
     )
 endif()
 
@@ -61,6 +61,7 @@
 
 # Set the project root directory.
 set(MBEDTLS_DIR ${CMAKE_CURRENT_SOURCE_DIR})
+set(MBEDTLS_FRAMEWORK_DIR ${CMAKE_CURRENT_SOURCE_DIR}/framework)
 
 option(ENABLE_PROGRAMS "Build Mbed TLS programs." ON)
 
@@ -315,7 +316,7 @@
 endif()
 
 if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/framework/CMakeLists.txt")
-    if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git/")
+    if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git")
         message(FATAL_ERROR "${CMAKE_CURRENT_SOURCE_DIR}CMakeLists.txt not found (and does appear to be a git checkout). Run `git submodule update --init` from the source tree to fetch the submodule contents.")
     else ()
         message(FATAL_ERROR "${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt not found (and does not appear to be a git checkout). Please ensure you have downloaded the right archive from the release page on GitHub.")
@@ -354,35 +355,37 @@
     if(GEN_FILES)
         add_custom_command(
             OUTPUT
-                ${CMAKE_CURRENT_SOURCE_DIR}/framework/tests/src/test_keys.h
-            WORKING_DIRECTORY
-                ${CMAKE_CURRENT_SOURCE_DIR}/tests
+                ${CMAKE_CURRENT_BINARY_DIR}/tests/include/test/test_keys.h
+            COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/tests/include/test
             COMMAND
                 "${MBEDTLS_PYTHON_EXECUTABLE}"
                 "${CMAKE_CURRENT_SOURCE_DIR}/framework/scripts/generate_test_keys.py"
                 "--output"
-                "${CMAKE_CURRENT_SOURCE_DIR}/framework/tests/src/test_keys.h"
+                "${CMAKE_CURRENT_BINARY_DIR}/tests/include/test/test_keys.h"
             DEPENDS
                 ${CMAKE_CURRENT_SOURCE_DIR}/framework/scripts/generate_test_keys.py
         )
-        add_custom_target(test_keys_header DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/framework/tests/src/test_keys.h)
+        add_custom_target(test_keys_header
+            DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/tests/include/test/test_keys.h)
         add_custom_command(
             OUTPUT
-                ${CMAKE_CURRENT_SOURCE_DIR}/tests/src/test_certs.h
+                ${CMAKE_CURRENT_BINARY_DIR}/tests/include/test/test_certs.h
+            COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/tests/include/test
             WORKING_DIRECTORY
                 ${CMAKE_CURRENT_SOURCE_DIR}/tests
             COMMAND
                 "${MBEDTLS_PYTHON_EXECUTABLE}"
                 "${CMAKE_CURRENT_SOURCE_DIR}/framework/scripts/generate_test_cert_macros.py"
                 "--output"
-                "${CMAKE_CURRENT_SOURCE_DIR}/tests/src/test_certs.h"
+                "${CMAKE_CURRENT_BINARY_DIR}/tests/include/test/test_certs.h"
             DEPENDS
                 ${CMAKE_CURRENT_SOURCE_DIR}/framework/scripts/generate_test_cert_macros.py
         )
-        add_custom_target(test_certs_header DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/tests/src/test_certs.h)
+        add_custom_target(test_certs_header DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/tests/include/test/test_certs.h)
         add_dependencies(mbedtls_test test_keys_header test_certs_header)
     endif()
     target_include_directories(mbedtls_test
+        PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/tests/include
         PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/framework/tests/include
         PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests/include
         PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include
@@ -473,7 +476,7 @@
     write_basic_package_version_file(
         "cmake/MbedTLSConfigVersion.cmake"
             COMPATIBILITY SameMajorVersion
-            VERSION 3.6.2)
+            VERSION 3.6.3)
 
     install(
         FILES "${CMAKE_CURRENT_BINARY_DIR}/cmake/MbedTLSConfig.cmake"
diff --git a/ChangeLog b/ChangeLog
index ec94776..5cadd2b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,88 @@
 Mbed TLS ChangeLog (Sorted per branch, date)
 
+= Mbed TLS 3.6.3 branch released 2025-03-24
+
+Default behavior changes
+   * In TLS clients, if mbedtls_ssl_set_hostname() has not been called,
+     mbedtls_ssl_handshake() now fails with
+     MBEDTLS_ERR_SSL_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME
+     if certificate-based authentication of the server is attempted.
+     This is because authenticating a server without knowing what name
+     to expect is usually insecure. To restore the old behavior, either
+     call mbedtls_ssl_set_hostname() with NULL as the hostname, or
+     enable the new compile-time option
+     MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME.
+
+Features
+   * Added new configuration option MBEDTLS_PSA_STATIC_KEY_SLOTS, which
+     uses static storage for keys, enabling malloc-less use of key slots.
+     The size of each buffer is given by the option
+     MBEDTLS_PSA_STATIC_KEY_SLOT_BUFFER_SIZE. By default it accommodates the
+     largest PSA key enabled in the build.
+   * MD module can now perform PSA dispatching also when
+     `MBEDTLS_PSA_CRYPTO_CLIENT && !MBEDTLS_PSA_CRYPTO_C`, even though this
+     configuration is not officially supported. This requires that a
+     PSA Crypto provider library which:
+     * supports the required `PSA_WANT_ALG_xxx` and
+     * implements `psa_can_do_hash()` on the client interface
+     is linked against Mbed TLS and that `psa_crypto_init()` is called before
+     performing any PSA call.
+
+Security
+   * Note that TLS clients should generally call mbedtls_ssl_set_hostname()
+     if they use certificate authentication (i.e. not pre-shared keys).
+     Otherwise, in many scenarios, the server could be impersonated.
+     The library will now prevent the handshake and return
+     MBEDTLS_ERR_SSL_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME
+     if mbedtls_ssl_set_hostname() has not been called.
+     Reported by Daniel Stenberg.
+     CVE-2025-27809
+   * Zeroize a temporary heap buffer used in psa_key_derivation_output_key()
+     when deriving an ECC key pair.
+   * Zeroize temporary heap buffers used in PSA operations.
+   * Fix a vulnerability in the TLS 1.2 handshake. If memory allocation failed
+     or there was a cryptographic hardware failure when calculating the
+     Finished message, it could be calculated incorrectly. This would break
+     the security guarantees of the TLS handshake.
+     CVE-2025-27810
+
+Bugfix
+   * When MBEDTLS_SSL_TLS1_3_COMPATIBILITY_MODE is disabled, work with
+     peers that have middlebox compatibility enabled, as long as no
+     problematic middlebox is in the way. Fixes #9551.
+   * Fix invalid JSON schemas for driver descriptions used by
+     generate_driver_wrappers.py.
+   * Use 'mbedtls_net_close' instead of 'close' in 'mbedtls_net_bind'
+     and 'mbedtls_net_connect' to prevent possible double close fd
+     problems. Fixes #9711.
+   * Fix undefined behavior in some cases when mbedtls_psa_raw_to_der() or
+     mbedtls_psa_der_to_raw() is called with bits=0.
+   * Fix compilation on MS-DOS DJGPP. Fixes #9813.
+   * Fix missing constraints on the AES-NI inline assembly which is used on
+     GCC-like compilers when building AES for generic x86_64 targets. This
+     may have resulted in incorrect code with some compilers, depending on
+     optimizations. Fixes #9819.
+   * Support re-assembly of fragmented handshake messages in TLS (both
+     1.2 and 1.3). The lack of support was causing handshake failures with
+     some servers, especially with TLS 1.3 in practice. There are a few
+     limitations, notably a fragmented ClientHello is only supported when
+     TLS 1.3 support is enabled. See the documentation of
+     mbedtls_ssl_handshake() for details.
+   * Fix definition of MBEDTLS_PRINTF_SIZET to prevent runtime crashes that
+     occurred whenever SSL debugging was enabled on a copy of Mbed TLS built
+     with Visual Studio 2013 or MinGW.
+     Fixes #10017.
+   * Remove Everest Visual Studio 2010 compatibility headers, which could
+     shadow standard CRT headers inttypes.h and stdbool.h with incomplete
+     implementatios if placed on the include path, eg. when building Mbed TLS
+     with the .sln file shipped with the project.
+   * Fix issue where psa_key_derivation_input_integer() is not detecting
+     bad state after an operation has been aborted.
+
+Changes
+   * Improve performance of PSA key generation with ECC keys: it no longer
+     computes the public key (which was immediately discarded). Fixes #9732.
+
 = Mbed TLS 3.6.2 branch released 2024-10-14
 
 Security
diff --git a/ChangeLog.d/00README.md b/ChangeLog.d/00README.md
index 2fbc989..321e888 100644
--- a/ChangeLog.d/00README.md
+++ b/ChangeLog.d/00README.md
@@ -86,6 +86,6 @@
 
 ## How `ChangeLog` is updated
 
-Run [`../scripts/assemble_changelog.py`](../scripts/assemble_changelog.py)
-from a Git working copy
+Run [`../framework/scripts/assemble_changelog.py`]
+(../framework/scripts/assemble_changelog.py) from a Git working copy
 to move the entries from files in `ChangeLog.d` to the main `ChangeLog` file.
diff --git a/ChangeLog.d/9302.txt b/ChangeLog.d/9302.txt
deleted file mode 100644
index d61ba19..0000000
--- a/ChangeLog.d/9302.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-Features
-   * Added new configuration option MBEDTLS_PSA_STATIC_KEY_SLOTS, which
-     uses static storage for keys, enabling malloc-less use of key slots.
-     The size of each buffer is given by the option
-     MBEDTLS_PSA_STATIC_KEY_SLOT_BUFFER_SIZE. By default it accommodates the
-     largest PSA key enabled in the build.
diff --git a/ChangeLog.d/add-tls-exporter.txt b/ChangeLog.d/add-tls-exporter.txt
new file mode 100644
index 0000000..1aea653
--- /dev/null
+++ b/ChangeLog.d/add-tls-exporter.txt
@@ -0,0 +1,6 @@
+Features
+   * Add the function mbedtls_ssl_export_keying_material() which allows the
+     client and server to extract additional shared symmetric keys from an SSL
+     session, according to the TLS-Exporter specification in RFC 8446 and 5705.
+     This requires MBEDTLS_SSL_KEYING_MATERIAL_EXPORT to be defined in
+     mbedtls_config.h.
diff --git a/ChangeLog.d/fix-compilation-with-djgpp.txt b/ChangeLog.d/fix-compilation-with-djgpp.txt
deleted file mode 100644
index 5b79fb6..0000000
--- a/ChangeLog.d/fix-compilation-with-djgpp.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-Bugfix
-   * Fix compilation on MS-DOS DJGPP. Fixes #9813.
diff --git a/ChangeLog.d/fix-driver-schema-check.txt b/ChangeLog.d/fix-driver-schema-check.txt
deleted file mode 100644
index 9b6d8ac..0000000
--- a/ChangeLog.d/fix-driver-schema-check.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-Bugfix
-   * Fix invalid JSON schemas for driver descriptions used by
-     generate_driver_wrappers.py.
diff --git a/ChangeLog.d/mbedtls_psa_ecp_generate_key-no_public_key.txt b/ChangeLog.d/mbedtls_psa_ecp_generate_key-no_public_key.txt
deleted file mode 100644
index 69c00e1..0000000
--- a/ChangeLog.d/mbedtls_psa_ecp_generate_key-no_public_key.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-Changes
-   * Improve performance of PSA key generation with ECC keys: it no longer
-     computes the public key (which was immediately discarded). Fixes #9732.
diff --git a/ChangeLog.d/psa_util-bits-0.txt b/ChangeLog.d/psa_util-bits-0.txt
deleted file mode 100644
index 9aa70ad..0000000
--- a/ChangeLog.d/psa_util-bits-0.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-Bugfix
-   * Fix undefined behavior in some cases when mbedtls_psa_raw_to_der() or
-     mbedtls_psa_der_to_raw() is called with bits=0.
diff --git a/ChangeLog.d/replace-close-with-mbedtls_net_close.txt b/ChangeLog.d/replace-close-with-mbedtls_net_close.txt
deleted file mode 100644
index 213cf55..0000000
--- a/ChangeLog.d/replace-close-with-mbedtls_net_close.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-Bugfix
-   * Use 'mbedtls_net_close' instead of 'close' in 'mbedtls_net_bind'
-     and 'mbedtls_net_connect' to prevent possible double close fd
-     problems. Fixes #9711.
diff --git a/ChangeLog.d/tls13-middlebox-compat-disabled.txt b/ChangeLog.d/tls13-middlebox-compat-disabled.txt
deleted file mode 100644
index f5331bc..0000000
--- a/ChangeLog.d/tls13-middlebox-compat-disabled.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-Bugfix
-   * When MBEDTLS_SSL_TLS1_3_COMPATIBILITY_MODE is disabled, work with
-     peers that have middlebox compatibility enabled, as long as no
-     problematic middlebox is in the way. Fixes #9551.
diff --git a/Makefile b/Makefile
index 4615a44..38f2832 100644
--- a/Makefile
+++ b/Makefile
@@ -6,7 +6,7 @@
     ifeq (,$(wildcard framework/exported.make))
         # Use the define keyword to get a multi-line message.
         # GNU make appends ".  Stop.", so tweak the ending of our message accordingly.
-        ifeq (,$(wildcard .git))
+        ifneq (,$(wildcard .git))
             define error_message
 ${MBEDTLS_PATH}/framework/exported.make not found (and does appear to be a git checkout). Run `git submodule update --init` from the source tree to fetch the submodule contents.
 This is a fatal error
@@ -46,11 +46,14 @@
 mbedtls_test:
 	$(MAKE) -C tests mbedtls_test
 
-library/%:
+.PHONY: FORCE
+FORCE:
+
+library/%: FORCE
 	$(MAKE) -C library $*
-programs/%:
+programs/%: FORCE
 	$(MAKE) -C programs $*
-tests/%:
+tests/%: FORCE
 	$(MAKE) -C tests $*
 
 .PHONY: generated_files
@@ -91,6 +94,8 @@
 # present before it runs. It doesn't matter if the files aren't up-to-date,
 # they just need to be present.
 $(VISUALC_FILES): | library/generated_files
+$(VISUALC_FILES): | programs/generated_files
+$(VISUALC_FILES): | tests/generated_files
 $(VISUALC_FILES): $(gen_file_dep) scripts/generate_visualc_files.pl
 $(VISUALC_FILES): $(gen_file_dep) scripts/data_files/vs2017-app-template.vcxproj
 $(VISUALC_FILES): $(gen_file_dep) scripts/data_files/vs2017-main-template.vcxproj
diff --git a/SECURITY.md b/SECURITY.md
index 7ed72de..0af1903 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -144,3 +144,22 @@
 
 The Everest variant is only used when `MBEDTLS_ECDH_VARIANT_EVEREST_ENABLED`
 configuration option is defined. This option is off by default.
+
+#### Formatting of X.509 certificates and certificate signing requests
+
+When parsing X.509 certificates and certificate signing requests (CSRs),
+Mbed TLS does not check that they are strictly compliant with X.509 and other
+relevant standards. In the case of signed certificates, the signing party is
+assumed to have performed this validation (and the certificate is trusted to
+be correctly formatted as long as the signature is correct).
+Similarly, CSRs are implicitly trusted by Mbed TLS to be standards-compliant.
+
+**Warning!** Mbed TLS must not be used to sign untrusted CSRs unless extra
+validation is performed separately to ensure that they are compliant to the
+relevant specifications. This makes Mbed TLS on its own unsuitable for use in
+a Certificate Authority (CA).
+
+However, Mbed TLS aims to protect against memory corruption and other
+undefined behavior when parsing certificates and CSRs. If a CSR or signed
+certificate causes undefined behavior when it is parsed by Mbed TLS, that
+is considered a security vulnerability.
diff --git a/docs/3.0-migration-guide.md b/docs/3.0-migration-guide.md
index 63a13ad..e927667 100644
--- a/docs/3.0-migration-guide.md
+++ b/docs/3.0-migration-guide.md
@@ -71,7 +71,7 @@
 
 If no accessor function exists, please open an [enhancement request against Mbed TLS](https://github.com/Mbed-TLS/mbedtls/issues/new?template=feature_request.md) and describe your use case. The Mbed TLS development team is aware that some useful accessor functions are missing in the 3.0 release, and we expect to add them to the first minor release(s) (3.1, etc.).
 
-As a last resort, you can access the field `foo` of a structure `bar` by writing `bar.MBEDTLS_PRIVATE(foo)`. Note that you do so at your own risk, since such code is likely to break in a future minor version of Mbed TLS.
+As a last resort, you can access the field `foo` of a structure `bar` by writing `bar.MBEDTLS_PRIVATE(foo)`. Note that you do so at your own risk, since such code is likely to break in a future minor version of Mbed TLS. In the Mbed TLS 3.6 LTS this will tend to be safer than in a normal minor release because LTS versions try to maintain ABI stability.
 
 ### Move part of timing module out of the library
 
@@ -349,7 +349,7 @@
 | `mbedtls_sha512_finish_ret`    | `mbedtls_sha512_finish`    |
 | `mbedtls_sha512_ret`           | `mbedtls_sha512`           |
 
-To migrate to the this change the user can keep the `*_ret` names in their code
+To migrate to this change the user can keep the `*_ret` names in their code
 and include the `compat_2.x.h` header file which holds macros with proper
 renaming or to rename those functions in their code according to the list from
 mentioned header file.
@@ -409,7 +409,7 @@
 Previously, the documentation didn't state explicitly if it was OK to call
 `mbedtls_cipher_check_tag()` or `mbedtls_cipher_write_tag()` directly after
 the last call to `mbedtls_cipher_update()` — that is, without calling
-`mbedtls_cipher_finish()` in-between. If you code was missing that call,
+`mbedtls_cipher_finish()` in-between. If your code was missing that call,
 please add it and be prepared to get as much as 15 bytes of output.
 
 Currently the output is always 0 bytes, but it may be more when alternative
@@ -422,7 +422,7 @@
 
 They are already niche or obsolete and most of them are weak or broken. For
 those reasons possible users should consider switching to modern and safe
-alternatives to be found in literature.
+alternatives to be found in the literature.
 
 ### Deprecated functions were removed from cipher
 
@@ -748,7 +748,7 @@
 
 The default preference order for curves in TLS now favors resource usage (performance and memory consumption) over size. The exact order is unspecified and may change, but generally you can expect 256-bit curves to be preferred over larger curves.
 
-If you prefer a different order, call `mbedtls_ssl_conf_curves()` when configuring a TLS connection.
+If you prefer a different order, call `mbedtls_ssl_conf_groups()` when configuring a TLS connection.
 
 ### SSL key export interface change
 
@@ -806,11 +806,11 @@
 In Mbed TLS 2.x, users would observe later calls overwriting
 the effect of earlier calls, with the prevailing PSK being
 the one that has been configured last. In Mbed TLS 3.0,
-calling `mbedtls_ssl_conf_[opaque_]psk()` multiple times
+calling `mbedtls_ssl_conf_psk[_opaque]()` multiple times
 will return an error, leaving the first PSK intact.
 
 To achieve equivalent functionality when migrating to Mbed TLS 3.0,
-users calling `mbedtls_ssl_conf_[opaque_]psk()` multiple times should
+users calling `mbedtls_ssl_conf_psk[_opaque]()` multiple times should
 remove all but the last call, so that only one call to _either_
 `mbedtls_ssl_conf_psk()` _or_ `mbedtls_ssl_conf_psk_opaque()`
 remains.
@@ -1025,7 +1025,7 @@
 my_profile.allowed_mds |= MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_SHA224 );
 ```
 
-If you still need to allow hashes and curves in TLS that have been removed from the default configuration, call `mbedtls_ssl_conf_sig_hashes()` and `mbedtls_ssl_conf_curves()` with the desired lists.
+If you still need to allow hashes and curves in TLS that have been removed from the default configuration, call `mbedtls_ssl_conf_sig_hashes()` and `mbedtls_ssl_conf_groups()` with the desired lists.
 
 ### Remove 3DES ciphersuites
 
diff --git a/docs/architecture/psa-migration/md-cipher-dispatch.md b/docs/architecture/psa-migration/md-cipher-dispatch.md
index eda65a3..89b7b61 100644
--- a/docs/architecture/psa-migration/md-cipher-dispatch.md
+++ b/docs/architecture/psa-migration/md-cipher-dispatch.md
@@ -17,36 +17,44 @@
 
 #### Backward compatibility user story
 
-As a developer of an application that uses Mbed TLS's interfaces (including legacy crypto),  
-I want Mbed TLS to preserve backward compatibility,  
+As a developer of an application that uses Mbed TLS's interfaces (including legacy crypto),
+I want Mbed TLS to preserve backward compatibility,
 so that my code keeps working in new minor versions of Mbed TLS.
 
 #### Interface design user story
 
-As a developer of library code that uses Mbed TLS to perform cryptographic operations,  
-I want to know which functions to call and which feature macros to check,  
+As a developer of library code that uses Mbed TLS to perform cryptographic operations,
+I want to know which functions to call and which feature macros to check,
 so that my code works in all Mbed TLS configurations.
 
 Note: this is the same problem we face in X.509 and TLS.
 
 #### Hardware accelerator vendor user stories
 
-As a vendor of a platform with hardware acceleration for some crypto,  
-I want to build Mbed TLS in a way that uses my hardware wherever relevant,  
+As a vendor of a platform with hardware acceleration for some crypto,
+I want to build Mbed TLS in a way that uses my hardware wherever relevant,
 so that my customers maximally benefit from my hardware.
 
-As a vendor of a platform with hardware acceleration for some crypto,  
-I want to build Mbed TLS without software that replicates what my hardware does,  
+As a vendor of a platform with hardware acceleration for some crypto,
+I want to build Mbed TLS without software that replicates what my hardware does,
 to minimize the code size.
 
+#### Integrators of Mbed TLS alongside a PSA Crypto provider
+
+I have a platform where the PSA Crypto is already provided "externally" from
+Mbed TLS (ex: through TF-M in Zephyr) and I would like Mbed TLS to make use
+of it whenever possible in order to benefit from higher performances (if some
+hardware acceleration is supported in the provider) and/or higher isolation/security
+(if the PSA provider is running in a completetly separated/inaccessible context).
+
 #### Maintainer user stories
 
-As a maintainer of Mbed TLS,  
-I want to have clear rules for when to use which interface,  
+As a maintainer of Mbed TLS,
+I want to have clear rules for when to use which interface,
 to avoid bugs in “unusual” configurations.
 
-As a maintainer of Mbed TLS,  
-I want to avoid duplicating code,  
+As a maintainer of Mbed TLS,
+I want to avoid duplicating code,
 because this is inefficient and error-prone.
 
 ### Use PSA more
@@ -55,8 +63,8 @@
 
 The goal of this work is to arrange for more non-PSA interfaces to use PSA interfaces under the hood, without breaking code in the cases where this doesn't work. Using PSA interfaces has two benefits:
 
-* Where a PSA driver is available, it likely has better performance, and sometimes better security, than the built-in software implementation.
-* In many scenarios, where a PSA driver is available, this allows removing the software implementation altogether.
+* Where a PSA driver/provider is available, it likely has better performance, and sometimes better security, than the built-in software implementation.
+* In many scenarios, where a PSA driver/provider is available, this allows removing the software implementation altogether.
 * We may be able to get rid of some redundancies, for example the duplication between the implementations of HMAC in `md.c` and in `psa_crypto_mac.c`, and HKDF in `hkdf.c` and `psa_crypto.c`.
 
 ### Correct dependencies
@@ -72,7 +80,6 @@
 The following configuration options are described as experimental, and are likely to change at least marginally:
 
 * `MBEDTLS_PSA_CRYPTO_CLIENT`: “This interface is experimental and may change or be removed without notice.” In practice we don't want to remove this, but we may constrain how it's used.
-* `MBEDTLS_PSA_CRYPTO_DRIVERS`: “This interface is experimental. We intend to maintain backward compatibility with application code that relies on drivers, but the driver interfaces may change without notice.” In practice, this may mean constraints not only on how to write drivers, but also on how to integrate drivers into code that is platform code more than application code.
 * `MBEDTLS_PSA_CRYPTO_CONFIG`: “This feature is still experimental and is not ready for production since it is not completed.” We may want to change this, for example, to automatically enable more mechanisms (although this wouldn't be considered a backward compatibility break anyway, since we don't promise that you will not get a feature if you don't enable its `PSA_WANT_xxx`).
 
 ### Non-goals
@@ -190,7 +197,7 @@
 
 Here are some reasons why calling `psa_xxx()` to perform a hash or cipher calculation might not be desirable in some circumstances, explaining why the application would arrange to call the legacy software implementation instead.
 
-* `MBEDTLS_PSA_CRYPTO_C` is disabled.
+* `MBEDTLS_PSA_CRYPTO_CLIENT` is disabled.
 * There is a PSA driver which has not been initialized (this happens in `psa_crypto_init()`).
 * For ciphers, the keystore is not initialized yet, and Mbed TLS uses a custom implementation of PSA ITS where the file system is not accessible yet (because something else needs to happen first, and the application takes care that it happens before it calls `psa_crypto_init()`). A possible workaround may be to dispatch to the internal functions that are called after the keystore lookup, rather than to the PSA API functions (but this is incompatible with `MBEDTLS_PSA_CRYPTO_CLIENT`).
 * The requested mechanism is enabled in the legacy interface but not in the PSA interface. This was not really intended, but is possible, for example, if you enable `MBEDTLS_MD5_C` for PEM decoding with PBKDF1 but don't want `PSA_ALG_WANT_MD5` because it isn't supported for `PSA_ALG_RSA_PSS` and `PSA_ALG_DETERMINISTIC_ECDSA`.
@@ -208,7 +215,7 @@
 
 #### Non-support guarantees: requirements
 
-Generally speaking, just because some feature is not enabled in `mbedtls_config.h` or `psa_config.h` doesn't guarantee that it won't be enabled in the build. We can enable additional features through `build_info.h`.
+Generally speaking, just because some feature is not enabled in `mbedtls_config.h` or `crypto_config.h` doesn't guarantee that it won't be enabled in the build. We can enable additional features through `build_info.h` and other header files included there (`*adjust*.h`).
 
 If `PSA_WANT_xxx` is disabled, this should guarantee that attempting xxx through the PSA API will fail. This is generally guaranteed by the test suite `test_suite_psa_crypto_not_supported` with automatically enumerated test cases, so it would be inconvenient to carve out an exception.
 
@@ -331,14 +338,10 @@
 
 This will go away naturally in 4.0 when this macros is not longer an option (because it's always on).
 
-#### Don't support for `MBEDTLS_PSA_CRYPTO_CLIENT` without `MBEDTLS_PSA_CRYPTO_C`
+#### Support for `MBEDTLS_PSA_CRYPTO_CLIENT` without `MBEDTLS_PSA_CRYPTO_C`
 
 We generally don't really support builds with `MBEDTLS_PSA_CRYPTO_CLIENT` without `MBEDTLS_PSA_CRYPTO_C`. For example, both `MBEDTLS_USE_PSA_CRYPTO` and `MBEDTLS_SSL_PROTO_TLS1_3` require `MBEDTLS_PSA_CRYPTO_C`, while in principle they should only require `MBEDTLS_PSA_CRYPTO_CLIENT`.
 
-Considering this existing restriction which we do not plan to lift before 4.0, it is acceptable driver-only hashes and cipher support to have the same restriction in 3.x.
-
-It is however desirable for the design to keep support for `MBEDTLS_PSA_CRYPTO_CLIENT` in mind, in order to avoid making it more difficult to add in the future.
-
 #### For cipher: prioritize constrained devices and modern TLS
 
 The primary target is a configuration like TF-M's medium profile, plus TLS with only AEAD ciphersuites.
@@ -420,8 +423,9 @@
 
 For each hash algorithm, `md.h` defines a macro `MBEDTLS_MD_CAN_xxx` whenever the corresponding hash is available through MD light. These macros are only defined when `MBEDTLS_MD_LIGHT` is enabled. Per “[Availability of hashes](#availability-of-hashes)”, `MBEDTLS_MD_CAN_xxx` is enabled if:
 
-* the corresponding `MBEDTLS_xxx_C` is defined; or
-* one of `MBEDTLS_PSA_CRYPTO_C` or `MBEDTLS_PSA_CRYPTO_CLIENT` is enabled, and the corresponding `PSA_WANT_ALG_xxx` is enabled.
+* the corresponding `MBEDTLS_xxx_C` is defined.
+* `MBEDTLS_PSA_CRYPTO_C` is enabled and the corresponding `PSA_WANT_ALG_xxx` and `MBEDTLS_PSA_ACCEL_ALG_xxx` are enabled. This enables driver acceleration support.
+* `MBEDTLS_PSA_CRYPTO_CLIENT` is enabled the corresponding `PSA_WANT_ALG_xxx` is enabled. Then the Mbed TLS library must be linked against the PSA Crypto provider one which will eventually handle all PSA calls.
 
 Note that some algorithms have different spellings in legacy and PSA. Since MD is a legacy interface, we'll use the legacy names. Thus, for example:
 
@@ -440,7 +444,7 @@
 
 #### MD light internal support macros
 
-* If at least one hash has a PSA driver, define `MBEDTLS_MD_SOME_PSA`.
+* If at least one hash has a PSA driver or support in PSA Crypto provider, define `MBEDTLS_MD_SOME_PSA`.
 * If at least one hash has a legacy implementation, defined `MBEDTLS_MD_SOME_LEGACY`.
 
 #### Support for PSA in the MD context
@@ -488,15 +492,24 @@
 
 #### Determination of PSA support at runtime
 
+Mbed TLS defines internal symbols `MBEDTLS_MD_xxx_VIA_PSA` which are used to check if the `xxx` hash algorithm is supported in PSA. They are enabled when:
+
+* `MBEDTLS_PSA_CRYPTO_C && MBEDTLS_PSA_ACCEL_ALG_xxx`, i.e. when the PSA Crypto core is built with Mbed TLS and the `xxx` is accelerated through a driver.
+* `MBEDTLS_PSA_CRYPTO_CLIENT && PSA_WANT_ALG_xxx`, i.e. there is a PSA Crypto provider/server which supports `xxx` hash algorithm.
+
+MD internally uses the following private function to determine if PSA can be used at runtime or not:
+
 ```
-int psa_can_do_hash(psa_algorithm_t hash_alg);
+static int md_can_use_psa(const mbedtls_md_info_t *info)
 ```
 
-The job of this private function is to return 1 if `hash_alg` can be performed through PSA now, and 0 otherwise. It is only defined on algorithms that are enabled via PSA.
+Internally this function does the following:
 
-As a starting point, return 1 if PSA crypto's driver subsystem has been initialized.
+* First of all it converts the `mbedtls_md_info_t` to `psa_algorithm_t`. The result of this conversion is based on the `MBEDTLS_MD_xxx_VIA_PSA` symbols: if an algorithm does not have the corresponding `MBEDTLS_MD_xxx_VIA_PSA` enabled, then `md_can_use_psa` will return false.
 
-Usage note: for algorithms that are not enabled via PSA, calling `psa_can_do_hash` is generally safe: whether it returns 0 or 1, you can call a PSA hash function on the algorithm and it will return `PSA_ERROR_NOT_SUPPORTED`.
+* `int psa_can_do_hash(psa_algorithm_t hash_alg)` is then used to further checking if the PSA Crypto core has been initialized or not. If so then `md_can_use_psa` will finally succeed, otherwise it will fail.
+
+To be noted that in client/server builds (i.e. `MBEDTLS_PSA_CRYPTO_CLIENT && !MBEDTLS_PSA_CRYPTO_C`) the implementer of the client interface is expected to provide psa_can_do_hash().
 
 #### Support for PSA dispatch in hash operations
 
@@ -506,7 +519,7 @@
 
 If given an algorithm as an `mbedtls_md_type_t type` (possibly being the `type` field of a `const mbedtls_md_info_t *`):
 
-* If there is a PSA accelerator for this hash and `psa_can_do_hash(alg)`, call the corresponding PSA function, and if applicable set the engine to `MBEDTLS_MD_ENGINE_PSA`. (Skip this is `MBEDTLS_MD_SOME_PSA` is not defined.)
+* If there is a PSA accelerator/provider for this hash and `md_can_use_psa` succeeds, call the corresponding PSA function, and if applicable set the engine to `MBEDTLS_MD_ENGINE_PSA`. (Skip this is `MBEDTLS_MD_SOME_PSA` is not defined.)
 * Otherwise dispatch to the legacy module based on the type as currently done. (Skip this is `MBEDTLS_MD_SOME_LEGACY` is not defined.)
 * If no dispatch is possible, return `MBEDTLS_ERR_MD_FEATURE_UNAVAILABLE`.
 
@@ -522,7 +535,7 @@
 
 > If an algorithm has a legacy implementation, it is also available through PSA.
 
-When `MBEDTLS_PSA_CRYPTO_CONFIG` is disabled, this is already the case. When is enabled, we will now make it so as well. Change `include/mbedtls/config_psa.h` accordingly.
+When `MBEDTLS_PSA_CRYPTO_CONFIG` is disabled, this is already the case. When is enabled, `include/config_adjust_psa_superset_legacy.h` will ensure that PSA configuration is always a superset of what's enabled in legacy.
 
 ### MD light optimizations
 
@@ -557,15 +570,6 @@
 
 PSA has its own HMAC implementation. In builds with both `MBEDTLS_MD_C` and `PSA_WANT_ALG_HMAC` not fully provided by drivers, we should have a single implementation. Replace the one in `md.h` by calls to the PSA driver interface. This will also give mixed-domain modules access to HMAC accelerated directly by a PSA driver (eliminating the need to a HMAC interface in software if all supported hashes have an accelerator that includes HMAC support).
 
-### Improving support for `MBEDTLS_PSA_CRYPTO_CLIENT`
-
-So far, MD light only dispatches to PSA if an algorithm is available via `MBEDTLS_PSA_CRYPTO_C`, not if it's available via `MBEDTLS_PSA_CRYPTO_CLIENT`. This is acceptable because `MBEDTLS_USE_PSA_CRYPTO` requires `MBEDTLS_PSA_CRYPTO_C`, hence mixed-domain code never invokes PSA.
-
-The architecture can be extended to support `MBEDTLS_PSA_CRYPTO_CLIENT` with a little extra work. Here is an overview of the task breakdown, which should be fleshed up after we've done the first [migration](#migration-to-md-light):
-
-* Compile-time dependencies: instead of checking `defined(MBEDTLS_PSA_CRYPTO_C)`, check `defined(MBEDTLS_PSA_CRYPTO_C) || defined(MBEDTLS_PSA_CRYPTO_CLIENT)`.
-* Implementers of `MBEDTLS_PSA_CRYPTO_CLIENT` will need to provide `psa_can_do_hash()` (or a more general function `psa_can_do`) alongside `psa_crypto_init()`. Note that at this point, it will become a public interface, hence we won't be able to change it at a whim.
-
 ### Internal "block cipher" abstraction (previously known as "Cipher light")
 
 #### Definition
diff --git a/docs/architecture/psa-migration/strategy.md b/docs/architecture/psa-migration/strategy.md
index a89fe67..5c5d83f 100644
--- a/docs/architecture/psa-migration/strategy.md
+++ b/docs/architecture/psa-migration/strategy.md
@@ -36,7 +36,9 @@
 We currently have a few compile-time options that are relevant to the migration:
 
 - `MBEDTLS_PSA_CRYPTO_C` - enabled by default, controls the presence of the PSA
-  Crypto APIs.
+  Crypto APIs with their implementations. (Builds with only
+  `MBEDTLS_PSA_CRYPTO_CLIENT`, where PSA crypto APIs are present but
+  implemented via third-party code, are out of scope of this document.)
 - `MBEDTLS_USE_PSA_CRYPTO` - disabled by default (enabled in "full" config),
   controls usage of PSA Crypto APIs to perform operations in X.509 and TLS
 (G1 above), as well as the availability of some new APIs (G2 above).
diff --git a/docs/architecture/testing/invasive-testing.md b/docs/architecture/testing/invasive-testing.md
index 464f761..bf8d631 100644
--- a/docs/architecture/testing/invasive-testing.md
+++ b/docs/architecture/testing/invasive-testing.md
@@ -275,7 +275,7 @@
 
 Goal: test that `mbedtls_platform_zeroize` does wipe the memory buffer.
 
-Solution ([debugger](#debugger-based-testing)): implemented in `tests/scripts/test_zeroize.gdb`.
+Solution ([debugger](#debugger-based-testing)): implemented in `framework/tests/programs/test_zeroize.gdb`.
 
 Rationale: this cannot be tested by adding C code, because the danger is that the compiler optimizes the zeroization away, and any C code that observes the zeroization would cause the compiler not to optimize it away.
 
diff --git a/docs/driver-only-builds.md b/docs/driver-only-builds.md
index 6bd9262..e85496a 100644
--- a/docs/driver-only-builds.md
+++ b/docs/driver-only-builds.md
@@ -278,9 +278,11 @@
 removing builtin support (i.e. `MBEDTLS_DHM_C`).
 
 Note that the PSA API only supports FFDH with RFC 7919 groups, whereas the
-Mbed TLS legacy API supports custom groups. As a consequence, the TLS layer
-of Mbed TLS only supports DHE cipher suites if built-in FFDH
+Mbed TLS legacy API supports custom groups. As a consequence, the TLS 1.2
+layer of Mbed TLS only supports DHE cipher suites if built-in FFDH
 (`MBEDTLS_DHM_C`) is present, even when `MBEDTLS_USE_PSA_CRYPTO` is enabled.
+(The TLS 1.3 layer uses PSA, and this is not a limitation because the
+protocol does not allow custom FFDH groups.)
 
 RSA
 ---
diff --git a/doxygen/input/doc_mainpage.h b/doxygen/input/doc_mainpage.h
index d872818..f903d3f 100644
--- a/doxygen/input/doc_mainpage.h
+++ b/doxygen/input/doc_mainpage.h
@@ -10,7 +10,7 @@
  */
 
 /**
- * @mainpage Mbed TLS v3.6.2 API Documentation
+ * @mainpage Mbed TLS v3.6.3 API Documentation
  *
  * This documentation describes the internal structure of Mbed TLS.  It was
  * automatically generated from specially formatted comment blocks in
diff --git a/doxygen/mbedtls.doxyfile b/doxygen/mbedtls.doxyfile
index 281f062..0acead9 100644
--- a/doxygen/mbedtls.doxyfile
+++ b/doxygen/mbedtls.doxyfile
@@ -1,4 +1,4 @@
-PROJECT_NAME           = "Mbed TLS v3.6.2"
+PROJECT_NAME           = "Mbed TLS v3.6.3"
 OUTPUT_DIRECTORY       = ../apidoc/
 FULL_PATH_NAMES        = NO
 OPTIMIZE_OUTPUT_FOR_C  = YES
diff --git a/framework b/framework
index beb5dad..1e7b5d5 160000
--- a/framework
+++ b/framework
@@ -1 +1 @@
-Subproject commit beb5dadbf26156f434ea58dce5c5a3182df4c60f
+Subproject commit 1e7b5d54d3823b65fd4755bcf60f9ca39cfcbca3
diff --git a/include/mbedtls/build_info.h b/include/mbedtls/build_info.h
index d91d296..e70c4d7 100644
--- a/include/mbedtls/build_info.h
+++ b/include/mbedtls/build_info.h
@@ -26,16 +26,16 @@
  */
 #define MBEDTLS_VERSION_MAJOR  3
 #define MBEDTLS_VERSION_MINOR  6
-#define MBEDTLS_VERSION_PATCH  2
+#define MBEDTLS_VERSION_PATCH  3
 
 /**
  * The single version number has the following structure:
  *    MMNNPP00
  *    Major version | Minor version | Patch version
  */
-#define MBEDTLS_VERSION_NUMBER         0x03060200
-#define MBEDTLS_VERSION_STRING         "3.6.2"
-#define MBEDTLS_VERSION_STRING_FULL    "Mbed TLS 3.6.2"
+#define MBEDTLS_VERSION_NUMBER         0x03060300
+#define MBEDTLS_VERSION_STRING         "3.6.3"
+#define MBEDTLS_VERSION_STRING_FULL    "Mbed TLS 3.6.3"
 
 /* Macros for build-time platform detection */
 
diff --git a/include/mbedtls/config_adjust_legacy_crypto.h b/include/mbedtls/config_adjust_legacy_crypto.h
index 3ba987e..331ac9b 100644
--- a/include/mbedtls/config_adjust_legacy_crypto.h
+++ b/include/mbedtls/config_adjust_legacy_crypto.h
@@ -48,6 +48,13 @@
 #endif
 #endif /* _MINGW32__ || (_MSC_VER && (_MSC_VER <= 1900)) */
 
+/* If MBEDTLS_PSA_CRYPTO_C is defined, make sure MBEDTLS_PSA_CRYPTO_CLIENT
+ * is defined as well to include all PSA code.
+ */
+#if defined(MBEDTLS_PSA_CRYPTO_C)
+#define MBEDTLS_PSA_CRYPTO_CLIENT
+#endif /* MBEDTLS_PSA_CRYPTO_C */
+
 /* Auto-enable CIPHER_C when any of the unauthenticated ciphers is builtin
  * in PSA. */
 #if defined(MBEDTLS_PSA_CRYPTO_C) && \
@@ -158,7 +165,66 @@
 #define MBEDTLS_MD_SHA3_512_VIA_PSA
 #define MBEDTLS_MD_SOME_PSA
 #endif
-#endif /* MBEDTLS_PSA_CRYPTO_C */
+
+#elif defined(MBEDTLS_PSA_CRYPTO_CLIENT)
+
+#if defined(PSA_WANT_ALG_MD5)
+#define MBEDTLS_MD_CAN_MD5
+#define MBEDTLS_MD_MD5_VIA_PSA
+#define MBEDTLS_MD_SOME_PSA
+#endif
+#if defined(PSA_WANT_ALG_SHA_1)
+#define MBEDTLS_MD_CAN_SHA1
+#define MBEDTLS_MD_SHA1_VIA_PSA
+#define MBEDTLS_MD_SOME_PSA
+#endif
+#if defined(PSA_WANT_ALG_SHA_224)
+#define MBEDTLS_MD_CAN_SHA224
+#define MBEDTLS_MD_SHA224_VIA_PSA
+#define MBEDTLS_MD_SOME_PSA
+#endif
+#if defined(PSA_WANT_ALG_SHA_256)
+#define MBEDTLS_MD_CAN_SHA256
+#define MBEDTLS_MD_SHA256_VIA_PSA
+#define MBEDTLS_MD_SOME_PSA
+#endif
+#if defined(PSA_WANT_ALG_SHA_384)
+#define MBEDTLS_MD_CAN_SHA384
+#define MBEDTLS_MD_SHA384_VIA_PSA
+#define MBEDTLS_MD_SOME_PSA
+#endif
+#if defined(PSA_WANT_ALG_SHA_512)
+#define MBEDTLS_MD_CAN_SHA512
+#define MBEDTLS_MD_SHA512_VIA_PSA
+#define MBEDTLS_MD_SOME_PSA
+#endif
+#if defined(PSA_WANT_ALG_RIPEMD160)
+#define MBEDTLS_MD_CAN_RIPEMD160
+#define MBEDTLS_MD_RIPEMD160_VIA_PSA
+#define MBEDTLS_MD_SOME_PSA
+#endif
+#if defined(PSA_WANT_ALG_SHA3_224)
+#define MBEDTLS_MD_CAN_SHA3_224
+#define MBEDTLS_MD_SHA3_224_VIA_PSA
+#define MBEDTLS_MD_SOME_PSA
+#endif
+#if defined(PSA_WANT_ALG_SHA3_256)
+#define MBEDTLS_MD_CAN_SHA3_256
+#define MBEDTLS_MD_SHA3_256_VIA_PSA
+#define MBEDTLS_MD_SOME_PSA
+#endif
+#if defined(PSA_WANT_ALG_SHA3_384)
+#define MBEDTLS_MD_CAN_SHA3_384
+#define MBEDTLS_MD_SHA3_384_VIA_PSA
+#define MBEDTLS_MD_SOME_PSA
+#endif
+#if defined(PSA_WANT_ALG_SHA3_512)
+#define MBEDTLS_MD_CAN_SHA3_512
+#define MBEDTLS_MD_SHA3_512_VIA_PSA
+#define MBEDTLS_MD_SOME_PSA
+#endif
+
+#endif /* !MBEDTLS_PSA_CRYPTO_CLIENT && !MBEDTLS_PSA_CRYPTO_C */
 
 /* Built-in implementations */
 #if defined(MBEDTLS_MD5_C)
@@ -352,13 +418,6 @@
 #define MBEDTLS_PK_CAN_ECDSA_SOME
 #endif
 
-/* If MBEDTLS_PSA_CRYPTO_C is defined, make sure MBEDTLS_PSA_CRYPTO_CLIENT
- * is defined as well to include all PSA code.
- */
-#if defined(MBEDTLS_PSA_CRYPTO_C)
-#define MBEDTLS_PSA_CRYPTO_CLIENT
-#endif /* MBEDTLS_PSA_CRYPTO_C */
-
 /* Helpers to state that each key is supported either on the builtin or PSA side. */
 #if defined(MBEDTLS_ECP_DP_SECP521R1_ENABLED) || defined(PSA_WANT_ECC_SECP_R1_521)
 #define MBEDTLS_ECP_HAVE_SECP521R1
diff --git a/include/mbedtls/config_adjust_legacy_from_psa.h b/include/mbedtls/config_adjust_legacy_from_psa.h
index 04bdae6..48f1bab 100644
--- a/include/mbedtls/config_adjust_legacy_from_psa.h
+++ b/include/mbedtls/config_adjust_legacy_from_psa.h
@@ -69,7 +69,6 @@
     (defined(PSA_WANT_ECC_SECP_R1_384) && !defined(MBEDTLS_PSA_ACCEL_ECC_SECP_R1_384)) || \
     (defined(PSA_WANT_ECC_SECP_R1_521) && !defined(MBEDTLS_PSA_ACCEL_ECC_SECP_R1_521)) || \
     (defined(PSA_WANT_ECC_SECP_K1_192) && !defined(MBEDTLS_PSA_ACCEL_ECC_SECP_K1_192)) || \
-    (defined(PSA_WANT_ECC_SECP_K1_224) && !defined(MBEDTLS_PSA_ACCEL_ECC_SECP_K1_224)) || \
     (defined(PSA_WANT_ECC_SECP_K1_256) && !defined(MBEDTLS_PSA_ACCEL_ECC_SECP_K1_256))
 #define MBEDTLS_PSA_ECC_ACCEL_INCOMPLETE_CURVES
 #define MBEDTLS_PSA_ECC_ACCEL_INCOMPLETE_WEIERSTRASS_CURVES
@@ -225,17 +224,6 @@
 #endif /* missing accel */
 #endif /* PSA_WANT_ECC_SECP_K1_192 */
 
-#if defined(PSA_WANT_ECC_SECP_K1_224)
-#if !defined(MBEDTLS_PSA_ACCEL_ECC_SECP_K1_224) || \
-    defined(MBEDTLS_PSA_ECC_ACCEL_INCOMPLETE_KEY_TYPES) || \
-    defined(MBEDTLS_PSA_ECC_ACCEL_INCOMPLETE_ALGS)
-#define MBEDTLS_PSA_BUILTIN_ECC_SECP_K1_224 1
-#define MBEDTLS_ECP_DP_SECP224K1_ENABLED
-/* https://github.com/Mbed-TLS/mbedtls/issues/3541 */
-#error "SECP224K1 is buggy via the PSA API in Mbed TLS."
-#endif /* missing accel */
-#endif /* PSA_WANT_ECC_SECP_K1_224 */
-
 #if defined(PSA_WANT_ECC_SECP_K1_256)
 #if !defined(MBEDTLS_PSA_ACCEL_ECC_SECP_K1_256) || \
     defined(MBEDTLS_PSA_ECC_ACCEL_INCOMPLETE_KEY_TYPES) || \
@@ -782,13 +770,6 @@
 #define PSA_HAVE_SOFT_BLOCK_CIPHER 1
 #endif
 
-#if defined(PSA_WANT_ALG_CBC_MAC)
-#if !defined(MBEDTLS_PSA_ACCEL_ALG_CBC_MAC)
-#error "CBC-MAC is not yet supported via the PSA API in Mbed TLS."
-#define MBEDTLS_PSA_BUILTIN_ALG_CBC_MAC 1
-#endif /* !MBEDTLS_PSA_ACCEL_ALG_CBC_MAC */
-#endif /* PSA_WANT_ALG_CBC_MAC */
-
 #if defined(PSA_WANT_ALG_CMAC)
 #if !defined(MBEDTLS_PSA_ACCEL_ALG_CMAC) || \
     defined(PSA_HAVE_SOFT_BLOCK_CIPHER)
diff --git a/include/mbedtls/config_adjust_psa_superset_legacy.h b/include/mbedtls/config_adjust_psa_superset_legacy.h
index ef65cce..1a232cb 100644
--- a/include/mbedtls/config_adjust_psa_superset_legacy.h
+++ b/include/mbedtls/config_adjust_psa_superset_legacy.h
@@ -136,13 +136,6 @@
 #endif /* PSA_WANT_ECC_SECP_K1_192 */
 #endif /* MBEDTLS_ECP_DP_SECP192K1_ENABLED */
 
-/* SECP224K1 is buggy via the PSA API (https://github.com/Mbed-TLS/mbedtls/issues/3541) */
-#if 0 && defined(MBEDTLS_ECP_DP_SECP224K1_ENABLED)
-#if !defined(PSA_WANT_ECC_SECP_K1_224)
-#define PSA_WANT_ECC_SECP_K1_224 1
-#endif /* PSA_WANT_ECC_SECP_K1_224 */
-#endif /* MBEDTLS_ECP_DP_SECP224K1_ENABLED */
-
 #if defined(MBEDTLS_ECP_DP_SECP256K1_ENABLED)
 #if !defined(PSA_WANT_ECC_SECP_K1_256)
 #define PSA_WANT_ECC_SECP_K1_256 1
diff --git a/include/mbedtls/debug.h b/include/mbedtls/debug.h
index 424ed4b..e6f5dad 100644
--- a/include/mbedtls/debug.h
+++ b/include/mbedtls/debug.h
@@ -108,16 +108,16 @@
  *
  * This module provides debugging functions.
  */
-#if (defined(__MINGW32__) && __USE_MINGW_ANSI_STDIO == 0) || (defined(_MSC_VER) && _MSC_VER < 1800)
+#if defined(__MINGW32__) || (defined(_MSC_VER) && _MSC_VER < 1900)
    #include <inttypes.h>
    #define MBEDTLS_PRINTF_SIZET     PRIuPTR
    #define MBEDTLS_PRINTF_LONGLONG  "I64d"
 #else \
-    /* (defined(__MINGW32__)  && __USE_MINGW_ANSI_STDIO == 0) || (defined(_MSC_VER) && _MSC_VER < 1800) */
+    /* defined(__MINGW32__) || (defined(_MSC_VER) && _MSC_VER < 1900) */
    #define MBEDTLS_PRINTF_SIZET     "zu"
    #define MBEDTLS_PRINTF_LONGLONG  "lld"
 #endif \
-    /* (defined(__MINGW32__)  && __USE_MINGW_ANSI_STDIO == 0) || (defined(_MSC_VER) && _MSC_VER < 1800) */
+    /* defined(__MINGW32__) || (defined(_MSC_VER) && _MSC_VER < 1900) */
 
 #if !defined(MBEDTLS_PRINTF_MS_TIME)
 #include <inttypes.h>
diff --git a/include/mbedtls/error.h b/include/mbedtls/error.h
index 186589a..635f7cd 100644
--- a/include/mbedtls/error.h
+++ b/include/mbedtls/error.h
@@ -81,7 +81,7 @@
  * MD        5   5
  * HKDF      5   1 (Started from top)
  * PKCS7     5   12 (Started from 0x5300)
- * SSL       5   2 (Started from 0x5F00)
+ * SSL       5   3 (Started from 0x5F00)
  * CIPHER    6   8 (Started from 0x6080)
  * SSL       6   22 (Started from top, plus 0x6000)
  * SSL       7   20 (Started from 0x7000, gaps at
diff --git a/include/mbedtls/mbedtls_config.h b/include/mbedtls/mbedtls_config.h
index ebc9276..d283136 100644
--- a/include/mbedtls/mbedtls_config.h
+++ b/include/mbedtls/mbedtls_config.h
@@ -1622,6 +1622,46 @@
  */
 //#define MBEDTLS_SSL_ASYNC_PRIVATE
 
+/** \def MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME
+ *
+ * In TLS clients, when a client authenticates a server through its
+ * certificate, the client normally checks three things:
+ * - the certificate chain must be valid;
+ * - the chain must start from a trusted CA;
+ * - the certificate must cover the server name that is expected by the client.
+ *
+ * Omitting any of these checks is generally insecure, and can allow a
+ * malicious server to impersonate a legitimate server.
+ *
+ * The third check may be safely skipped in some unusual scenarios,
+ * such as networks where eavesdropping is a risk but not active attacks,
+ * or a private PKI where the client equally trusts all servers that are
+ * accredited by the root CA.
+ *
+ * You should call mbedtls_ssl_set_hostname() with the expected server name
+ * before starting a TLS handshake on a client (unless the client is
+ * set up to only use PSK-based authentication, which does not rely on the
+ * host name). This configuration option controls what happens if a TLS client
+ * is configured with the authentication mode #MBEDTLS_SSL_VERIFY_REQUIRED
+ * (default), certificate authentication is enabled and the client does not
+ * call mbedtls_ssl_set_hostname():
+ *
+ * - If this option is unset (default), the connection attempt is aborted
+ *   with the error #MBEDTLS_ERR_SSL_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME.
+ * - If this option is set, the TLS library does not check the server name
+ *   that the certificate is valid for. This is the historical behavior
+ *   of Mbed TLS, but may be insecure as explained above.
+ *
+ * Enable this option for strict backward compatibility if you have
+ * determined that it is secure in the scenario where you are using
+ * Mbed TLS.
+ *
+ * \deprecated This option exists only for backward compatibility and will
+ *             be removed in the next major version of Mbed TLS.
+ *
+ */
+//#define MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME
+
 /**
  * \def MBEDTLS_SSL_CONTEXT_SERIALIZATION
  *
@@ -1724,6 +1764,20 @@
 #define MBEDTLS_SSL_KEEP_PEER_CERTIFICATE
 
 /**
+ * \def MBEDTLS_SSL_KEYING_MATERIAL_EXPORT
+ *
+ * When this option is enabled, the client and server can extract additional
+ * shared symmetric keys after an SSL handshake using the function
+ * mbedtls_ssl_export_keying_material().
+ *
+ * The process for deriving the keys is specified in RFC 5705 for TLS 1.2 and
+ * in RFC 8446, Section 7.5, for TLS 1.3.
+ *
+ * Comment this macro to disable mbedtls_ssl_export_keying_material().
+ */
+#define MBEDTLS_SSL_KEYING_MATERIAL_EXPORT
+
+/**
  * \def MBEDTLS_SSL_RENEGOTIATION
  *
  * Enable support for TLS renegotiation.
@@ -1807,6 +1861,11 @@
  *       running handshake hash) only use PSA crypto if
  *       #MBEDTLS_USE_PSA_CRYPTO is enabled.
  *
+ * \note In multithreaded applications, you must also enable
+ *       #MBEDTLS_THREADING_C, even if individual TLS contexts are not
+ *       shared between threads, unless only one thread ever calls
+ *       TLS functions.
+ *
  * Uncomment this macro to enable the support for TLS 1.3.
  */
 #define MBEDTLS_SSL_PROTO_TLS1_3
@@ -2125,6 +2184,10 @@
  * before calling any function from the SSL/TLS, X.509 or PK modules, except
  * for the various mbedtls_xxx_init() functions which can be called at any time.
  *
+ * \warning In multithreaded applications, you must also enable
+ * #MBEDTLS_THREADING_C, unless only one thread ever calls PSA functions
+ * (`psa_xxx()`), including indirect calls through SSL/TLS, X.509 or PK.
+ *
  * \note An important and desirable effect of this option is that it allows
  * PK, X.509 and TLS to take advantage of PSA drivers. For example, enabling
  * this option is what allows use of drivers for ECDSA, ECDH and EC J-PAKE in
@@ -3211,7 +3274,18 @@
 /**
  * \def MBEDTLS_PSA_CRYPTO_C
  *
- * Enable the Platform Security Architecture cryptography API.
+ * Enable the Platform Security Architecture (PSA) cryptography API.
+ *
+ * \note In multithreaded applications, you must enable #MBEDTLS_THREADING_C,
+ *       unless only one thread ever calls `psa_xxx()` functions.
+ *       That includes indirect calls, such as:
+ *       - performing a TLS handshake if support for TLS 1.3 is enabled;
+ *       - using a TLS 1.3 connection;
+ *       - indirect calls from PK, X.509 or SSL functions when
+ *         #MBEDTLS_USE_PSA_CRYPTO is enabled;
+ *       - indirect calls to calculate a hash when #MBEDTLS_MD_C is disabled;
+ *       - any other call to a function that requires calling psa_crypto_init()
+ *         beforehand.
  *
  * Module:  library/psa_crypto.c
  *
@@ -3631,10 +3705,38 @@
  * \def MBEDTLS_THREADING_C
  *
  * Enable the threading abstraction layer.
- * By default Mbed TLS assumes it is used in a non-threaded environment or that
- * contexts are not shared between threads. If you do intend to use contexts
+ *
+ * Traditionally, Mbed TLS assumes it is used in a non-threaded environment or
+ * that contexts are not shared between threads. If you do intend to use contexts
  * between threads, you will need to enable this layer to prevent race
- * conditions. See also our Knowledge Base article about threading:
+ * conditions.
+ *
+ * The PSA subsystem has an implicit shared context. Therefore, you must
+ * enable this option if more than one thread may use any part of
+ * Mbed TLS that is implemented on top of the PSA subsystem.
+ *
+ * You must enable this option in multithreaded applications where more than
+ * one thread performs any of the following operations:
+ *
+ * - Any call to a PSA function (`psa_xxx()`).
+ * - Any call to a TLS, X.509 or PK function (`mbedtls_ssl_xxx()`,
+ *   `mbedtls_x509_xxx()`, `mbedtls_pkcs7_xxx()`, `mbedtls_pk_xxx()`)
+ *   if `MBEDTLS_USE_PSA_CRYPTO` is enabled (regardless of whether individual
+ *   TLS, X.509 or PK contexts are shared between threads).
+ * - A TLS 1.3 connection, regardless of the compile-time configuration.
+ * - Any library feature that calculates a hash, if `MBEDTLS_MD_C` is disabled.
+ *   As an exception, algorithm-specific low-level modules do not require
+ *   threading protection unless the contexts are shared between threads.
+ * - Any library feature that performs symmetric encryption or decryption,
+ *   if `MBEDTLS_CIPHER_C` is disabled.
+ *   As an exception, algorithm-specific low-level modules do not require
+ *   threading protection unless the contexts are shared between threads.
+ * - Any use of a cryptographic context if the same context is used in
+ *   multiple threads.
+ * - Any call to a function where the documentation specifies that
+ *   psa_crypto_init() must be called prior to that function.
+ *
+ * See also our Knowledge Base article about threading:
  * https://mbed-tls.readthedocs.io/en/latest/kb/development/thread-safety-and-multi-threading
  *
  * Module:  library/threading.c
diff --git a/include/mbedtls/net_sockets.h b/include/mbedtls/net_sockets.h
index 85c1197..8e69bc0 100644
--- a/include/mbedtls/net_sockets.h
+++ b/include/mbedtls/net_sockets.h
@@ -229,7 +229,7 @@
 
 /**
  * \brief          Write at most 'len' characters. If no error occurs,
- *                 the actual amount read is returned.
+ *                 the actual amount written is returned.
  *
  * \param ctx      Socket
  * \param buf      The buffer to read from
diff --git a/include/mbedtls/ssl.h b/include/mbedtls/ssl.h
index 42fffbf..cc9da34 100644
--- a/include/mbedtls/ssl.h
+++ b/include/mbedtls/ssl.h
@@ -166,6 +166,42 @@
 #define MBEDTLS_ERR_SSL_VERSION_MISMATCH                  -0x5F00
 /** Invalid value in SSL config */
 #define MBEDTLS_ERR_SSL_BAD_CONFIG                        -0x5E80
+/* Error space gap */
+/** Attempt to verify a certificate without an expected hostname.
+ * This is usually insecure.
+ *
+ * In TLS clients, when a client authenticates a server through its
+ * certificate, the client normally checks three things:
+ * - the certificate chain must be valid;
+ * - the chain must start from a trusted CA;
+ * - the certificate must cover the server name that is expected by the client.
+ *
+ * Omitting any of these checks is generally insecure, and can allow a
+ * malicious server to impersonate a legitimate server.
+ *
+ * The third check may be safely skipped in some unusual scenarios,
+ * such as networks where eavesdropping is a risk but not active attacks,
+ * or a private PKI where the client equally trusts all servers that are
+ * accredited by the root CA.
+ *
+ * You should call mbedtls_ssl_set_hostname() with the expected server name
+ * before starting a TLS handshake on a client (unless the client is
+ * set up to only use PSK-based authentication, which does not rely on the
+ * host name). If you have determined that server name verification is not
+ * required for security in your scenario, call mbedtls_ssl_set_hostname()
+ * with \p NULL as the server name.
+ *
+ * This error is raised if all of the following conditions are met:
+ *
+ * - A TLS client is configured with the authentication mode
+ *   #MBEDTLS_SSL_VERIFY_REQUIRED (default).
+ * - Certificate authentication is enabled.
+ * - The client does not call mbedtls_ssl_set_hostname().
+ * - The configuration option
+ *   #MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME
+ *   is not enabled.
+ */
+#define MBEDTLS_ERR_SSL_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME  -0x5D80
 
 /*
  * Constants from RFC 8446 for TLS 1.3 PSK modes
@@ -693,6 +729,14 @@
 /* Length in number of bytes of the TLS sequence number */
 #define MBEDTLS_SSL_SEQUENCE_NUMBER_LEN 8
 
+/* Helper to state that client_random and server_random need to be stored
+ * after the handshake is complete. This is required for context serialization
+ * and for the keying material exporter in TLS 1.2. */
+#if defined(MBEDTLS_SSL_CONTEXT_SERIALIZATION) || \
+    (defined(MBEDTLS_SSL_KEYING_MATERIAL_EXPORT) && defined(MBEDTLS_SSL_PROTO_TLS1_2))
+#define MBEDTLS_SSL_KEEP_RANDBYTES
+#endif
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -1724,7 +1768,16 @@
     int MBEDTLS_PRIVATE(early_data_state);
 #endif
 
-    unsigned MBEDTLS_PRIVATE(badmac_seen);       /*!< records with a bad MAC received    */
+    /** Multipurpose field.
+     *
+     * - DTLS: records with a bad MAC received.
+     * - TLS: accumulated length of handshake fragments (up to \c in_hslen).
+     *
+     * This field is multipurpose in order to preserve the ABI in the
+     * Mbed TLS 3.6 LTS branch. Until 3.6.2, it was only used in DTLS
+     * and called `badmac_seen`.
+     */
+    unsigned MBEDTLS_PRIVATE(badmac_seen_or_in_hsfraglen);
 
 #if defined(MBEDTLS_X509_CRT_PARSE_C)
     /** Callback to customize X.509 certificate chain verification          */
@@ -1884,8 +1937,35 @@
      * User settings
      */
 #if defined(MBEDTLS_X509_CRT_PARSE_C)
-    char *MBEDTLS_PRIVATE(hostname);             /*!< expected peer CN for verification
-                                                    (and SNI if available)                 */
+    /** Expected peer CN for verification.
+     *
+     * Also used on clients for SNI,
+     * and for TLS 1.3 session resumption using tickets.
+     *
+     * The value of this field can be:
+     * - \p NULL in a newly initialized or reset context.
+     * - A heap-allocated copy of the last value passed to
+     *   mbedtls_ssl_set_hostname(), if the last call had a non-null
+     *  \p hostname argument.
+     * - A special value to indicate that mbedtls_ssl_set_hostname()
+     *   was called with \p NULL (as opposed to never having been called).
+     *   See `mbedtls_ssl_get_hostname_pointer()` in `ssl_tls.c`.
+     *
+     * If this field contains the value \p NULL and the configuration option
+     * #MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME
+     * is unset, on a TLS client, attempting to verify a server certificate
+     * results in the error
+     * #MBEDTLS_ERR_SSL_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME.
+     *
+     * If this field contains the special value described above, or if
+     * the value is \p NULL and the configuration option
+     * #MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME
+     * is set, then the peer name verification is skipped, which may be
+     * insecure, especially on a client. Furthermore, on a client, the
+     * server_name extension is not sent, and the server name is ignored
+     * in TLS 1.3 session resumption using tickets.
+     */
+    char *MBEDTLS_PRIVATE(hostname);
 #endif /* MBEDTLS_X509_CRT_PARSE_C */
 
 #if defined(MBEDTLS_SSL_ALPN)
@@ -1993,6 +2073,14 @@
  *                 Calling mbedtls_ssl_setup again is not supported, even
  *                 if no session is active.
  *
+ * \warning        After setting up a client context, if certificate-based
+ *                 authentication is enabled, you should call
+ *                 mbedtls_ssl_set_hostname() to specifiy the expected
+ *                 name of the server. Without this, in most scenarios,
+ *                 the TLS connection is insecure. See
+ *                 #MBEDTLS_ERR_SSL_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME
+ *                 for more information.
+ *
  * \note           If #MBEDTLS_USE_PSA_CRYPTO is enabled, the PSA crypto
  *                 subsystem must have been initialized by calling
  *                 psa_crypto_init() before calling this function.
@@ -3967,16 +4055,29 @@
 #if defined(MBEDTLS_X509_CRT_PARSE_C)
 /**
  * \brief          Set or reset the hostname to check against the received
- *                 server certificate. It sets the ServerName TLS extension,
- *                 too, if that extension is enabled. (client-side only)
+ *                 peer certificate. On a client, this also sets the
+ *                 ServerName TLS extension, if that extension is enabled.
+ *                 On a TLS 1.3 client, this also sets the server name in
+ *                 the session resumption ticket, if that feature is enabled.
  *
  * \param ssl      SSL context
- * \param hostname the server hostname, may be NULL to clear hostname
-
- * \note           Maximum hostname length MBEDTLS_SSL_MAX_HOST_NAME_LEN.
+ * \param hostname The server hostname. This may be \c NULL to clear
+ *                 the hostname.
  *
- * \return         0 if successful, MBEDTLS_ERR_SSL_ALLOC_FAILED on
- *                 allocation failure, MBEDTLS_ERR_SSL_BAD_INPUT_DATA on
+ * \note           Maximum hostname length #MBEDTLS_SSL_MAX_HOST_NAME_LEN.
+ *
+ * \note           If the hostname is \c NULL on a client, then the server
+ *                 is not authenticated: it only needs to have a valid
+ *                 certificate, not a certificate matching its name.
+ *                 Therefore you should always call this function on a client,
+ *                 unless the connection is set up to only allow
+ *                 pre-shared keys, or in scenarios where server
+ *                 impersonation is not a concern. See the documentation of
+ *                 #MBEDTLS_ERR_SSL_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME
+ *                 for more details.
+ *
+ * \return         0 if successful, #MBEDTLS_ERR_SSL_ALLOC_FAILED on
+ *                 allocation failure, #MBEDTLS_ERR_SSL_BAD_INPUT_DATA on
  *                 too long input hostname.
  *
  *                 Hostname set to the one provided on success (cleared
@@ -4440,6 +4541,10 @@
  *                 with \c mbedtls_ssl_read()), not handshake messages.
  *                 With DTLS, this affects both ApplicationData and handshake.
  *
+ * \note           Defragmentation of TLS handshake messages is supported
+ *                 with some limitations. See the documentation of
+ *                 mbedtls_ssl_handshake() for details.
+ *
  * \note           This sets the maximum length for a record's payload,
  *                 excluding record overhead that will be added to it, see
  *                 \c mbedtls_ssl_get_record_expansion().
@@ -4970,6 +5075,24 @@
  *                 if a negotiation involving TLS 1.3 takes place (this may
  *                 be the case even if TLS 1.3 is offered but eventually
  *                 not selected).
+ *
+ * \note           In TLS, reception of fragmented handshake messages is
+ *                 supported with some limitations (those limitations do
+ *                 not apply to DTLS, where defragmentation is fully
+ *                 supported):
+ *                 - On an Mbed TLS server that only accepts TLS 1.2,
+ *                   the initial ClientHello message must not be fragmented.
+ *                   A TLS 1.2 ClientHello may be fragmented if the server
+ *                   also accepts TLS 1.3 connections (meaning
+ *                   that #MBEDTLS_SSL_PROTO_TLS1_3 enabled, and the
+ *                   accepted versions have not been restricted with
+ *                   mbedtls_ssl_conf_max_tls_version() or the like).
+ *                 - The first fragment of a handshake message must be
+ *                   at least 4 bytes long.
+ *                 - Non-handshake records must not be interleaved between
+ *                   the fragments of a handshake message. (This is permitted
+ *                   in TLS 1.2 but not in TLS 1.3, but Mbed TLS rejects it
+ *                   even in TLS 1.2.)
  */
 int mbedtls_ssl_handshake(mbedtls_ssl_context *ssl);
 
@@ -5652,6 +5775,41 @@
                          const unsigned char *random, size_t rlen,
                          unsigned char *dstbuf, size_t dlen);
 
+#if defined(MBEDTLS_SSL_KEYING_MATERIAL_EXPORT)
+/* Maximum value for key_len in mbedtls_ssl_export_keying material. Depending on the TLS
+ * version and the negotiated ciphersuite, larger keys could in principle be exported,
+ * but for simplicity, we define one limit that works in all cases. TLS 1.3 with SHA256
+ * has the strictest limit: 255 blocks of SHA256 output, or 8160 bytes. */
+#define MBEDTLS_SSL_EXPORT_MAX_KEY_LEN 8160
+
+/**
+ * \brief             TLS-Exporter to derive shared symmetric keys between server and client.
+ *
+ * \param ssl         SSL context from which to export keys. Must have finished the handshake.
+ * \param out         Output buffer of length at least key_len bytes.
+ * \param key_len     Length of the key to generate in bytes, must be at most
+ *                    MBEDTLS_SSL_EXPORT_MAX_KEY_LEN (8160).
+ * \param label       Label for which to generate the key of length label_len.
+ * \param label_len   Length of label in bytes. Must be at most 249 in TLS 1.3.
+ * \param context     Context of the key. Can be NULL if context_len or use_context is 0.
+ * \param context_len Length of context. Must be < 2^16 in TLS 1.2.
+ * \param use_context Indicates if a context should be used in deriving the key.
+ *
+ * \note TLS 1.2 makes a distinction between a 0-length context and no context.
+ *       This is why the use_context argument exists. TLS 1.3 does not make
+ *       this distinction. If use_context is 0 and TLS 1.3 is used, context and
+ *       context_len are ignored and a 0-length context is used.
+ *
+ * \return            0 on success.
+ * \return            MBEDTLS_ERR_SSL_BAD_INPUT_DATA if the handshake is not yet completed.
+ * \return            An SSL-specific error on failure.
+ */
+int mbedtls_ssl_export_keying_material(mbedtls_ssl_context *ssl,
+                                       uint8_t *out, const size_t key_len,
+                                       const char *label, const size_t label_len,
+                                       const unsigned char *context, const size_t context_len,
+                                       const int use_context);
+#endif
 #ifdef __cplusplus
 }
 #endif
diff --git a/include/psa/crypto_config.h b/include/psa/crypto_config.h
index 36e937a..e0a8d60 100644
--- a/include/psa/crypto_config.h
+++ b/include/psa/crypto_config.h
@@ -38,10 +38,6 @@
 #ifndef PSA_CRYPTO_CONFIG_H
 #define PSA_CRYPTO_CONFIG_H
 
-/*
- * CBC-MAC is not yet supported via the PSA API in Mbed TLS.
- */
-//#define PSA_WANT_ALG_CBC_MAC                    1
 #define PSA_WANT_ALG_CBC_NO_PADDING             1
 #define PSA_WANT_ALG_CBC_PKCS7                  1
 #define PSA_WANT_ALG_CCM                        1
@@ -84,22 +80,12 @@
 #define PSA_WANT_ALG_TLS12_PSK_TO_MS            1
 #define PSA_WANT_ALG_TLS12_ECJPAKE_TO_PMS       1
 
-/* XTS is not yet supported via the PSA API in Mbed TLS.
- * Note: when adding support, also adjust include/mbedtls/config_psa.h */
-//#define PSA_WANT_ALG_XTS                        1
-
 #define PSA_WANT_ECC_BRAINPOOL_P_R1_256         1
 #define PSA_WANT_ECC_BRAINPOOL_P_R1_384         1
 #define PSA_WANT_ECC_BRAINPOOL_P_R1_512         1
 #define PSA_WANT_ECC_MONTGOMERY_255             1
 #define PSA_WANT_ECC_MONTGOMERY_448             1
 #define PSA_WANT_ECC_SECP_K1_192                1
-/*
- * SECP224K1 is buggy via the PSA API in Mbed TLS
- * (https://github.com/Mbed-TLS/mbedtls/issues/3541). Thus, do not enable it by
- * default.
- */
-//#define PSA_WANT_ECC_SECP_K1_224                1
 #define PSA_WANT_ECC_SECP_K1_256                1
 #define PSA_WANT_ECC_SECP_R1_192                1
 #define PSA_WANT_ECC_SECP_R1_224                1
diff --git a/include/psa/crypto_extra.h b/include/psa/crypto_extra.h
index f48c087..a046ba5 100644
--- a/include/psa/crypto_extra.h
+++ b/include/psa/crypto_extra.h
@@ -583,6 +583,35 @@
 
 /** @} */
 
+/** \defgroup psa_crypto_client Functions defined by a client provider
+ *
+ * The functions in this group are meant to be implemented by providers of
+ * the PSA Crypto client interface. They are provided by the library when
+ * #MBEDTLS_PSA_CRYPTO_C is enabled.
+ *
+ * \note All functions in this group are experimental, as using
+ *       alternative client interface providers is experimental.
+ *
+ * @{
+ */
+
+/** Check if PSA is capable of handling the specified hash algorithm.
+ *
+ * This means that PSA core was built with the corresponding PSA_WANT_ALG_xxx
+ * set and that psa_crypto_init has already been called.
+ *
+ * \note When using Mbed TLS version of PSA core (i.e. MBEDTLS_PSA_CRYPTO_C is
+ *       set) for now this function only checks the state of the driver
+ *       subsystem, not the algorithm. This might be improved in the future.
+ *
+ * \param hash_alg  The hash algorithm.
+ *
+ * \return 1 if the PSA can handle \p hash_alg, 0 otherwise.
+ */
+int psa_can_do_hash(psa_algorithm_t hash_alg);
+
+/**@}*/
+
 /** \addtogroup crypto_types
  * @{
  */
diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt
index 4be9a54..e2a1920 100644
--- a/library/CMakeLists.txt
+++ b/library/CMakeLists.txt
@@ -300,7 +300,7 @@
 if(USE_SHARED_MBEDTLS_LIBRARY)
     set(CMAKE_LIBRARY_PATH ${CMAKE_CURRENT_BINARY_DIR})
     add_library(${mbedcrypto_target} SHARED ${src_crypto})
-    set_target_properties(${mbedcrypto_target} PROPERTIES VERSION 3.6.2 SOVERSION 16)
+    set_target_properties(${mbedcrypto_target} PROPERTIES VERSION 3.6.3 SOVERSION 16)
     target_link_libraries(${mbedcrypto_target} PUBLIC ${libs})
 
     if(TARGET ${everest_target})
@@ -312,11 +312,11 @@
     endif()
 
     add_library(${mbedx509_target} SHARED ${src_x509})
-    set_target_properties(${mbedx509_target} PROPERTIES VERSION 3.6.2 SOVERSION 7)
+    set_target_properties(${mbedx509_target} PROPERTIES VERSION 3.6.3 SOVERSION 7)
     target_link_libraries(${mbedx509_target} PUBLIC ${libs} ${mbedcrypto_target})
 
     add_library(${mbedtls_target} SHARED ${src_tls})
-    set_target_properties(${mbedtls_target} PROPERTIES VERSION 3.6.2 SOVERSION 21)
+    set_target_properties(${mbedtls_target} PROPERTIES VERSION 3.6.3 SOVERSION 21)
     target_link_libraries(${mbedtls_target} PUBLIC ${libs} ${mbedx509_target})
 endif(USE_SHARED_MBEDTLS_LIBRARY)
 
diff --git a/library/aesni.c b/library/aesni.c
index 8e5bd55..4fc1cb9 100644
--- a/library/aesni.c
+++ b/library/aesni.c
@@ -489,7 +489,7 @@
          "movdqu    %%xmm0, (%4)    \n\t" // export output
          :
          : "r" (ctx->nr), "r" (ctx->buf + ctx->rk_offset), "r" (mode), "r" (input), "r" (output)
-         : "memory", "cc", "xmm0", "xmm1");
+         : "memory", "cc", "xmm0", "xmm1", "0", "1");
 
 
     return 0;
@@ -679,7 +679,7 @@
          AESKEYGENA(xmm0_xmm1, "0x36")      "call 1b \n\t"
          :
          : "r" (rk), "r" (key)
-         : "memory", "cc", "0");
+         : "memory", "cc", "xmm0", "xmm1", "0");
 }
 
 /*
@@ -737,7 +737,7 @@
 
          :
          : "r" (rk), "r" (key)
-         : "memory", "cc", "0");
+         : "memory", "cc", "xmm0", "xmm1", "xmm2", "0");
 }
 #endif /* !MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH */
 
@@ -805,7 +805,7 @@
          AESKEYGENA(xmm1_xmm2, "0x40")      "call 1b \n\t"
          :
          : "r" (rk), "r" (key)
-         : "memory", "cc", "0");
+         : "memory", "cc", "xmm0", "xmm1", "xmm2", "0");
 }
 #endif /* !MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH */
 
diff --git a/library/cipher_wrap.c b/library/cipher_wrap.c
index d2fee22..b4c21ee 100644
--- a/library/cipher_wrap.c
+++ b/library/cipher_wrap.c
@@ -2425,7 +2425,7 @@
                      sizeof(mbedtls_cipher_definitions[0]))
 int mbedtls_cipher_supported[NUM_CIPHERS];
 
-const mbedtls_cipher_base_t *mbedtls_cipher_base_lookup_table[] = {
+const mbedtls_cipher_base_t * const mbedtls_cipher_base_lookup_table[] = {
 #if defined(MBEDTLS_AES_C)
     [MBEDTLS_CIPHER_BASE_INDEX_AES] = &aes_info,
 #endif
diff --git a/library/cipher_wrap.h b/library/cipher_wrap.h
index f229151..9564c5e 100644
--- a/library/cipher_wrap.h
+++ b/library/cipher_wrap.h
@@ -169,7 +169,7 @@
 
 extern int mbedtls_cipher_supported[];
 
-extern const mbedtls_cipher_base_t *mbedtls_cipher_base_lookup_table[];
+extern const mbedtls_cipher_base_t * const mbedtls_cipher_base_lookup_table[];
 
 #ifdef __cplusplus
 }
diff --git a/library/ecp.c b/library/ecp.c
index 427059b..fdd00a5 100644
--- a/library/ecp.c
+++ b/library/ecp.c
@@ -3056,7 +3056,7 @@
         /* see RFC 7748 sec. 5 para. 5 */
         if (mbedtls_mpi_get_bit(d, 0) != 0 ||
             mbedtls_mpi_get_bit(d, 1) != 0 ||
-            mbedtls_mpi_bitlen(d) - 1 != grp->nbits) {  /* mbedtls_mpi_bitlen is one-based! */
+            mbedtls_mpi_bitlen(d) != grp->nbits + 1) {  /* mbedtls_mpi_bitlen is one-based! */
             return MBEDTLS_ERR_ECP_INVALID_KEY;
         }
 
diff --git a/library/psa_crypto.c b/library/psa_crypto.c
index f0ccf3d..348c79c 100644
--- a/library/psa_crypto.c
+++ b/library/psa_crypto.c
@@ -2319,6 +2319,58 @@
 /* Message digests */
 /****************************************************************/
 
+static int is_hash_supported(psa_algorithm_t alg)
+{
+    switch (alg) {
+#if defined(PSA_WANT_ALG_MD5)
+        case PSA_ALG_MD5:
+            return 1;
+#endif
+#if defined(PSA_WANT_ALG_RIPEMD160)
+        case PSA_ALG_RIPEMD160:
+            return 1;
+#endif
+#if defined(PSA_WANT_ALG_SHA_1)
+        case PSA_ALG_SHA_1:
+            return 1;
+#endif
+#if defined(PSA_WANT_ALG_SHA_224)
+        case PSA_ALG_SHA_224:
+            return 1;
+#endif
+#if defined(PSA_WANT_ALG_SHA_256)
+        case PSA_ALG_SHA_256:
+            return 1;
+#endif
+#if defined(PSA_WANT_ALG_SHA_384)
+        case PSA_ALG_SHA_384:
+            return 1;
+#endif
+#if defined(PSA_WANT_ALG_SHA_512)
+        case PSA_ALG_SHA_512:
+            return 1;
+#endif
+#if defined(PSA_WANT_ALG_SHA3_224)
+        case PSA_ALG_SHA3_224:
+            return 1;
+#endif
+#if defined(PSA_WANT_ALG_SHA3_256)
+        case PSA_ALG_SHA3_256:
+            return 1;
+#endif
+#if defined(PSA_WANT_ALG_SHA3_384)
+        case PSA_ALG_SHA3_384:
+            return 1;
+#endif
+#if defined(PSA_WANT_ALG_SHA3_512)
+        case PSA_ALG_SHA3_512:
+            return 1;
+#endif
+        default:
+            return 0;
+    }
+}
+
 psa_status_t psa_hash_abort(psa_hash_operation_t *operation)
 {
     /* Aborting a non-active operation is allowed */
@@ -2962,16 +3014,44 @@
         if (!PSA_ALG_IS_SIGN_MESSAGE(alg)) {
             return PSA_ERROR_INVALID_ARGUMENT;
         }
+    }
 
-        if (PSA_ALG_IS_SIGN_HASH(alg)) {
-            if (!PSA_ALG_IS_HASH(PSA_ALG_SIGN_GET_HASH(alg))) {
-                return PSA_ERROR_INVALID_ARGUMENT;
-            }
-        }
-    } else {
-        if (!PSA_ALG_IS_SIGN_HASH(alg)) {
-            return PSA_ERROR_INVALID_ARGUMENT;
-        }
+    psa_algorithm_t hash_alg = 0;
+    if (PSA_ALG_IS_SIGN_HASH(alg)) {
+        hash_alg = PSA_ALG_SIGN_GET_HASH(alg);
+    }
+
+    /* Now hash_alg==0 if alg by itself doesn't need a hash.
+     * This is good enough for sign-hash, but a guaranteed failure for
+     * sign-message which needs to hash first for all algorithms
+     * supported at the moment. */
+
+    if (hash_alg == 0 && input_is_message) {
+        return PSA_ERROR_INVALID_ARGUMENT;
+    }
+    if (hash_alg == PSA_ALG_ANY_HASH) {
+        return PSA_ERROR_INVALID_ARGUMENT;
+    }
+    /* Give up immediately if the hash is not supported. This has
+     * several advantages:
+     * - For mechanisms that don't use the hash at all (e.g.
+     *   ECDSA verification, randomized ECDSA signature), without
+     *   this check, the operation would succeed even though it has
+     *   been given an invalid argument. This would not be insecure
+     *   since the hash was not necessary, but it would be weird.
+     * - For mechanisms that do use the hash, we avoid an error
+     *   deep inside the execution. In principle this doesn't matter,
+     *   but there is a little more risk of a bug in error handling
+     *   deep inside than in this preliminary check.
+     * - When calling a driver, the driver might be capable of using
+     *   a hash that the core doesn't support. This could potentially
+     *   result in a buffer overflow if the hash is larger than the
+     *   maximum hash size assumed by the core.
+     * - Returning a consistent error makes it possible to test
+     *   not-supported hashes in a consistent way.
+     */
+    if (hash_alg != 0 && !is_hash_supported(hash_alg)) {
+        return PSA_ERROR_NOT_SUPPORTED;
     }
 
     return PSA_SUCCESS;
@@ -3852,6 +3932,34 @@
         * defined( MBEDTLS_ECP_RESTARTABLE ) */
 }
 
+/* Detect supported interruptible sign/verify mechanisms precisely.
+ * This is not strictly needed: we could accept everything, and let the
+ * code fail later during complete() if the mechanism is unsupported
+ * (e.g. attempting deterministic ECDSA when only the randomized variant
+ * is available). But it's easier for applications and especially for our
+ * test code to detect all not-supported errors during start().
+ *
+ * Note that this function ignores the hash component. The core code
+ * is supposed to check the hash part by calling is_hash_supported().
+ */
+static inline int can_do_interruptible_sign_verify(psa_algorithm_t alg)
+{
+#if defined(MBEDTLS_ECP_RESTARTABLE)
+#if defined(MBEDTLS_PSA_BUILTIN_ALG_DETERMINISTIC_ECDSA)
+    if (PSA_ALG_IS_DETERMINISTIC_ECDSA(alg)) {
+        return 1;
+    }
+#endif
+#if defined(MBEDTLS_PSA_BUILTIN_ALG_ECDSA)
+    if (PSA_ALG_IS_RANDOMIZED_ECDSA(alg)) {
+        return 1;
+    }
+#endif
+#endif /* defined(MBEDTLS_ECP_RESTARTABLE) */
+    (void) alg;
+    return 0;
+}
+
 psa_status_t mbedtls_psa_sign_hash_start(
     mbedtls_psa_sign_hash_interruptible_operation_t *operation,
     const psa_key_attributes_t *attributes, const uint8_t *key_buffer,
@@ -3861,11 +3969,15 @@
     psa_status_t status = PSA_ERROR_CORRUPTION_DETECTED;
     size_t required_hash_length;
 
-    if (!PSA_KEY_TYPE_IS_ECC(attributes->type)) {
+    if (!PSA_KEY_TYPE_IS_ECC_KEY_PAIR(attributes->type)) {
         return PSA_ERROR_NOT_SUPPORTED;
     }
+    psa_ecc_family_t curve = PSA_KEY_TYPE_ECC_GET_FAMILY(attributes->type);
+    if (!PSA_ECC_FAMILY_IS_WEIERSTRASS(curve)) {
+        return PSA_ERROR_INVALID_ARGUMENT;
+    }
 
-    if (!PSA_ALG_IS_ECDSA(alg)) {
+    if (!can_do_interruptible_sign_verify(alg)) {
         return PSA_ERROR_NOT_SUPPORTED;
     }
 
@@ -4080,8 +4192,12 @@
     if (!PSA_KEY_TYPE_IS_ECC(attributes->type)) {
         return PSA_ERROR_NOT_SUPPORTED;
     }
+    psa_ecc_family_t curve = PSA_KEY_TYPE_ECC_GET_FAMILY(attributes->type);
+    if (!PSA_ECC_FAMILY_IS_WEIERSTRASS(curve)) {
+        return PSA_ERROR_INVALID_ARGUMENT;
+    }
 
-    if (!PSA_ALG_IS_ECDSA(alg)) {
+    if (!can_do_interruptible_sign_verify(alg)) {
         return PSA_ERROR_NOT_SUPPORTED;
     }
 
@@ -6200,7 +6316,7 @@
     int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
     psa_status_t status = PSA_ERROR_CORRUPTION_DETECTED;
     size_t m;
-    size_t m_bytes;
+    size_t m_bytes = 0;
 
     mbedtls_mpi_init(&k);
     mbedtls_mpi_init(&diff_N_2);
@@ -6273,7 +6389,7 @@
         status = mbedtls_to_psa_error(ret);
     }
     if (status != PSA_SUCCESS) {
-        mbedtls_free(*data);
+        mbedtls_zeroize_and_free(*data, m_bytes);
         *data = NULL;
     }
     mbedtls_mpi_free(&k);
@@ -6448,7 +6564,7 @@
     }
 
 exit:
-    mbedtls_free(data);
+    mbedtls_zeroize_and_free(data, bytes);
     return status;
 }
 
@@ -7372,6 +7488,12 @@
     psa_status_t status;
     psa_algorithm_t kdf_alg = psa_key_derivation_get_kdf_alg(operation);
 
+    if (kdf_alg == PSA_ALG_NONE) {
+        /* This is a blank or aborted operation. */
+        status = PSA_ERROR_BAD_STATE;
+        goto exit;
+    }
+
     status = psa_key_derivation_check_input_type(step, key_type);
     if (status != PSA_SUCCESS) {
         goto exit;
@@ -7430,6 +7552,12 @@
     psa_status_t status;
     psa_algorithm_t kdf_alg = psa_key_derivation_get_kdf_alg(operation);
 
+    if (kdf_alg == PSA_ALG_NONE) {
+        /* This is a blank or aborted operation. */
+        status = PSA_ERROR_BAD_STATE;
+        goto exit;
+    }
+
 #if defined(PSA_HAVE_SOFT_PBKDF2)
     if (PSA_ALG_IS_PBKDF2(kdf_alg)) {
         status = psa_pbkdf2_set_input_cost(
@@ -7443,6 +7571,7 @@
         status = PSA_ERROR_INVALID_ARGUMENT;
     }
 
+exit:
     if (status != PSA_SUCCESS) {
         psa_key_derivation_abort(operation);
     }
@@ -9184,7 +9313,7 @@
     return PSA_SUCCESS;
 
 error:
-    mbedtls_free(local_input->buffer);
+    mbedtls_zeroize_and_free(local_input->buffer, local_input->length);
     local_input->buffer = NULL;
     local_input->length = 0;
     return status;
@@ -9192,7 +9321,7 @@
 
 void psa_crypto_local_input_free(psa_crypto_local_input_t *local_input)
 {
-    mbedtls_free(local_input->buffer);
+    mbedtls_zeroize_and_free(local_input->buffer, local_input->length);
     local_input->buffer = NULL;
     local_input->length = 0;
 }
@@ -9236,7 +9365,7 @@
         return status;
     }
 
-    mbedtls_free(local_output->buffer);
+    mbedtls_zeroize_and_free(local_output->buffer, local_output->length);
     local_output->buffer = NULL;
     local_output->length = 0;
 
diff --git a/library/psa_crypto_cipher.c b/library/psa_crypto_cipher.c
index 3216c94..efc5813 100644
--- a/library/psa_crypto_cipher.c
+++ b/library/psa_crypto_cipher.c
@@ -71,9 +71,6 @@
 #if !defined(PSA_WANT_ALG_OFB)
     MBEDTLS_ASSUME(alg != PSA_ALG_OFB);
 #endif
-#if !defined(PSA_WANT_ALG_XTS)
-    MBEDTLS_ASSUME(alg != PSA_ALG_XTS);
-#endif
 #if !defined(PSA_WANT_ALG_ECB_NO_PADDING)
     MBEDTLS_ASSUME(alg != PSA_ALG_ECB_NO_PADDING);
 #endif
diff --git a/library/psa_crypto_core.h b/library/psa_crypto_core.h
index df0ee50..c3c0770 100644
--- a/library/psa_crypto_core.h
+++ b/library/psa_crypto_core.h
@@ -25,18 +25,6 @@
 #endif
 
 /**
- * Tell if PSA is ready for this hash.
- *
- * \note            For now, only checks the state of the driver subsystem,
- *                  not the algorithm. Might do more in the future.
- *
- * \param hash_alg  The hash algorithm (ignored for now).
- *
- * \return 1 if the driver subsytem is ready, 0 otherwise.
- */
-int psa_can_do_hash(psa_algorithm_t hash_alg);
-
-/**
  * Tell if PSA is ready for this cipher.
  *
  * \note            For now, only checks the state of the driver subsystem,
diff --git a/library/psa_crypto_slot_management.c b/library/psa_crypto_slot_management.c
index 9850d8c..358c7a2 100644
--- a/library/psa_crypto_slot_management.c
+++ b/library/psa_crypto_slot_management.c
@@ -35,9 +35,9 @@
                       "Empty user key ID range");
 MBEDTLS_STATIC_ASSERT(PSA_KEY_ID_VENDOR_MIN < PSA_KEY_ID_VENDOR_MAX,
                       "Empty vendor key ID range");
-MBEDTLS_STATIC_ASSERT(MBEDTLS_PSA_KEY_ID_BUILTIN_MIN < MBEDTLS_PSA_KEY_ID_BUILTIN_MAX,
+MBEDTLS_STATIC_ASSERT(MBEDTLS_PSA_KEY_ID_BUILTIN_MIN <= MBEDTLS_PSA_KEY_ID_BUILTIN_MAX,
                       "Empty builtin key ID range");
-MBEDTLS_STATIC_ASSERT(PSA_KEY_ID_VOLATILE_MIN < PSA_KEY_ID_VOLATILE_MAX,
+MBEDTLS_STATIC_ASSERT(PSA_KEY_ID_VOLATILE_MIN <= PSA_KEY_ID_VOLATILE_MAX,
                       "Empty volatile key ID range");
 
 MBEDTLS_STATIC_ASSERT(PSA_KEY_ID_USER_MAX < PSA_KEY_ID_VENDOR_MIN ||
diff --git a/library/ssl_client.c b/library/ssl_client.c
index 345e608..0bd00cd 100644
--- a/library/ssl_client.c
+++ b/library/ssl_client.c
@@ -29,19 +29,20 @@
                                   size_t *olen)
 {
     unsigned char *p = buf;
+    const char *hostname = mbedtls_ssl_get_hostname_pointer(ssl);
     size_t hostname_len;
 
     *olen = 0;
 
-    if (ssl->hostname == NULL) {
+    if (hostname == NULL) {
         return 0;
     }
 
     MBEDTLS_SSL_DEBUG_MSG(3,
                           ("client hello, adding server name extension: %s",
-                           ssl->hostname));
+                           hostname));
 
-    hostname_len = strlen(ssl->hostname);
+    hostname_len = strlen(hostname);
 
     MBEDTLS_SSL_CHK_BUF_PTR(p, end, hostname_len + 9);
 
@@ -85,7 +86,7 @@
     MBEDTLS_PUT_UINT16_BE(hostname_len, p, 0);
     p += 2;
 
-    memcpy(p, ssl->hostname, hostname_len);
+    memcpy(p, hostname, hostname_len);
 
     *olen = hostname_len + 9;
 
@@ -881,13 +882,14 @@
 #if defined(MBEDTLS_SSL_PROTO_TLS1_3) && \
     defined(MBEDTLS_SSL_SESSION_TICKETS) && \
     defined(MBEDTLS_SSL_SERVER_NAME_INDICATION)
+    const char *context_hostname = mbedtls_ssl_get_hostname_pointer(ssl);
     if (ssl->tls_version == MBEDTLS_SSL_VERSION_TLS1_3  &&
         ssl->handshake->resume) {
-        int hostname_mismatch = ssl->hostname != NULL ||
+        int hostname_mismatch = context_hostname != NULL ||
                                 session_negotiate->hostname != NULL;
-        if (ssl->hostname != NULL && session_negotiate->hostname != NULL) {
+        if (context_hostname != NULL && session_negotiate->hostname != NULL) {
             hostname_mismatch = strcmp(
-                ssl->hostname, session_negotiate->hostname) != 0;
+                context_hostname, session_negotiate->hostname) != 0;
         }
 
         if (hostname_mismatch) {
@@ -898,7 +900,7 @@
         }
     } else {
         return mbedtls_ssl_session_set_hostname(session_negotiate,
-                                                ssl->hostname);
+                                                context_hostname);
     }
 #endif /* MBEDTLS_SSL_PROTO_TLS1_3 &&
           MBEDTLS_SSL_SESSION_TICKETS &&
diff --git a/library/ssl_misc.h b/library/ssl_misc.h
index 7495ae3..faa1b5e 100644
--- a/library/ssl_misc.h
+++ b/library/ssl_misc.h
@@ -16,6 +16,9 @@
 #include "mbedtls/error.h"
 
 #include "mbedtls/ssl.h"
+#include "mbedtls/debug.h"
+#include "debug_internal.h"
+
 #include "mbedtls/cipher.h"
 
 #if defined(MBEDTLS_USE_PSA_CRYPTO) || defined(MBEDTLS_SSL_PROTO_TLS1_3)
@@ -1163,14 +1166,15 @@
     unsigned char out_cid[MBEDTLS_SSL_CID_OUT_LEN_MAX];
 #endif /* MBEDTLS_SSL_DTLS_CONNECTION_ID */
 
-#if defined(MBEDTLS_SSL_CONTEXT_SERIALIZATION)
+#if defined(MBEDTLS_SSL_KEEP_RANDBYTES)
     /* We need the Hello random bytes in order to re-derive keys from the
-     * Master Secret and other session info,
-     * see ssl_tls12_populate_transform() */
+     * Master Secret and other session info and for the keying material
+     * exporter in TLS 1.2.
+     * See ssl_tls12_populate_transform() */
     unsigned char randbytes[MBEDTLS_SERVER_HELLO_RANDOM_LEN +
                             MBEDTLS_CLIENT_HELLO_RANDOM_LEN];
     /*!< ServerHello.random+ClientHello.random */
-#endif /* MBEDTLS_SSL_CONTEXT_SERIALIZATION */
+#endif /* defined(MBEDTLS_SSL_KEEP_RANDBYTES) */
 };
 
 /*
@@ -1333,12 +1337,30 @@
 MBEDTLS_CHECK_RETURN_CRITICAL
 int mbedtls_ssl_handshake_server_step(mbedtls_ssl_context *ssl);
 void mbedtls_ssl_handshake_wrapup(mbedtls_ssl_context *ssl);
+
+#if defined(MBEDTLS_DEBUG_C)
+/* Declared in "ssl_debug_helpers.h". We can't include this file from
+ * "ssl_misc.h" because it includes "ssl_misc.h" because it needs some
+ * type definitions. TODO: split the type definitions and the helper
+ * functions into different headers.
+ */
+const char *mbedtls_ssl_states_str(mbedtls_ssl_states state);
+#endif
+
 static inline void mbedtls_ssl_handshake_set_state(mbedtls_ssl_context *ssl,
                                                    mbedtls_ssl_states state)
 {
+    MBEDTLS_SSL_DEBUG_MSG(3, ("handshake state: %d (%s) -> %d (%s)",
+                              ssl->state, mbedtls_ssl_states_str(ssl->state),
+                              (int) state, mbedtls_ssl_states_str(state)));
     ssl->state = (int) state;
 }
 
+static inline void mbedtls_ssl_handshake_increment_state(mbedtls_ssl_context *ssl)
+{
+    mbedtls_ssl_handshake_set_state(ssl, ssl->state + 1);
+}
+
 MBEDTLS_CHECK_RETURN_CRITICAL
 int mbedtls_ssl_send_fatal_handshake_failure(mbedtls_ssl_context *ssl);
 
@@ -1830,10 +1852,11 @@
 MBEDTLS_CHECK_RETURN_CRITICAL
 int mbedtls_ssl_check_timer(mbedtls_ssl_context *ssl);
 
-void mbedtls_ssl_reset_in_out_pointers(mbedtls_ssl_context *ssl);
+void mbedtls_ssl_reset_in_pointers(mbedtls_ssl_context *ssl);
+void mbedtls_ssl_update_in_pointers(mbedtls_ssl_context *ssl);
+void mbedtls_ssl_reset_out_pointers(mbedtls_ssl_context *ssl);
 void mbedtls_ssl_update_out_pointers(mbedtls_ssl_context *ssl,
                                      mbedtls_ssl_transform *transform);
-void mbedtls_ssl_update_in_pointers(mbedtls_ssl_context *ssl);
 
 MBEDTLS_CHECK_RETURN_CRITICAL
 int mbedtls_ssl_session_reset_int(mbedtls_ssl_context *ssl, int partial);
@@ -2899,6 +2922,18 @@
     unsigned char *buf, unsigned char *end);
 #endif /* MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_SOME_PSK_ENABLED */
 
+#if defined(MBEDTLS_SSL_SERVER_NAME_INDICATION)
+/** Get the host name from the SSL context.
+ *
+ * \param[in]   ssl     SSL context
+ *
+ * \return The \p hostname pointer from the SSL context.
+ *         \c NULL if mbedtls_ssl_set_hostname() has never been called on
+ *         \p ssl or if it was last called with \p NULL.
+ */
+const char *mbedtls_ssl_get_hostname_pointer(const mbedtls_ssl_context *ssl);
+#endif /* MBEDTLS_SSL_SERVER_NAME_INDICATION */
+
 #if defined(MBEDTLS_SSL_PROTO_TLS1_3) && \
     defined(MBEDTLS_SSL_SESSION_TICKETS) && \
     defined(MBEDTLS_SSL_SERVER_NAME_INDICATION) && \
diff --git a/library/ssl_msg.c b/library/ssl_msg.c
index dcda1d3..9f50c8e 100644
--- a/library/ssl_msg.c
+++ b/library/ssl_msg.c
@@ -25,6 +25,7 @@
 #include "constant_time_internal.h"
 #include "mbedtls/constant_time.h"
 
+#include <limits.h>
 #include <string.h>
 
 #if defined(MBEDTLS_USE_PSA_CRYPTO)
@@ -3220,19 +3221,34 @@
 
 int mbedtls_ssl_prepare_handshake_record(mbedtls_ssl_context *ssl)
 {
-    if (ssl->in_msglen < mbedtls_ssl_hs_hdr_len(ssl)) {
-        MBEDTLS_SSL_DEBUG_MSG(1, ("handshake message too short: %" MBEDTLS_PRINTF_SIZET,
-                                  ssl->in_msglen));
-        return MBEDTLS_ERR_SSL_INVALID_RECORD;
-    }
+    if (ssl->badmac_seen_or_in_hsfraglen == 0) {
+        /* The handshake message must at least include the header.
+         * We may not have the full message yet in case of fragmentation.
+         * To simplify the code, we insist on having the header (and in
+         * particular the handshake message length) in the first
+         * fragment. */
+        if (ssl->in_msglen < mbedtls_ssl_hs_hdr_len(ssl)) {
+            MBEDTLS_SSL_DEBUG_MSG(1, ("handshake message too short: %" MBEDTLS_PRINTF_SIZET,
+                                      ssl->in_msglen));
+            return MBEDTLS_ERR_SSL_INVALID_RECORD;
+        }
 
-    ssl->in_hslen = mbedtls_ssl_hs_hdr_len(ssl) + ssl_get_hs_total_len(ssl);
+        ssl->in_hslen = mbedtls_ssl_hs_hdr_len(ssl) + ssl_get_hs_total_len(ssl);
+    }
 
     MBEDTLS_SSL_DEBUG_MSG(3, ("handshake message: msglen ="
                               " %" MBEDTLS_PRINTF_SIZET ", type = %u, hslen = %"
                               MBEDTLS_PRINTF_SIZET,
                               ssl->in_msglen, ssl->in_msg[0], ssl->in_hslen));
 
+    if (ssl->transform_in != NULL) {
+        MBEDTLS_SSL_DEBUG_MSG(4, ("decrypted handshake message:"
+                                  " iv-buf=%d hdr-buf=%d hdr-buf=%d",
+                                  (int) (ssl->in_iv - ssl->in_buf),
+                                  (int) (ssl->in_hdr - ssl->in_buf),
+                                  (int) (ssl->in_msg - ssl->in_buf)));
+    }
+
 #if defined(MBEDTLS_SSL_PROTO_DTLS)
     if (ssl->conf->transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM) {
         int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
@@ -3292,10 +3308,103 @@
         }
     } else
 #endif /* MBEDTLS_SSL_PROTO_DTLS */
-    /* With TLS we don't handle fragmentation (for now) */
-    if (ssl->in_msglen < ssl->in_hslen) {
-        MBEDTLS_SSL_DEBUG_MSG(1, ("TLS handshake fragmentation not supported"));
-        return MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE;
+    {
+        unsigned char *const reassembled_record_start =
+            ssl->in_buf + MBEDTLS_SSL_SEQUENCE_NUMBER_LEN;
+        unsigned char *const payload_start =
+            reassembled_record_start + mbedtls_ssl_in_hdr_len(ssl);
+        unsigned char *payload_end = payload_start + ssl->badmac_seen_or_in_hsfraglen;
+        /* How many more bytes we want to have a complete handshake message. */
+        const size_t hs_remain = ssl->in_hslen - ssl->badmac_seen_or_in_hsfraglen;
+        /* How many bytes of the current record are part of the first
+         * handshake message. There may be more handshake messages (possibly
+         * incomplete) in the same record; if so, we leave them after the
+         * current record, and ssl_consume_current_message() will take
+         * care of consuming the next handshake message. */
+        const size_t hs_this_fragment_len =
+            ssl->in_msglen > hs_remain ? hs_remain : ssl->in_msglen;
+        (void) hs_this_fragment_len;
+
+        MBEDTLS_SSL_DEBUG_MSG(3,
+                              ("%s handshake fragment: %" MBEDTLS_PRINTF_SIZET
+                               ", %u..%u of %" MBEDTLS_PRINTF_SIZET,
+                               (ssl->badmac_seen_or_in_hsfraglen != 0 ?
+                                "subsequent" :
+                                hs_this_fragment_len == ssl->in_hslen ?
+                                "sole" :
+                                "initial"),
+                               ssl->in_msglen,
+                               ssl->badmac_seen_or_in_hsfraglen,
+                               ssl->badmac_seen_or_in_hsfraglen +
+                               (unsigned) hs_this_fragment_len,
+                               ssl->in_hslen));
+
+        /* Move the received handshake fragment to have the whole message
+         * (at least the part received so far) in a single segment at a
+         * known offset in the input buffer.
+         * - When receiving a non-initial handshake fragment, append it to
+         *   the initial segment.
+         * - Even the initial handshake fragment is moved, if it was
+         *   encrypted with an explicit IV: decryption leaves the payload
+         *   after the explicit IV, but here we move it to start where the
+         *   IV was.
+         */
+#if defined(MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH)
+        size_t const in_buf_len = ssl->in_buf_len;
+#else
+        size_t const in_buf_len = MBEDTLS_SSL_IN_BUFFER_LEN;
+#endif
+        if (payload_end + ssl->in_msglen > ssl->in_buf + in_buf_len) {
+            MBEDTLS_SSL_DEBUG_MSG(1,
+                                  ("Shouldn't happen: no room to move handshake fragment %"
+                                   MBEDTLS_PRINTF_SIZET " from %p to %p (buf=%p len=%"
+                                   MBEDTLS_PRINTF_SIZET ")",
+                                   ssl->in_msglen,
+                                   (void *) ssl->in_msg, (void *) payload_end,
+                                   (void *) ssl->in_buf, in_buf_len));
+            return MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
+        }
+        memmove(payload_end, ssl->in_msg, ssl->in_msglen);
+
+        ssl->badmac_seen_or_in_hsfraglen += (unsigned) ssl->in_msglen;
+        payload_end += ssl->in_msglen;
+
+        if (ssl->badmac_seen_or_in_hsfraglen < ssl->in_hslen) {
+            MBEDTLS_SSL_DEBUG_MSG(3, ("Prepare: waiting for more handshake fragments "
+                                      "%u/%" MBEDTLS_PRINTF_SIZET,
+                                      ssl->badmac_seen_or_in_hsfraglen, ssl->in_hslen));
+            ssl->in_hdr = payload_end;
+            ssl->in_msglen = 0;
+            mbedtls_ssl_update_in_pointers(ssl);
+            return MBEDTLS_ERR_SSL_CONTINUE_PROCESSING;
+        } else {
+            ssl->in_msglen = ssl->badmac_seen_or_in_hsfraglen;
+            ssl->badmac_seen_or_in_hsfraglen = 0;
+            ssl->in_hdr = reassembled_record_start;
+            mbedtls_ssl_update_in_pointers(ssl);
+
+            /* Update the record length in the fully reassembled record */
+            if (ssl->in_msglen > 0xffff) {
+                MBEDTLS_SSL_DEBUG_MSG(1,
+                                      ("Shouldn't happen: in_msglen=%"
+                                       MBEDTLS_PRINTF_SIZET " > 0xffff",
+                                       ssl->in_msglen));
+                return MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
+            }
+            MBEDTLS_PUT_UINT16_BE(ssl->in_msglen, ssl->in_len, 0);
+
+            size_t record_len = mbedtls_ssl_in_hdr_len(ssl) + ssl->in_msglen;
+            (void) record_len;
+            MBEDTLS_SSL_DEBUG_BUF(4, "reassembled record",
+                                  ssl->in_hdr, record_len);
+            if (ssl->in_hslen < ssl->in_msglen) {
+                MBEDTLS_SSL_DEBUG_MSG(3,
+                                      ("More handshake messages in the record: "
+                                       "%" MBEDTLS_PRINTF_SIZET " + %" MBEDTLS_PRINTF_SIZET,
+                                       ssl->in_hslen,
+                                       ssl->in_msglen - ssl->in_hslen));
+            }
+        }
     }
 
     return 0;
@@ -3862,6 +3971,7 @@
     rec->buf_len = rec->data_offset + rec->data_len;
 
     if (rec->data_len == 0) {
+        MBEDTLS_SSL_DEBUG_MSG(1, ("rejecting empty record"));
         return MBEDTLS_ERR_SSL_INVALID_RECORD;
     }
 
@@ -4640,6 +4750,14 @@
             return MBEDTLS_ERR_SSL_INTERNAL_ERROR;
         }
 
+        if (ssl->badmac_seen_or_in_hsfraglen != 0) {
+            /* Not all handshake fragments have arrived, do not consume. */
+            MBEDTLS_SSL_DEBUG_MSG(3, ("Consume: waiting for more handshake fragments "
+                                      "%u/%" MBEDTLS_PRINTF_SIZET,
+                                      ssl->badmac_seen_or_in_hsfraglen, ssl->in_hslen));
+            return 0;
+        }
+
         /*
          * Get next Handshake message in the current record
          */
@@ -4665,6 +4783,7 @@
             ssl->in_msglen -= ssl->in_hslen;
             memmove(ssl->in_msg, ssl->in_msg + ssl->in_hslen,
                     ssl->in_msglen);
+            MBEDTLS_PUT_UINT16_BE(ssl->in_msglen, ssl->in_len, 0);
 
             MBEDTLS_SSL_DEBUG_BUF(4, "remaining content in record",
                                   ssl->in_msg, ssl->in_msglen);
@@ -4967,10 +5086,12 @@
                     return ret;
                 }
 
-                if (ssl->conf->badmac_limit != 0 &&
-                    ++ssl->badmac_seen >= ssl->conf->badmac_limit) {
-                    MBEDTLS_SSL_DEBUG_MSG(1, ("too many records with bad MAC"));
-                    return MBEDTLS_ERR_SSL_INVALID_MAC;
+                if (ssl->conf->badmac_limit != 0) {
+                    ++ssl->badmac_seen_or_in_hsfraglen;
+                    if (ssl->badmac_seen_or_in_hsfraglen >= ssl->conf->badmac_limit) {
+                        MBEDTLS_SSL_DEBUG_MSG(1, ("too many records with bad MAC"));
+                        return MBEDTLS_ERR_SSL_INVALID_MAC;
+                    }
                 }
 
                 /* As above, invalid records cause
@@ -5028,6 +5149,18 @@
 {
     int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
 
+    /* If we're in the middle of a fragmented TLS handshake message,
+     * we don't accept any other message type. For TLS 1.3, the spec forbids
+     * interleaving other message types between handshake fragments. For TLS
+     * 1.2, the spec does not forbid it but we do. */
+    if (ssl->conf->transport == MBEDTLS_SSL_TRANSPORT_STREAM &&
+        ssl->badmac_seen_or_in_hsfraglen != 0 &&
+        ssl->in_msgtype != MBEDTLS_SSL_MSG_HANDSHAKE) {
+        MBEDTLS_SSL_DEBUG_MSG(1, ("non-handshake message in the middle"
+                                  " of a fragmented handshake message"));
+        return MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE;
+    }
+
     /*
      * Handle particular types of records
      */
@@ -5186,7 +5319,7 @@
     ssl->out_msglen  = 1;
     ssl->out_msg[0]  = 1;
 
-    ssl->state++;
+    mbedtls_ssl_handshake_increment_state(ssl);
 
     if ((ret = mbedtls_ssl_write_handshake_msg(ssl)) != 0) {
         MBEDTLS_SSL_DEBUG_RET(1, "mbedtls_ssl_write_handshake_msg", ret);
@@ -5248,7 +5381,7 @@
 
     mbedtls_ssl_update_in_pointers(ssl);
 
-    ssl->state++;
+    mbedtls_ssl_handshake_increment_state(ssl);
 
     MBEDTLS_SSL_DEBUG_MSG(2, ("<= parse change cipher spec"));
 
@@ -5339,7 +5472,7 @@
     } else
 #endif
     {
-        ssl->in_ctr = ssl->in_hdr - MBEDTLS_SSL_SEQUENCE_NUMBER_LEN;
+        ssl->in_ctr = ssl->in_buf;
         ssl->in_len = ssl->in_hdr + 3;
 #if defined(MBEDTLS_SSL_DTLS_CONNECTION_ID)
         ssl->in_cid = ssl->in_len;
@@ -5355,24 +5488,35 @@
  * Setup an SSL context
  */
 
-void mbedtls_ssl_reset_in_out_pointers(mbedtls_ssl_context *ssl)
+void mbedtls_ssl_reset_in_pointers(mbedtls_ssl_context *ssl)
+{
+#if defined(MBEDTLS_SSL_PROTO_DTLS)
+    if (ssl->conf->transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM) {
+        ssl->in_hdr = ssl->in_buf;
+    } else
+#endif  /* MBEDTLS_SSL_PROTO_DTLS */
+    {
+        ssl->in_hdr = ssl->in_buf + MBEDTLS_SSL_SEQUENCE_NUMBER_LEN;
+    }
+
+    /* Derive other internal pointers. */
+    mbedtls_ssl_update_in_pointers(ssl);
+}
+
+void mbedtls_ssl_reset_out_pointers(mbedtls_ssl_context *ssl)
 {
     /* Set the incoming and outgoing record pointers. */
 #if defined(MBEDTLS_SSL_PROTO_DTLS)
     if (ssl->conf->transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM) {
         ssl->out_hdr = ssl->out_buf;
-        ssl->in_hdr  = ssl->in_buf;
     } else
 #endif /* MBEDTLS_SSL_PROTO_DTLS */
     {
         ssl->out_ctr = ssl->out_buf;
-        ssl->out_hdr = ssl->out_buf + 8;
-        ssl->in_hdr  = ssl->in_buf  + 8;
+        ssl->out_hdr = ssl->out_buf + MBEDTLS_SSL_SEQUENCE_NUMBER_LEN;
     }
-
     /* Derive other internal pointers. */
     mbedtls_ssl_update_out_pointers(ssl, NULL /* no transform enabled */);
-    mbedtls_ssl_update_in_pointers(ssl);
 }
 
 /*
diff --git a/library/ssl_tls.c b/library/ssl_tls.c
index c773365..ce14c7d 100644
--- a/library/ssl_tls.c
+++ b/library/ssl_tls.c
@@ -19,6 +19,7 @@
 #include "ssl_client.h"
 #include "ssl_debug_helpers.h"
 #include "ssl_misc.h"
+#include "ssl_tls13_keys.h"
 
 #include "debug_internal.h"
 #include "mbedtls/error.h"
@@ -344,12 +345,13 @@
                                    size_t out_buf_new_len)
 {
     int modified = 0;
-    size_t written_in = 0, iv_offset_in = 0, len_offset_in = 0;
+    size_t written_in = 0, iv_offset_in = 0, len_offset_in = 0, hdr_in = 0;
     size_t written_out = 0, iv_offset_out = 0, len_offset_out = 0;
     if (ssl->in_buf != NULL) {
         written_in = ssl->in_msg - ssl->in_buf;
         iv_offset_in = ssl->in_iv - ssl->in_buf;
         len_offset_in = ssl->in_len - ssl->in_buf;
+        hdr_in = ssl->in_hdr - ssl->in_buf;
         if (downsizing ?
             ssl->in_buf_len > in_buf_new_len && ssl->in_left < in_buf_new_len :
             ssl->in_buf_len < in_buf_new_len) {
@@ -381,7 +383,10 @@
     }
     if (modified) {
         /* Update pointers here to avoid doing it twice. */
-        mbedtls_ssl_reset_in_out_pointers(ssl);
+        ssl->in_hdr = ssl->in_buf + hdr_in;
+        mbedtls_ssl_update_in_pointers(ssl);
+        mbedtls_ssl_reset_out_pointers(ssl);
+
         /* Fields below might not be properly updated with record
          * splitting or with CID, so they are manually updated here. */
         ssl->out_msg = ssl->out_buf + written_out;
@@ -1409,7 +1414,8 @@
         goto error;
     }
 
-    mbedtls_ssl_reset_in_out_pointers(ssl);
+    mbedtls_ssl_reset_in_pointers(ssl);
+    mbedtls_ssl_reset_out_pointers(ssl);
 
 #if defined(MBEDTLS_SSL_DTLS_SRTP)
     memset(&ssl->dtls_srtp_info, 0, sizeof(ssl->dtls_srtp_info));
@@ -1474,7 +1480,8 @@
     /* Cancel any possibly running timer */
     mbedtls_ssl_set_timer(ssl, 0);
 
-    mbedtls_ssl_reset_in_out_pointers(ssl);
+    mbedtls_ssl_reset_in_pointers(ssl);
+    mbedtls_ssl_reset_out_pointers(ssl);
 
     /* Reset incoming message parsing */
     ssl->in_offt    = NULL;
@@ -1485,6 +1492,12 @@
     ssl->keep_current_message = 0;
     ssl->transform_in  = NULL;
 
+    /* TLS: reset in_hsfraglen, which is part of message parsing.
+     * DTLS: on a client reconnect, don't reset badmac_seen. */
+    if (!partial) {
+        ssl->badmac_seen_or_in_hsfraglen = 0;
+    }
+
 #if defined(MBEDTLS_SSL_PROTO_DTLS)
     ssl->next_record_offset = 0;
     ssl->in_epoch = 0;
@@ -1542,7 +1555,7 @@
 {
     int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
 
-    ssl->state = MBEDTLS_SSL_HELLO_REQUEST;
+    mbedtls_ssl_handshake_set_state(ssl, MBEDTLS_SSL_HELLO_REQUEST);
     ssl->tls_version = ssl->conf->max_tls_version;
 
     mbedtls_ssl_session_reset_msg_layer(ssl, partial);
@@ -2757,6 +2770,51 @@
 }
 
 #if defined(MBEDTLS_X509_CRT_PARSE_C)
+
+/* A magic value for `ssl->hostname` indicating that
+ * mbedtls_ssl_set_hostname() has been called with `NULL`.
+ * If mbedtls_ssl_set_hostname() has never been called on `ssl`, then
+ * `ssl->hostname == NULL`. */
+static const char *const ssl_hostname_skip_cn_verification = "";
+
+#if defined(MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED)
+/** Whether mbedtls_ssl_set_hostname() has been called.
+ *
+ * \param[in]   ssl     SSL context
+ *
+ * \return \c 1 if mbedtls_ssl_set_hostname() has been called on \p ssl
+ *         (including `mbedtls_ssl_set_hostname(ssl, NULL)`),
+ *         otherwise \c 0.
+ */
+static int mbedtls_ssl_has_set_hostname_been_called(
+    const mbedtls_ssl_context *ssl)
+{
+    return ssl->hostname != NULL;
+}
+#endif
+
+/* Micro-optimization: don't export this function if it isn't needed outside
+ * of this source file. */
+#if !defined(MBEDTLS_SSL_SERVER_NAME_INDICATION)
+static
+#endif
+const char *mbedtls_ssl_get_hostname_pointer(const mbedtls_ssl_context *ssl)
+{
+    if (ssl->hostname == ssl_hostname_skip_cn_verification) {
+        return NULL;
+    }
+    return ssl->hostname;
+}
+
+static void mbedtls_ssl_free_hostname(mbedtls_ssl_context *ssl)
+{
+    if (ssl->hostname != NULL &&
+        ssl->hostname != ssl_hostname_skip_cn_verification) {
+        mbedtls_zeroize_and_free(ssl->hostname, strlen(ssl->hostname));
+    }
+    ssl->hostname = NULL;
+}
+
 int mbedtls_ssl_set_hostname(mbedtls_ssl_context *ssl, const char *hostname)
 {
     /* Initialize to suppress unnecessary compiler warning */
@@ -2774,18 +2832,21 @@
 
     /* Now it's clear that we will overwrite the old hostname,
      * so we can free it safely */
-
-    if (ssl->hostname != NULL) {
-        mbedtls_zeroize_and_free(ssl->hostname, strlen(ssl->hostname));
-    }
-
-    /* Passing NULL as hostname shall clear the old one */
+    mbedtls_ssl_free_hostname(ssl);
 
     if (hostname == NULL) {
-        ssl->hostname = NULL;
+        /* Passing NULL as hostname clears the old one, but leaves a
+         * special marker to indicate that mbedtls_ssl_set_hostname()
+         * has been called. */
+        /* ssl->hostname should be const, but isn't. We won't actually
+         * write to the buffer, so it's ok to cast away the const. */
+        ssl->hostname = (char *) ssl_hostname_skip_cn_verification;
     } else {
         ssl->hostname = mbedtls_calloc(1, hostname_len + 1);
         if (ssl->hostname == NULL) {
+            /* mbedtls_ssl_set_hostname() has been called, but unsuccessfully.
+             * Leave ssl->hostname in the same state as if the function had
+             * not been called, i.e. a null pointer. */
             return MBEDTLS_ERR_SSL_ALLOC_FAILED;
         }
 
@@ -4527,7 +4588,7 @@
 
         switch (ssl->state) {
             case MBEDTLS_SSL_HELLO_REQUEST:
-                ssl->state = MBEDTLS_SSL_CLIENT_HELLO;
+                mbedtls_ssl_handshake_set_state(ssl, MBEDTLS_SSL_CLIENT_HELLO);
                 ret = 0;
                 break;
 
@@ -4678,7 +4739,7 @@
     }
 #endif
 
-    ssl->state = MBEDTLS_SSL_HELLO_REQUEST;
+    mbedtls_ssl_handshake_set_state(ssl, MBEDTLS_SSL_HELLO_REQUEST);
     ssl->renego_status = MBEDTLS_SSL_RENEGOTIATION_IN_PROGRESS;
 
     if ((ret = mbedtls_ssl_handshake(ssl)) != 0) {
@@ -5014,7 +5075,7 @@
  *  uint8 in_cid<0..2^8-1>      // Connection ID: expected incoming value
  *  uint8 out_cid<0..2^8-1>     // Connection ID: outgoing value to use
  *  // fields from ssl_context
- *  uint32 badmac_seen;         // DTLS: number of records with failing MAC
+ *  uint32 badmac_seen_or_in_hsfraglen;         // DTLS: number of records with failing MAC
  *  uint64 in_window_top;       // DTLS: last validated record seq_num
  *  uint64 in_window;           // DTLS: bitmask for replay protection
  *  uint8 disable_datagram_packing; // DTLS: only one record per datagram
@@ -5156,7 +5217,7 @@
      */
     used += 4;
     if (used <= buf_len) {
-        MBEDTLS_PUT_UINT32_BE(ssl->badmac_seen, p, 0);
+        MBEDTLS_PUT_UINT32_BE(ssl->badmac_seen_or_in_hsfraglen, p, 0);
         p += 4;
     }
 
@@ -5386,7 +5447,7 @@
         return MBEDTLS_ERR_SSL_BAD_INPUT_DATA;
     }
 
-    ssl->badmac_seen = MBEDTLS_GET_UINT32_BE(p, 0);
+    ssl->badmac_seen_or_in_hsfraglen = MBEDTLS_GET_UINT32_BE(p, 0);
     p += 4;
 
 #if defined(MBEDTLS_SSL_DTLS_ANTI_REPLAY)
@@ -5461,7 +5522,7 @@
      * Most of them already set to the correct value by mbedtls_ssl_init() and
      * mbedtls_ssl_reset(), so we only need to set the remaining ones.
      */
-    ssl->state = MBEDTLS_SSL_HANDSHAKE_OVER;
+    mbedtls_ssl_handshake_set_state(ssl, MBEDTLS_SSL_HANDSHAKE_OVER);
     ssl->tls_version = MBEDTLS_SSL_VERSION_TLS1_2;
 
     /* Adjust pointers for header fields of outgoing records to
@@ -5571,9 +5632,7 @@
     }
 
 #if defined(MBEDTLS_X509_CRT_PARSE_C)
-    if (ssl->hostname != NULL) {
-        mbedtls_zeroize_and_free(ssl->hostname, strlen(ssl->hostname));
-    }
+    mbedtls_ssl_free_hostname(ssl);
 #endif
 
 #if defined(MBEDTLS_SSL_DTLS_HELLO_VERIFY) && defined(MBEDTLS_SSL_SRV_C)
@@ -6710,7 +6769,7 @@
 MBEDTLS_CHECK_RETURN_CRITICAL
 static int tls_prf_generic(mbedtls_md_type_t md_type,
                            const unsigned char *secret, size_t slen,
-                           const char *label,
+                           const char *label, size_t label_len,
                            const unsigned char *random, size_t rlen,
                            unsigned char *dstbuf, size_t dlen)
 {
@@ -6750,7 +6809,7 @@
                                       NULL, 0,
                                       random, rlen,
                                       (unsigned char const *) label,
-                                      (size_t) strlen(label),
+                                      label_len,
                                       NULL, 0,
                                       dlen);
     if (status != PSA_SUCCESS) {
@@ -6790,7 +6849,7 @@
 MBEDTLS_CHECK_RETURN_CRITICAL
 static int tls_prf_generic(mbedtls_md_type_t md_type,
                            const unsigned char *secret, size_t slen,
-                           const char *label,
+                           const char *label, size_t label_len,
                            const unsigned char *random, size_t rlen,
                            unsigned char *dstbuf, size_t dlen)
 {
@@ -6811,14 +6870,14 @@
 
     md_len = mbedtls_md_get_size(md_info);
 
-    tmp_len = md_len + strlen(label) + rlen;
+    tmp_len = md_len + label_len + rlen;
     tmp = mbedtls_calloc(1, tmp_len);
     if (tmp == NULL) {
         ret = MBEDTLS_ERR_SSL_ALLOC_FAILED;
         goto exit;
     }
 
-    nb = strlen(label);
+    nb = label_len;
     memcpy(tmp + md_len, label, nb);
     memcpy(tmp + md_len + nb, random, rlen);
     nb += rlen;
@@ -6901,7 +6960,7 @@
                           unsigned char *dstbuf, size_t dlen)
 {
     return tls_prf_generic(MBEDTLS_MD_SHA256, secret, slen,
-                           label, random, rlen, dstbuf, dlen);
+                           label, strlen(label), random, rlen, dstbuf, dlen);
 }
 #endif /* MBEDTLS_MD_CAN_SHA256*/
 
@@ -6913,7 +6972,7 @@
                           unsigned char *dstbuf, size_t dlen)
 {
     return tls_prf_generic(MBEDTLS_MD_SHA384, secret, slen,
-                           label, random, rlen, dstbuf, dlen);
+                           label, strlen(label), random, rlen, dstbuf, dlen);
 }
 #endif /* MBEDTLS_MD_CAN_SHA384*/
 
@@ -7518,7 +7577,7 @@
 
     if (!mbedtls_ssl_ciphersuite_uses_srv_cert(ciphersuite_info)) {
         MBEDTLS_SSL_DEBUG_MSG(2, ("<= skip write certificate"));
-        ssl->state++;
+        mbedtls_ssl_handshake_increment_state(ssl);
         return 0;
     }
 
@@ -7535,7 +7594,7 @@
 
     if (!mbedtls_ssl_ciphersuite_uses_srv_cert(ciphersuite_info)) {
         MBEDTLS_SSL_DEBUG_MSG(2, ("<= skip parse certificate"));
-        ssl->state++;
+        mbedtls_ssl_handshake_increment_state(ssl);
         return 0;
     }
 
@@ -7558,7 +7617,7 @@
 
     if (!mbedtls_ssl_ciphersuite_uses_srv_cert(ciphersuite_info)) {
         MBEDTLS_SSL_DEBUG_MSG(2, ("<= skip write certificate"));
-        ssl->state++;
+        mbedtls_ssl_handshake_increment_state(ssl);
         return 0;
     }
 
@@ -7566,7 +7625,7 @@
     if (ssl->conf->endpoint == MBEDTLS_SSL_IS_CLIENT) {
         if (ssl->handshake->client_auth == 0) {
             MBEDTLS_SSL_DEBUG_MSG(2, ("<= skip write certificate"));
-            ssl->state++;
+            mbedtls_ssl_handshake_increment_state(ssl);
             return 0;
         }
     }
@@ -7620,7 +7679,7 @@
     ssl->out_msgtype = MBEDTLS_SSL_MSG_HANDSHAKE;
     ssl->out_msg[0]  = MBEDTLS_SSL_HS_CERTIFICATE;
 
-    ssl->state++;
+    mbedtls_ssl_handshake_increment_state(ssl);
 
     if ((ret = mbedtls_ssl_write_handshake_msg(ssl)) != 0) {
         MBEDTLS_SSL_DEBUG_RET(1, "mbedtls_ssl_write_handshake_msg", ret);
@@ -8078,7 +8137,7 @@
 exit:
 
     if (ret == 0) {
-        ssl->state++;
+        mbedtls_ssl_handshake_increment_state(ssl);
     }
 
 #if defined(MBEDTLS_SSL_ECP_RESTARTABLE_ENABLED)
@@ -8294,7 +8353,7 @@
 #endif
     mbedtls_ssl_handshake_wrapup_free_hs_transform(ssl);
 
-    ssl->state = MBEDTLS_SSL_HANDSHAKE_OVER;
+    mbedtls_ssl_handshake_set_state(ssl, MBEDTLS_SSL_HANDSHAKE_OVER);
 
     MBEDTLS_SSL_DEBUG_MSG(3, ("<= handshake wrapup"));
 }
@@ -8311,6 +8370,7 @@
     ret = ssl->handshake->calc_finished(ssl, ssl->out_msg + 4, ssl->conf->endpoint);
     if (ret != 0) {
         MBEDTLS_SSL_DEBUG_RET(1, "calc_finished", ret);
+        return ret;
     }
 
     /*
@@ -8337,16 +8397,16 @@
     if (ssl->handshake->resume != 0) {
 #if defined(MBEDTLS_SSL_CLI_C)
         if (ssl->conf->endpoint == MBEDTLS_SSL_IS_CLIENT) {
-            ssl->state = MBEDTLS_SSL_HANDSHAKE_WRAPUP;
+            mbedtls_ssl_handshake_set_state(ssl, MBEDTLS_SSL_HANDSHAKE_WRAPUP);
         }
 #endif
 #if defined(MBEDTLS_SSL_SRV_C)
         if (ssl->conf->endpoint == MBEDTLS_SSL_IS_SERVER) {
-            ssl->state = MBEDTLS_SSL_CLIENT_CHANGE_CIPHER_SPEC;
+            mbedtls_ssl_handshake_set_state(ssl, MBEDTLS_SSL_CLIENT_CHANGE_CIPHER_SPEC);
         }
 #endif
     } else {
-        ssl->state++;
+        mbedtls_ssl_handshake_increment_state(ssl);
     }
 
     /*
@@ -8424,6 +8484,7 @@
     ret = ssl->handshake->calc_finished(ssl, buf, ssl->conf->endpoint ^ 1);
     if (ret != 0) {
         MBEDTLS_SSL_DEBUG_RET(1, "calc_finished", ret);
+        return ret;
     }
 
     if ((ret = mbedtls_ssl_read_record(ssl, 1)) != 0) {
@@ -8471,16 +8532,16 @@
     if (ssl->handshake->resume != 0) {
 #if defined(MBEDTLS_SSL_CLI_C)
         if (ssl->conf->endpoint == MBEDTLS_SSL_IS_CLIENT) {
-            ssl->state = MBEDTLS_SSL_CLIENT_CHANGE_CIPHER_SPEC;
+            mbedtls_ssl_handshake_set_state(ssl, MBEDTLS_SSL_CLIENT_CHANGE_CIPHER_SPEC);
         }
 #endif
 #if defined(MBEDTLS_SSL_SRV_C)
         if (ssl->conf->endpoint == MBEDTLS_SSL_IS_SERVER) {
-            ssl->state = MBEDTLS_SSL_HANDSHAKE_WRAPUP;
+            mbedtls_ssl_handshake_set_state(ssl, MBEDTLS_SSL_HANDSHAKE_WRAPUP);
         }
 #endif
     } else {
-        ssl->state++;
+        mbedtls_ssl_handshake_increment_state(ssl);
     }
 
 #if defined(MBEDTLS_SSL_PROTO_DTLS)
@@ -8608,7 +8669,7 @@
 #endif /* MBEDTLS_SSL_SOME_SUITES_USE_CBC_ETM */
     transform->tls_version = tls_version;
 
-#if defined(MBEDTLS_SSL_CONTEXT_SERIALIZATION)
+#if defined(MBEDTLS_SSL_KEEP_RANDBYTES)
     memcpy(transform->randbytes, randbytes, sizeof(transform->randbytes));
 #endif
 
@@ -9784,6 +9845,27 @@
     return ret;
 }
 
+static int get_hostname_for_verification(mbedtls_ssl_context *ssl,
+                                         const char **hostname)
+{
+    if (!mbedtls_ssl_has_set_hostname_been_called(ssl)) {
+        MBEDTLS_SSL_DEBUG_MSG(1, ("Certificate verification without having set hostname"));
+#if !defined(MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME)
+        if (mbedtls_ssl_conf_get_endpoint(ssl->conf) == MBEDTLS_SSL_IS_CLIENT &&
+            ssl->conf->authmode == MBEDTLS_SSL_VERIFY_REQUIRED) {
+            return MBEDTLS_ERR_SSL_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME;
+        }
+#endif
+    }
+
+    *hostname = mbedtls_ssl_get_hostname_pointer(ssl);
+    if (*hostname == NULL) {
+        MBEDTLS_SSL_DEBUG_MSG(2, ("Certificate verification without CN verification"));
+    }
+
+    return 0;
+}
+
 int mbedtls_ssl_verify_certificate(mbedtls_ssl_context *ssl,
                                    int authmode,
                                    mbedtls_x509_crt *chain,
@@ -9809,7 +9891,13 @@
         p_vrfy = ssl->conf->p_vrfy;
     }
 
-    int ret = 0;
+    const char *hostname = "";
+    int ret = get_hostname_for_verification(ssl, &hostname);
+    if (ret != 0) {
+        MBEDTLS_SSL_DEBUG_RET(1, "get_hostname_for_verification", ret);
+        return ret;
+    }
+
     int have_ca_chain_or_callback = 0;
 #if defined(MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK)
     if (ssl->conf->f_ca_cb != NULL) {
@@ -9822,7 +9910,7 @@
             ssl->conf->f_ca_cb,
             ssl->conf->p_ca_cb,
             ssl->conf->cert_profile,
-            ssl->hostname,
+            hostname,
             &ssl->session_negotiate->verify_result,
             f_vrfy, p_vrfy);
     } else
@@ -9849,7 +9937,7 @@
             chain,
             ca_chain, ca_crl,
             ssl->conf->cert_profile,
-            ssl->hostname,
+            hostname,
             &ssl->session_negotiate->verify_result,
             f_vrfy, p_vrfy, rs_ctx);
     }
@@ -9966,4 +10054,134 @@
 }
 #endif /* MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED */
 
+#if defined(MBEDTLS_SSL_KEYING_MATERIAL_EXPORT)
+
+#if defined(MBEDTLS_SSL_PROTO_TLS1_2)
+static int mbedtls_ssl_tls12_export_keying_material(const mbedtls_ssl_context *ssl,
+                                                    const mbedtls_md_type_t hash_alg,
+                                                    uint8_t *out,
+                                                    const size_t key_len,
+                                                    const char *label,
+                                                    const size_t label_len,
+                                                    const unsigned char *context,
+                                                    const size_t context_len,
+                                                    const int use_context)
+{
+    int ret = 0;
+    unsigned char *prf_input = NULL;
+
+    /* The input to the PRF is client_random, then server_random.
+     * If a context is provided, this is then followed by the context length
+     * as a 16-bit big-endian integer, and then the context itself. */
+    const size_t randbytes_len = MBEDTLS_CLIENT_HELLO_RANDOM_LEN + MBEDTLS_SERVER_HELLO_RANDOM_LEN;
+    size_t prf_input_len = randbytes_len;
+    if (use_context) {
+        if (context_len > UINT16_MAX) {
+            return MBEDTLS_ERR_SSL_BAD_INPUT_DATA;
+        }
+
+        /* This does not overflow a 32-bit size_t because the current value of
+         * prf_input_len is 64 (length of client_random + server_random) and
+         * context_len fits into two bytes (checked above). */
+        prf_input_len += sizeof(uint16_t) + context_len;
+    }
+
+    prf_input = mbedtls_calloc(prf_input_len, sizeof(unsigned char));
+    if (prf_input == NULL) {
+        return MBEDTLS_ERR_SSL_ALLOC_FAILED;
+    }
+
+    memcpy(prf_input,
+           ssl->transform->randbytes + MBEDTLS_SERVER_HELLO_RANDOM_LEN,
+           MBEDTLS_CLIENT_HELLO_RANDOM_LEN);
+    memcpy(prf_input + MBEDTLS_CLIENT_HELLO_RANDOM_LEN,
+           ssl->transform->randbytes,
+           MBEDTLS_SERVER_HELLO_RANDOM_LEN);
+    if (use_context) {
+        MBEDTLS_PUT_UINT16_BE(context_len, prf_input, randbytes_len);
+        memcpy(prf_input + randbytes_len + sizeof(uint16_t), context, context_len);
+    }
+    ret = tls_prf_generic(hash_alg, ssl->session->master, sizeof(ssl->session->master),
+                          label, label_len,
+                          prf_input, prf_input_len,
+                          out, key_len);
+    mbedtls_free(prf_input);
+    return ret;
+}
+#endif /* defined(MBEDTLS_SSL_PROTO_TLS1_2) */
+
+#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
+static int mbedtls_ssl_tls13_export_keying_material(mbedtls_ssl_context *ssl,
+                                                    const mbedtls_md_type_t hash_alg,
+                                                    uint8_t *out,
+                                                    const size_t key_len,
+                                                    const char *label,
+                                                    const size_t label_len,
+                                                    const unsigned char *context,
+                                                    const size_t context_len)
+{
+    const psa_algorithm_t psa_hash_alg = mbedtls_md_psa_alg_from_type(hash_alg);
+    const size_t hash_len = PSA_HASH_LENGTH(hash_alg);
+    const unsigned char *secret = ssl->session->app_secrets.exporter_master_secret;
+
+    /* The length of the label must be at most 249 bytes to fit into the HkdfLabel
+     * struct as defined in RFC 8446, Section 7.1.
+     *
+     * The length of the context is unlimited even though the context field in the
+     * struct can only hold up to 255 bytes. This is because we place a *hash* of
+     * the context in the field. */
+    if (label_len > 249) {
+        return MBEDTLS_ERR_SSL_BAD_INPUT_DATA;
+    }
+
+    return mbedtls_ssl_tls13_exporter(psa_hash_alg, secret, hash_len,
+                                      (const unsigned char *) label, label_len,
+                                      context, context_len, out, key_len);
+}
+#endif /* defined(MBEDTLS_SSL_PROTO_TLS1_3) */
+
+int mbedtls_ssl_export_keying_material(mbedtls_ssl_context *ssl,
+                                       uint8_t *out, const size_t key_len,
+                                       const char *label, const size_t label_len,
+                                       const unsigned char *context, const size_t context_len,
+                                       const int use_context)
+{
+    if (!mbedtls_ssl_is_handshake_over(ssl)) {
+        /* TODO: Change this to a more appropriate error code when one is available. */
+        return MBEDTLS_ERR_SSL_BAD_INPUT_DATA;
+    }
+
+    if (key_len > MBEDTLS_SSL_EXPORT_MAX_KEY_LEN) {
+        return MBEDTLS_ERR_SSL_BAD_INPUT_DATA;
+    }
+
+    int ciphersuite_id = mbedtls_ssl_get_ciphersuite_id_from_ssl(ssl);
+    const mbedtls_ssl_ciphersuite_t *ciphersuite = mbedtls_ssl_ciphersuite_from_id(ciphersuite_id);
+    const mbedtls_md_type_t hash_alg = ciphersuite->mac;
+
+    switch (mbedtls_ssl_get_version_number(ssl)) {
+#if defined(MBEDTLS_SSL_PROTO_TLS1_2)
+        case MBEDTLS_SSL_VERSION_TLS1_2:
+            return mbedtls_ssl_tls12_export_keying_material(ssl, hash_alg, out, key_len,
+                                                            label, label_len,
+                                                            context, context_len, use_context);
+#endif
+#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
+        case MBEDTLS_SSL_VERSION_TLS1_3:
+            return mbedtls_ssl_tls13_export_keying_material(ssl,
+                                                            hash_alg,
+                                                            out,
+                                                            key_len,
+                                                            label,
+                                                            label_len,
+                                                            use_context ? context : NULL,
+                                                            use_context ? context_len : 0);
+#endif
+        default:
+            return MBEDTLS_ERR_SSL_BAD_PROTOCOL_VERSION;
+    }
+}
+
+#endif /* defined(MBEDTLS_SSL_KEYING_MATERIAL_EXPORT) */
+
 #endif /* MBEDTLS_SSL_TLS_C */
diff --git a/library/ssl_tls12_client.c b/library/ssl_tls12_client.c
index 75783d0..791b84e 100644
--- a/library/ssl_tls12_client.c
+++ b/library/ssl_tls12_client.c
@@ -1163,7 +1163,7 @@
     ssl->handshake->cookie_len = cookie_len;
 
     /* Start over at ClientHello */
-    ssl->state = MBEDTLS_SSL_CLIENT_HELLO;
+    mbedtls_ssl_handshake_set_state(ssl, MBEDTLS_SSL_CLIENT_HELLO);
     ret = mbedtls_ssl_reset_checksum(ssl);
     if (0 != ret) {
         MBEDTLS_SSL_DEBUG_RET(1, ("mbedtls_ssl_reset_checksum"), ret);
@@ -1372,7 +1372,7 @@
         ssl->session_negotiate->ciphersuite != i ||
         ssl->session_negotiate->id_len != n ||
         memcmp(ssl->session_negotiate->id, buf + 35, n) != 0) {
-        ssl->state++;
+        mbedtls_ssl_handshake_increment_state(ssl);
         ssl->handshake->resume = 0;
 #if defined(MBEDTLS_HAVE_TIME)
         ssl->session_negotiate->start = mbedtls_time(NULL);
@@ -1381,7 +1381,7 @@
         ssl->session_negotiate->id_len = n;
         memcpy(ssl->session_negotiate->id, buf + 35, n);
     } else {
-        ssl->state = MBEDTLS_SSL_SERVER_CHANGE_CIPHER_SPEC;
+        mbedtls_ssl_handshake_set_state(ssl, MBEDTLS_SSL_SERVER_CHANGE_CIPHER_SPEC);
     }
 
     MBEDTLS_SSL_DEBUG_MSG(3, ("%s session has been resumed",
@@ -2091,7 +2091,7 @@
 #if defined(MBEDTLS_KEY_EXCHANGE_RSA_ENABLED)
     if (ciphersuite_info->key_exchange == MBEDTLS_KEY_EXCHANGE_RSA) {
         MBEDTLS_SSL_DEBUG_MSG(2, ("<= skip parse server key exchange"));
-        ssl->state++;
+        mbedtls_ssl_handshake_increment_state(ssl);
         return 0;
     }
     ((void) p);
@@ -2112,7 +2112,7 @@
         }
 
         MBEDTLS_SSL_DEBUG_MSG(2, ("<= skip parse server key exchange"));
-        ssl->state++;
+        mbedtls_ssl_handshake_increment_state(ssl);
         return 0;
     }
     ((void) p);
@@ -2454,7 +2454,7 @@
 #endif /* MBEDTLS_KEY_EXCHANGE_WITH_SERVER_SIGNATURE_ENABLED */
 
 exit:
-    ssl->state++;
+    mbedtls_ssl_handshake_increment_state(ssl);
 
     MBEDTLS_SSL_DEBUG_MSG(2, ("<= parse server key exchange"));
 
@@ -2472,7 +2472,7 @@
 
     if (!mbedtls_ssl_ciphersuite_cert_req_allowed(ciphersuite_info)) {
         MBEDTLS_SSL_DEBUG_MSG(2, ("<= skip parse certificate request"));
-        ssl->state++;
+        mbedtls_ssl_handshake_increment_state(ssl);
         return 0;
     }
 
@@ -2499,7 +2499,7 @@
 
     if (!mbedtls_ssl_ciphersuite_cert_req_allowed(ciphersuite_info)) {
         MBEDTLS_SSL_DEBUG_MSG(2, ("<= skip parse certificate request"));
-        ssl->state++;
+        mbedtls_ssl_handshake_increment_state(ssl);
         return 0;
     }
 
@@ -2517,7 +2517,7 @@
         return MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE;
     }
 
-    ssl->state++;
+    mbedtls_ssl_handshake_increment_state(ssl);
     ssl->handshake->client_auth =
         (ssl->in_msg[0] == MBEDTLS_SSL_HS_CERTIFICATE_REQUEST);
 
@@ -2688,7 +2688,7 @@
         return MBEDTLS_ERR_SSL_DECODE_ERROR;
     }
 
-    ssl->state++;
+    mbedtls_ssl_handshake_increment_state(ssl);
 
 #if defined(MBEDTLS_SSL_PROTO_DTLS)
     if (ssl->conf->transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM) {
@@ -3199,7 +3199,7 @@
     ssl->out_msgtype = MBEDTLS_SSL_MSG_HANDSHAKE;
     ssl->out_msg[0]  = MBEDTLS_SSL_HS_CLIENT_KEY_EXCHANGE;
 
-    ssl->state++;
+    mbedtls_ssl_handshake_increment_state(ssl);
 
     if ((ret = mbedtls_ssl_write_handshake_msg(ssl)) != 0) {
         MBEDTLS_SSL_DEBUG_RET(1, "mbedtls_ssl_write_handshake_msg", ret);
@@ -3228,7 +3228,7 @@
 
     if (!mbedtls_ssl_ciphersuite_cert_req_allowed(ciphersuite_info)) {
         MBEDTLS_SSL_DEBUG_MSG(2, ("<= skip write certificate verify"));
-        ssl->state++;
+        mbedtls_ssl_handshake_increment_state(ssl);
         return 0;
     }
 
@@ -3270,14 +3270,14 @@
 
     if (!mbedtls_ssl_ciphersuite_cert_req_allowed(ciphersuite_info)) {
         MBEDTLS_SSL_DEBUG_MSG(2, ("<= skip write certificate verify"));
-        ssl->state++;
+        mbedtls_ssl_handshake_increment_state(ssl);
         return 0;
     }
 
     if (ssl->handshake->client_auth == 0 ||
         mbedtls_ssl_own_cert(ssl) == NULL) {
         MBEDTLS_SSL_DEBUG_MSG(2, ("<= skip write certificate verify"));
-        ssl->state++;
+        mbedtls_ssl_handshake_increment_state(ssl);
         return 0;
     }
 
@@ -3359,7 +3359,7 @@
     ssl->out_msgtype = MBEDTLS_SSL_MSG_HANDSHAKE;
     ssl->out_msg[0]  = MBEDTLS_SSL_HS_CERTIFICATE_VERIFY;
 
-    ssl->state++;
+    mbedtls_ssl_handshake_increment_state(ssl);
 
     if ((ret = mbedtls_ssl_write_handshake_msg(ssl)) != 0) {
         MBEDTLS_SSL_DEBUG_RET(1, "mbedtls_ssl_write_handshake_msg", ret);
@@ -3433,7 +3433,7 @@
 
     /* We're not waiting for a NewSessionTicket message any more */
     ssl->handshake->new_session_ticket = 0;
-    ssl->state = MBEDTLS_SSL_SERVER_CHANGE_CIPHER_SPEC;
+    mbedtls_ssl_handshake_set_state(ssl, MBEDTLS_SSL_SERVER_CHANGE_CIPHER_SPEC);
 
     /*
      * Zero-length ticket means the server changed his mind and doesn't want
@@ -3494,13 +3494,13 @@
 #if defined(MBEDTLS_SSL_SESSION_TICKETS)
     if (ssl->state == MBEDTLS_SSL_SERVER_CHANGE_CIPHER_SPEC &&
         ssl->handshake->new_session_ticket != 0) {
-        ssl->state = MBEDTLS_SSL_NEW_SESSION_TICKET;
+        mbedtls_ssl_handshake_set_state(ssl, MBEDTLS_SSL_NEW_SESSION_TICKET);
     }
 #endif
 
     switch (ssl->state) {
         case MBEDTLS_SSL_HELLO_REQUEST:
-            ssl->state = MBEDTLS_SSL_CLIENT_HELLO;
+            mbedtls_ssl_handshake_set_state(ssl, MBEDTLS_SSL_CLIENT_HELLO);
             break;
 
         /*
@@ -3585,7 +3585,7 @@
 
         case MBEDTLS_SSL_FLUSH_BUFFERS:
             MBEDTLS_SSL_DEBUG_MSG(2, ("handshake: done"));
-            ssl->state = MBEDTLS_SSL_HANDSHAKE_WRAPUP;
+            mbedtls_ssl_handshake_set_state(ssl, MBEDTLS_SSL_HANDSHAKE_WRAPUP);
             break;
 
         case MBEDTLS_SSL_HANDSHAKE_WRAPUP:
diff --git a/library/ssl_tls12_server.c b/library/ssl_tls12_server.c
index 03722ac..2d73855 100644
--- a/library/ssl_tls12_server.c
+++ b/library/ssl_tls12_server.c
@@ -1057,28 +1057,6 @@
         MBEDTLS_SSL_DEBUG_MSG(1, ("bad client hello message"));
         return MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE;
     }
-    {
-        size_t handshake_len = MBEDTLS_GET_UINT24_BE(buf, 1);
-        MBEDTLS_SSL_DEBUG_MSG(3, ("client hello v3, handshake len.: %u",
-                                  (unsigned) handshake_len));
-
-        /* The record layer has a record size limit of 2^14 - 1 and
-         * fragmentation is not supported, so buf[1] should be zero. */
-        if (buf[1] != 0) {
-            MBEDTLS_SSL_DEBUG_MSG(1, ("bad client hello message: %u != 0",
-                                      (unsigned) buf[1]));
-            return MBEDTLS_ERR_SSL_DECODE_ERROR;
-        }
-
-        /* We don't support fragmentation of ClientHello (yet?) */
-        if (msg_len != mbedtls_ssl_hs_hdr_len(ssl) + handshake_len) {
-            MBEDTLS_SSL_DEBUG_MSG(1, ("bad client hello message: %u != %u + %u",
-                                      (unsigned) msg_len,
-                                      (unsigned) mbedtls_ssl_hs_hdr_len(ssl),
-                                      (unsigned) handshake_len));
-            return MBEDTLS_ERR_SSL_DECODE_ERROR;
-        }
-    }
 
 #if defined(MBEDTLS_SSL_PROTO_DTLS)
     if (ssl->conf->transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM) {
@@ -1658,7 +1636,7 @@
     ssl->session_negotiate->ciphersuite = ciphersuites[i];
     ssl->handshake->ciphersuite_info = ciphersuite_info;
 
-    ssl->state++;
+    mbedtls_ssl_handshake_increment_state(ssl);
 
 #if defined(MBEDTLS_SSL_PROTO_DTLS)
     if (ssl->conf->transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM) {
@@ -2086,7 +2064,7 @@
     ssl->out_msgtype = MBEDTLS_SSL_MSG_HANDSHAKE;
     ssl->out_msg[0]  = MBEDTLS_SSL_HS_HELLO_VERIFY_REQUEST;
 
-    ssl->state = MBEDTLS_SSL_SERVER_HELLO_VERIFY_REQUEST_SENT;
+    mbedtls_ssl_handshake_set_state(ssl, MBEDTLS_SSL_SERVER_HELLO_VERIFY_REQUEST_SENT);
 
     if ((ret = mbedtls_ssl_write_handshake_msg(ssl)) != 0) {
         MBEDTLS_SSL_DEBUG_RET(1, "mbedtls_ssl_write_handshake_msg", ret);
@@ -2254,7 +2232,7 @@
          * New session, create a new session id,
          * unless we're about to issue a session ticket
          */
-        ssl->state++;
+        mbedtls_ssl_handshake_increment_state(ssl);
 
 #if defined(MBEDTLS_HAVE_TIME)
         ssl->session_negotiate->start = mbedtls_time(NULL);
@@ -2278,7 +2256,7 @@
          * Resuming a session
          */
         n = ssl->session_negotiate->id_len;
-        ssl->state = MBEDTLS_SSL_SERVER_CHANGE_CIPHER_SPEC;
+        mbedtls_ssl_handshake_set_state(ssl, MBEDTLS_SSL_SERVER_CHANGE_CIPHER_SPEC);
 
         if ((ret = mbedtls_ssl_derive_keys(ssl)) != 0) {
             MBEDTLS_SSL_DEBUG_RET(1, "mbedtls_ssl_derive_keys", ret);
@@ -2404,7 +2382,7 @@
 
     if (!mbedtls_ssl_ciphersuite_cert_req_allowed(ciphersuite_info)) {
         MBEDTLS_SSL_DEBUG_MSG(2, ("<= skip write certificate request"));
-        ssl->state++;
+        mbedtls_ssl_handshake_increment_state(ssl);
         return 0;
     }
 
@@ -2427,7 +2405,7 @@
 
     MBEDTLS_SSL_DEBUG_MSG(2, ("=> write certificate request"));
 
-    ssl->state++;
+    mbedtls_ssl_handshake_increment_state(ssl);
 
 #if defined(MBEDTLS_SSL_SERVER_NAME_INDICATION)
     if (ssl->handshake->sni_authmode != MBEDTLS_SSL_VERIFY_UNSET) {
@@ -3274,7 +3252,7 @@
         /* Key exchanges not involving ephemeral keys don't use
          * ServerKeyExchange, so end here. */
         MBEDTLS_SSL_DEBUG_MSG(2, ("<= skip write server key exchange"));
-        ssl->state++;
+        mbedtls_ssl_handshake_increment_state(ssl);
         return 0;
     }
 #endif /* MBEDTLS_KEY_EXCHANGE_SOME_NON_PFS_ENABLED */
@@ -3328,7 +3306,7 @@
     ssl->out_msgtype = MBEDTLS_SSL_MSG_HANDSHAKE;
     ssl->out_msg[0]  = MBEDTLS_SSL_HS_SERVER_KEY_EXCHANGE;
 
-    ssl->state++;
+    mbedtls_ssl_handshake_increment_state(ssl);
 
     if ((ret = mbedtls_ssl_write_handshake_msg(ssl)) != 0) {
         MBEDTLS_SSL_DEBUG_RET(1, "mbedtls_ssl_write_handshake_msg", ret);
@@ -3350,7 +3328,7 @@
     ssl->out_msgtype = MBEDTLS_SSL_MSG_HANDSHAKE;
     ssl->out_msg[0]  = MBEDTLS_SSL_HS_SERVER_HELLO_DONE;
 
-    ssl->state++;
+    mbedtls_ssl_handshake_increment_state(ssl);
 
 #if defined(MBEDTLS_SSL_PROTO_DTLS)
     if (ssl->conf->transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM) {
@@ -4074,7 +4052,7 @@
         return ret;
     }
 
-    ssl->state++;
+    mbedtls_ssl_handshake_increment_state(ssl);
 
     MBEDTLS_SSL_DEBUG_MSG(2, ("<= parse client key exchange"));
 
@@ -4092,7 +4070,7 @@
 
     if (!mbedtls_ssl_ciphersuite_cert_req_allowed(ciphersuite_info)) {
         MBEDTLS_SSL_DEBUG_MSG(2, ("<= skip parse certificate verify"));
-        ssl->state++;
+        mbedtls_ssl_handshake_increment_state(ssl);
         return 0;
     }
 
@@ -4118,20 +4096,20 @@
 
     if (!mbedtls_ssl_ciphersuite_cert_req_allowed(ciphersuite_info)) {
         MBEDTLS_SSL_DEBUG_MSG(2, ("<= skip parse certificate verify"));
-        ssl->state++;
+        mbedtls_ssl_handshake_increment_state(ssl);
         return 0;
     }
 
 #if defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE)
     if (ssl->session_negotiate->peer_cert == NULL) {
         MBEDTLS_SSL_DEBUG_MSG(2, ("<= skip parse certificate verify"));
-        ssl->state++;
+        mbedtls_ssl_handshake_increment_state(ssl);
         return 0;
     }
 #else /* MBEDTLS_SSL_KEEP_PEER_CERTIFICATE */
     if (ssl->session_negotiate->peer_cert_digest == NULL) {
         MBEDTLS_SSL_DEBUG_MSG(2, ("<= skip parse certificate verify"));
-        ssl->state++;
+        mbedtls_ssl_handshake_increment_state(ssl);
         return 0;
     }
 #endif /* !MBEDTLS_SSL_KEEP_PEER_CERTIFICATE */
@@ -4143,7 +4121,7 @@
         return ret;
     }
 
-    ssl->state++;
+    mbedtls_ssl_handshake_increment_state(ssl);
 
     /* Process the message contents */
     if (ssl->in_msgtype != MBEDTLS_SSL_MSG_HANDSHAKE ||
@@ -4327,7 +4305,7 @@
 
     switch (ssl->state) {
         case MBEDTLS_SSL_HELLO_REQUEST:
-            ssl->state = MBEDTLS_SSL_CLIENT_HELLO;
+            mbedtls_ssl_handshake_set_state(ssl, MBEDTLS_SSL_CLIENT_HELLO);
             break;
 
         /*
@@ -4416,7 +4394,7 @@
 
         case MBEDTLS_SSL_FLUSH_BUFFERS:
             MBEDTLS_SSL_DEBUG_MSG(2, ("handshake: done"));
-            ssl->state = MBEDTLS_SSL_HANDSHAKE_WRAPUP;
+            mbedtls_ssl_handshake_set_state(ssl, MBEDTLS_SSL_HANDSHAKE_WRAPUP);
             break;
 
         case MBEDTLS_SSL_HANDSHAKE_WRAPUP:
diff --git a/library/ssl_tls13_client.c b/library/ssl_tls13_client.c
index b63b5e6..78a069f 100644
--- a/library/ssl_tls13_client.c
+++ b/library/ssl_tls13_client.c
@@ -319,6 +319,7 @@
             ssl, group_id, p, end, &key_exchange_len);
         p += key_exchange_len;
         if (ret != 0) {
+            MBEDTLS_SSL_DEBUG_MSG(1, ("client hello: failed generating xxdh key exchange"));
             return ret;
         }
 
diff --git a/library/ssl_tls13_keys.c b/library/ssl_tls13_keys.c
index 739414e..fd559a7 100644
--- a/library/ssl_tls13_keys.c
+++ b/library/ssl_tls13_keys.c
@@ -58,15 +58,16 @@
  * };
  *
  * Parameters:
- * - desired_length: Length of expanded key material
- *                   Even though the standard allows expansion to up to
- *                   2**16 Bytes, TLS 1.3 never uses expansion to more than
- *                   255 Bytes, so we require `desired_length` to be at most
- *                   255. This allows us to save a few Bytes of code by
- *                   hardcoding the writing of the high bytes.
+ * - desired_length: Length of expanded key material.
+ *                   The length field can hold numbers up to 2**16, but HKDF
+ *                   can only generate outputs of up to 255 * HASH_LEN bytes.
+ *                   It is the caller's responsibility to ensure that this
+ *                   limit is not exceeded. In TLS 1.3, SHA256 is the hash
+ *                   function with the smallest block size, so a length
+ *                   <= 255 * 32 = 8160 is always safe.
  * - (label, label_len): label + label length, without "tls13 " prefix
  *                       The label length MUST be less than or equal to
- *                       MBEDTLS_SSL_TLS1_3_KEY_SCHEDULE_MAX_LABEL_LEN
+ *                       MBEDTLS_SSL_TLS1_3_HKDF_LABEL_MAX_LABEL_LEN.
  *                       It is the caller's responsibility to ensure this.
  *                       All (label, label length) pairs used in TLS 1.3
  *                       can be obtained via MBEDTLS_SSL_TLS1_3_LBL_WITH_LEN().
@@ -93,7 +94,7 @@
 #define SSL_TLS1_3_KEY_SCHEDULE_MAX_HKDF_LABEL_LEN                      \
     SSL_TLS1_3_KEY_SCHEDULE_HKDF_LABEL_LEN(                             \
         sizeof(tls13_label_prefix) +                       \
-        MBEDTLS_SSL_TLS1_3_KEY_SCHEDULE_MAX_LABEL_LEN,     \
+        MBEDTLS_SSL_TLS1_3_HKDF_LABEL_MAX_LABEL_LEN,       \
         MBEDTLS_SSL_TLS1_3_KEY_SCHEDULE_MAX_CONTEXT_LEN)
 
 static void ssl_tls13_hkdf_encode_label(
@@ -109,15 +110,13 @@
 
     unsigned char *p = dst;
 
-    /* Add the size of the expanded key material.
-     * We're hardcoding the high byte to 0 here assuming that we never use
-     * TLS 1.3 HKDF key expansion to more than 255 Bytes. */
-#if MBEDTLS_SSL_TLS1_3_KEY_SCHEDULE_MAX_EXPANSION_LEN > 255
-#error "The implementation of ssl_tls13_hkdf_encode_label() is not fit for the \
-    value of MBEDTLS_SSL_TLS1_3_KEY_SCHEDULE_MAX_EXPANSION_LEN"
+    /* Add the size of the expanded key material. */
+#if MBEDTLS_SSL_TLS1_3_KEY_SCHEDULE_MAX_EXPANSION_LEN > UINT16_MAX
+#error "The desired key length must fit into an uint16 but \
+    MBEDTLS_SSL_TLS1_3_KEY_SCHEDULE_MAX_EXPANSION_LEN is greater than UINT16_MAX"
 #endif
 
-    *p++ = 0;
+    *p++ = MBEDTLS_BYTE_1(desired_length);
     *p++ = MBEDTLS_BYTE_0(desired_length);
 
     /* Add label incl. prefix */
@@ -151,7 +150,7 @@
     psa_key_derivation_operation_t operation =
         PSA_KEY_DERIVATION_OPERATION_INIT;
 
-    if (label_len > MBEDTLS_SSL_TLS1_3_KEY_SCHEDULE_MAX_LABEL_LEN) {
+    if (label_len > MBEDTLS_SSL_TLS1_3_HKDF_LABEL_MAX_LABEL_LEN) {
         /* Should never happen since this is an internal
          * function, and we know statically which labels
          * are allowed. */
@@ -1882,4 +1881,37 @@
 }
 #endif /* MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_SOME_PSK_ENABLED */
 
+#if defined(MBEDTLS_SSL_KEYING_MATERIAL_EXPORT)
+int mbedtls_ssl_tls13_exporter(const psa_algorithm_t hash_alg,
+                               const unsigned char *secret, const size_t secret_len,
+                               const unsigned char *label, const size_t label_len,
+                               const unsigned char *context_value, const size_t context_len,
+                               unsigned char *out, const size_t out_len)
+{
+    size_t hash_len = PSA_HASH_LENGTH(hash_alg);
+    unsigned char hkdf_secret[MBEDTLS_TLS1_3_MD_MAX_SIZE];
+    int ret = 0;
+
+    ret = mbedtls_ssl_tls13_derive_secret(hash_alg, secret, secret_len, label, label_len, NULL, 0,
+                                          MBEDTLS_SSL_TLS1_3_CONTEXT_UNHASHED, hkdf_secret,
+                                          hash_len);
+    if (ret != 0) {
+        goto exit;
+    }
+    ret = mbedtls_ssl_tls13_derive_secret(hash_alg,
+                                          hkdf_secret,
+                                          hash_len,
+                                          MBEDTLS_SSL_TLS1_3_LBL_WITH_LEN(exporter),
+                                          context_value,
+                                          context_len,
+                                          MBEDTLS_SSL_TLS1_3_CONTEXT_UNHASHED,
+                                          out,
+                                          out_len);
+
+exit:
+    mbedtls_platform_zeroize(hkdf_secret, sizeof(hkdf_secret));
+    return ret;
+}
+#endif /* defined(MBEDTLS_SSL_KEYING_MATERIAL_EXPORT) */
+
 #endif /* MBEDTLS_SSL_PROTO_TLS1_3 */
diff --git a/library/ssl_tls13_keys.h b/library/ssl_tls13_keys.h
index d3a4c6c..14f6e48 100644
--- a/library/ssl_tls13_keys.h
+++ b/library/ssl_tls13_keys.h
@@ -60,8 +60,9 @@
     mbedtls_ssl_tls13_labels.LABEL,              \
     MBEDTLS_SSL_TLS1_3_LBL_LEN(LABEL)
 
-#define MBEDTLS_SSL_TLS1_3_KEY_SCHEDULE_MAX_LABEL_LEN  \
-    sizeof(union mbedtls_ssl_tls13_labels_union)
+/* Maximum length of the label field in the HkdfLabel struct defined in
+ * RFC 8446, Section 7.1, excluding the "tls13 " prefix. */
+#define MBEDTLS_SSL_TLS1_3_HKDF_LABEL_MAX_LABEL_LEN 249
 
 /* The maximum length of HKDF contexts used in the TLS 1.3 standard.
  * Since contexts are always hashes of message transcripts, this can
@@ -70,13 +71,11 @@
     PSA_HASH_MAX_SIZE
 
 /* Maximum desired length for expanded key material generated
- * by HKDF-Expand-Label.
- *
- * Warning: If this ever needs to be increased, the implementation
- * ssl_tls13_hkdf_encode_label() in ssl_tls13_keys.c needs to be
- * adjusted since it currently assumes that HKDF key expansion
- * is never used with more than 255 Bytes of output. */
-#define MBEDTLS_SSL_TLS1_3_KEY_SCHEDULE_MAX_EXPANSION_LEN 255
+ * by HKDF-Expand-Label. This algorithm can output up to 255 * hash_size
+ * bytes of key material where hash_size is the output size of the
+ * underlying hash function. */
+#define MBEDTLS_SSL_TLS1_3_KEY_SCHEDULE_MAX_EXPANSION_LEN \
+    (255 * MBEDTLS_TLS1_3_MD_MAX_SIZE)
 
 /**
  * \brief            The \c HKDF-Expand-Label function from
@@ -646,6 +645,23 @@
                                            size_t *psk_len);
 #endif
 
+/**
+ * \brief Calculate TLS-Exporter function as defined in RFC 8446, Section 7.5.
+ *
+ * \param[in]   hash_alg  The hash algorithm.
+ * \param[in]   secret  The secret to use. (Should be the exporter master secret.)
+ * \param[in]   secret_len  Length of secret.
+ * \param[in]   label  The label of the exported key.
+ * \param[in]   label_len  The length of label.
+ * \param[out]  out  The output buffer for the exported key. Must have room for at least out_len bytes.
+ * \param[in]   out_len  Length of the key to generate.
+ */
+int mbedtls_ssl_tls13_exporter(const psa_algorithm_t hash_alg,
+                               const unsigned char *secret, const size_t secret_len,
+                               const unsigned char *label, const size_t label_len,
+                               const unsigned char *context_value, const size_t context_len,
+                               uint8_t *out, const size_t out_len);
+
 #endif /* MBEDTLS_SSL_PROTO_TLS1_3 */
 
 #endif /* MBEDTLS_SSL_TLS1_3_KEYS_H */
diff --git a/library/ssl_tls13_server.c b/library/ssl_tls13_server.c
index 693edc7..5757d20 100644
--- a/library/ssl_tls13_server.c
+++ b/library/ssl_tls13_server.c
@@ -92,7 +92,7 @@
         return;
     }
 
-    MBEDTLS_SSL_DEBUG_MSG(2, ("No matched ciphersuite, psk_ciphersuite_id=%x, psk_hash_alg=%lx",
+    MBEDTLS_SSL_DEBUG_MSG(1, ("No matched ciphersuite, psk_ciphersuite_id=%x, psk_hash_alg=%lx",
                               (unsigned) psk_ciphersuite_id,
                               (unsigned long) psk_hash_alg));
 }
@@ -1380,6 +1380,7 @@
     }
 
     if (ret == 0) {
+        MBEDTLS_SSL_DEBUG_MSG(2, ("no supported_versions extension"));
         return SSL_CLIENT_HELLO_TLS1_2;
     }
 
@@ -1401,6 +1402,7 @@
          * the TLS version to negotiate.
          */
         if (MBEDTLS_SSL_VERSION_TLS1_2 == ret) {
+            MBEDTLS_SSL_DEBUG_MSG(2, ("supported_versions without 1.3"));
             return SSL_CLIENT_HELLO_TLS1_2;
         }
     }
@@ -1985,6 +1987,7 @@
         }
         ssl->keep_current_message = 1;
         ssl->tls_version = MBEDTLS_SSL_VERSION_TLS1_2;
+        MBEDTLS_SSL_DEBUG_MSG(1, ("non-1.3 ClientHello left for later processing"));
         return 0;
     }
 
diff --git a/programs/Makefile b/programs/Makefile
index 0604a68..e765886 100644
--- a/programs/Makefile
+++ b/programs/Makefile
@@ -1,4 +1,5 @@
 MBEDTLS_TEST_PATH = ../tests
+FRAMEWORK = ${MBEDTLS_PATH}/framework
 include ../scripts/common.make
 
 ifeq ($(shell uname -s),Linux)
@@ -24,6 +25,8 @@
 BUILD_DLOPEN =
 endif
 
+LOCAL_CFLAGS += -I$(FRAMEWORK)/tests/programs
+
 ## The following assignment is the list of base names of applications that
 ## will be built on Windows. Extra Linux/Unix/POSIX-only applications can
 ## be declared by appending with `APPS += ...` afterwards.
@@ -298,7 +301,7 @@
 
 SSL_TEST_OBJECTS = test/query_config.o ssl/ssl_test_lib.o
 SSL_TEST_DEPS = $(SSL_TEST_OBJECTS) \
-		test/query_config.h \
+		$(FRAMEWORK)/tests/programs/query_config.h \
 		ssl/ssl_test_lib.h \
 		ssl/ssl_test_common_source.c \
 		$(DEP)
@@ -319,7 +322,7 @@
 	echo "  CC    ssl/ssl_server2.c"
 	$(CC) $(LOCAL_CFLAGS) $(CFLAGS) ssl/ssl_server2.c $(SSL_TEST_OBJECTS) $(LOCAL_LDFLAGS) $(LDFLAGS) -o $@
 
-ssl/ssl_context_info$(EXEXT): ssl/ssl_context_info.c test/query_config.o test/query_config.h $(DEP)
+ssl/ssl_context_info$(EXEXT): ssl/ssl_context_info.c test/query_config.o $(FRAMEWORK)/tests/programs/query_config.h $(DEP)
 	echo "  CC    ssl/ssl_context_info.c"
 	$(CC) $(LOCAL_CFLAGS) $(CFLAGS) ssl/ssl_context_info.c test/query_config.o $(LOCAL_LDFLAGS) $(LDFLAGS) -o $@
 
@@ -360,17 +363,17 @@
 	$(CC) $(LOCAL_CFLAGS) $(CFLAGS) test/dlopen.c $(LDFLAGS) $(DLOPEN_LDFLAGS) -o $@
 endif
 
-test/metatest$(EXEXT): test/metatest.c $(DEP)
-	echo "  CC    test/metatest.c"
-	$(CC) $(LOCAL_CFLAGS) $(CFLAGS) -I ../library test/metatest.c    $(LOCAL_LDFLAGS) $(LDFLAGS) -o $@
+test/metatest$(EXEXT): $(FRAMEWORK)/tests/programs/metatest.c $(DEP)
+	echo "  CC    $(FRAMEWORK)/tests/programs/metatest.c"
+	$(CC) $(LOCAL_CFLAGS) $(CFLAGS) -I ../library $(FRAMEWORK)/tests/programs/metatest.c    $(LOCAL_LDFLAGS) $(LDFLAGS) -o $@
 
-test/query_config.o: test/query_config.c test/query_config.h $(DEP)
-	echo "  CC    test/query_config.c"
+test/query_config.o: test/query_config.c $(FRAMEWORK)/tests/programs/query_config.h $(DEP)
+	echo "  CC    $(FRAMEWORK)/tests/programs/query_config.c"
 	$(CC) $(LOCAL_CFLAGS) $(CFLAGS) -c test/query_config.c -o $@
 
-test/query_included_headers$(EXEXT): test/query_included_headers.c $(DEP)
-	echo "  CC    test/query_included_headers.c"
-	$(CC) $(LOCAL_CFLAGS) $(CFLAGS) test/query_included_headers.c    $(LOCAL_LDFLAGS) $(LDFLAGS) -o $@
+test/query_included_headers$(EXEXT): $(FRAMEWORK)/tests/programs/query_included_headers.c $(DEP)
+	echo "  CC    $(FRAMEWORK)/tests/programs/query_included_headers.c"
+	$(CC) $(LOCAL_CFLAGS) $(CFLAGS) $(FRAMEWORK)/tests/programs/query_included_headers.c    $(LOCAL_LDFLAGS) $(LDFLAGS) -o $@
 
 test/selftest$(EXEXT): test/selftest.c $(DEP)
 	echo "  CC    test/selftest.c"
@@ -380,13 +383,13 @@
 	echo "  CC    test/udp_proxy.c"
 	$(CC) $(LOCAL_CFLAGS) $(CFLAGS) test/udp_proxy.c    $(LOCAL_LDFLAGS) $(LDFLAGS) -o $@
 
-test/zeroize$(EXEXT): test/zeroize.c $(DEP)
-	echo "  CC    test/zeroize.c"
-	$(CC) $(LOCAL_CFLAGS) $(CFLAGS) test/zeroize.c    $(LOCAL_LDFLAGS) $(LDFLAGS) -o $@
+test/zeroize$(EXEXT): $(FRAMEWORK)/tests/programs/zeroize.c $(DEP)
+	echo "  CC    $(FRAMEWORK)/tests/programs/zeroize.c"
+	$(CC) $(LOCAL_CFLAGS) $(CFLAGS) $(FRAMEWORK)/tests/programs/zeroize.c    $(LOCAL_LDFLAGS) $(LDFLAGS) -o $@
 
-test/query_compile_time_config$(EXEXT): test/query_compile_time_config.c test/query_config.o test/query_config.h $(DEP)
-	echo "  CC    test/query_compile_time_config.c"
-	$(CC) $(LOCAL_CFLAGS) $(CFLAGS) test/query_compile_time_config.c test/query_config.o $(LOCAL_LDFLAGS) $(LDFLAGS) -o $@
+test/query_compile_time_config$(EXEXT): $(FRAMEWORK)/tests/programs/query_compile_time_config.c test/query_config.o $(FRAMEWORK)/tests/programs/query_config.h $(DEP)
+	echo "  CC    $(FRAMEWORK)/tests/programs/query_compile_time_config.c"
+	$(CC) $(LOCAL_CFLAGS) $(CFLAGS) $(FRAMEWORK)/tests/programs/query_compile_time_config.c test/query_config.o $(LOCAL_LDFLAGS) $(LDFLAGS) -o $@
 
 util/pem2der$(EXEXT): util/pem2der.c $(DEP)
 	echo "  CC    util/pem2der.c"
diff --git a/programs/README.md b/programs/README.md
index f53bde5..a58037d 100644
--- a/programs/README.md
+++ b/programs/README.md
@@ -53,7 +53,7 @@
 
 ## Random number generator (RNG) examples
 
-* [`random/gen_entropy.c`](random/gen_entropy.c): shows how to use the default entropy sources to generate random data.  
+* [`random/gen_entropy.c`](random/gen_entropy.c): shows how to use the default entropy sources to generate random data.
   Note: most applications should only use the entropy generator to seed a cryptographic pseudorandom generator, as illustrated by `random/gen_random_ctr_drbg.c`.
 
 * [`random/gen_random_ctr_drbg.c`](random/gen_random_ctr_drbg.c): shows how to use the default entropy sources to seed a pseudorandom generator, and how to use the resulting random generator to generate random data.
@@ -96,7 +96,7 @@
 
 * [`test/udp_proxy.c`](test/udp_proxy.c): a UDP proxy that can inject certain failures (delay, duplicate, drop). Useful for testing DTLS.
 
-* [`test/zeroize.c`](test/zeroize.c): a test program for `mbedtls_platform_zeroize`, used by [`tests/scripts/test_zeroize.gdb`](tests/scripts/test_zeroize.gdb).
+* [`test/zeroize.c`](../framework/tests/programs/zeroize.c): a test program for `mbedtls_platform_zeroize`, used by [`test_zeroize.gdb`](../framework/tests/programs/test_zeroize.gdb).
 
 ## Development utilities
 
diff --git a/programs/demo_common.sh b/programs/demo_common.sh
deleted file mode 100644
index d8fcda5..0000000
--- a/programs/demo_common.sh
+++ /dev/null
@@ -1,137 +0,0 @@
-## Common shell functions used by demo scripts programs/*/*.sh.
-
-## How to write a demo script
-## ==========================
-##
-## Include this file near the top of each demo script:
-##   . "${0%/*}/../demo_common.sh"
-##
-## Start with a "msg" call that explains the purpose of the script.
-## Then call the "depends_on" function to ensure that all config
-## dependencies are met.
-##
-## As the last thing in the script, call the cleanup function.
-##
-## You can use the functions and variables described below.
-
-set -e -u
-
-## $root_dir is the root directory of the Mbed TLS source tree.
-root_dir="${0%/*}"
-# Find a nice path to the root directory, avoiding unnecessary "../".
-# The code supports demo scripts nested up to 4 levels deep.
-# The code works no matter where the demo script is relative to the current
-# directory, even if it is called with a relative path.
-n=4 # limit the search depth
-while ! [ -d "$root_dir/programs" ] || ! [ -d "$root_dir/library" ]; do
-  if [ $n -eq 0 ]; then
-    echo >&2 "This doesn't seem to be an Mbed TLS source tree."
-    exit 125
-  fi
-  n=$((n - 1))
-  case $root_dir in
-    .) root_dir="..";;
-    ..|?*/..) root_dir="$root_dir/..";;
-    ?*/*) root_dir="${root_dir%/*}";;
-    /*) root_dir="/";;
-    *) root_dir=".";;
-  esac
-done
-
-## $programs_dir is the directory containing the sample programs.
-# Assume an in-tree build.
-programs_dir="$root_dir/programs"
-
-## msg LINE...
-## msg <TEXT_ORIGIN
-## Display an informational message.
-msg () {
-  if [ $# -eq 0 ]; then
-    sed 's/^/# /'
-  else
-    for x in "$@"; do
-      echo "# $x"
-    done
-  fi
-}
-
-## run "Message" COMMAND ARGUMENT...
-## Display the message, then run COMMAND with the specified arguments.
-run () {
-    echo
-    echo "# $1"
-    shift
-    echo "+ $*"
-    "$@"
-}
-
-## Like '!', but stop on failure with 'set -e'
-not () {
-  if "$@"; then false; fi
-}
-
-## run_bad "Message" COMMAND ARGUMENT...
-## Like run, but the command is expected to fail.
-run_bad () {
-  echo
-  echo "$1 This must fail."
-  shift
-  echo "+ ! $*"
-  not "$@"
-}
-
-## config_has SYMBOL...
-## Succeeds if the library configuration has all SYMBOLs set.
-config_has () {
-  for x in "$@"; do
-    "$programs_dir/test/query_compile_time_config" "$x"
-  done
-}
-
-## depends_on SYMBOL...
-## Exit if the library configuration does not have all SYMBOLs set.
-depends_on () {
-  m=
-  for x in "$@"; do
-    if ! config_has "$x"; then
-      m="$m $x"
-    fi
-  done
-  if [ -n "$m" ]; then
-    cat >&2 <<EOF
-$0: this demo requires the following
-configuration options to be enabled at compile time:
- $m
-EOF
-    # Exit with a success status so that this counts as a pass for run_demos.py.
-    exit
-  fi
-}
-
-## Add the names of files to clean up to this whitespace-separated variable.
-## The file names must not contain whitespace characters.
-files_to_clean=
-
-## Call this function at the end of each script.
-## It is called automatically if the script is killed by a signal.
-cleanup () {
-  rm -f -- $files_to_clean
-}
-
-
-
-################################################################
-## End of the public interfaces. Code beyond this point is not
-## meant to be called directly from a demo script.
-
-trap 'cleanup; trap - HUP; kill -HUP $$' HUP
-trap 'cleanup; trap - INT; kill -INT $$' INT
-trap 'cleanup; trap - TERM; kill -TERM $$' TERM
-
-if config_has MBEDTLS_ENTROPY_NV_SEED; then
-  # Create a seedfile that's sufficiently long in all library configurations.
-  # This is necessary for programs that use randomness.
-  # Assume that the name of the seedfile is the default name.
-  files_to_clean="$files_to_clean seedfile"
-  dd if=/dev/urandom of=seedfile ibs=64 obs=64 count=1
-fi
diff --git a/programs/psa/key_ladder_demo.sh b/programs/psa/key_ladder_demo.sh
index e55da7e..e3afb66 100755
--- a/programs/psa/key_ladder_demo.sh
+++ b/programs/psa/key_ladder_demo.sh
@@ -3,7 +3,7 @@
 # Copyright The Mbed TLS Contributors
 # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
 
-. "${0%/*}/../demo_common.sh"
+. "${0%/*}/../../framework/scripts/demo_common.sh"
 
 msg <<'EOF'
 This script demonstrates the use of the PSA cryptography interface to
diff --git a/programs/psa/psa_hash_demo.sh b/programs/psa/psa_hash_demo.sh
index a26697c..c2cc87a 100755
--- a/programs/psa/psa_hash_demo.sh
+++ b/programs/psa/psa_hash_demo.sh
@@ -3,7 +3,7 @@
 # Copyright The Mbed TLS Contributors
 # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
 
-. "${0%/*}/../demo_common.sh"
+. "${0%/*}/../../framework/scripts/demo_common.sh"
 
 msg <<'EOF'
 This program demonstrates the use of the PSA cryptography interface to
diff --git a/programs/ssl/CMakeLists.txt b/programs/ssl/CMakeLists.txt
index 1a9e75e..4e954f0 100644
--- a/programs/ssl/CMakeLists.txt
+++ b/programs/ssl/CMakeLists.txt
@@ -35,20 +35,19 @@
     if(exe STREQUAL "ssl_client2" OR exe STREQUAL "ssl_server2")
         list(APPEND extra_sources
             ssl_test_lib.c
-            ${CMAKE_CURRENT_SOURCE_DIR}/../test/query_config.h
+            ${MBEDTLS_FRAMEWORK_DIR}/tests/programs/query_config.h
             ${CMAKE_CURRENT_BINARY_DIR}/../test/query_config.c)
     endif()
     add_executable(${exe} ${exe}.c $<TARGET_OBJECTS:mbedtls_test>
         ${extra_sources})
     target_link_libraries(${exe} ${libs} ${CMAKE_THREAD_LIBS_INIT})
-    target_include_directories(${exe} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../framework/tests/include
+    target_include_directories(${exe} PRIVATE ${MBEDTLS_FRAMEWORK_DIR}/tests/programs
+                                              ${MBEDTLS_FRAMEWORK_DIR}/tests/include
                                               ${CMAKE_CURRENT_SOURCE_DIR}/../../tests/include)
     if(exe STREQUAL "ssl_client2" OR exe STREQUAL "ssl_server2")
         if(GEN_FILES)
             add_dependencies(${exe} generate_query_config_c)
         endif()
-        target_include_directories(${exe}
-            PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../test)
     endif()
 endforeach()
 
diff --git a/programs/ssl/ssl_client2.c b/programs/ssl/ssl_client2.c
index ec68730..ed72095 100644
--- a/programs/ssl/ssl_client2.c
+++ b/programs/ssl/ssl_client2.c
@@ -68,6 +68,7 @@
 #define DFL_MAX_VERSION         -1
 #define DFL_SHA1                -1
 #define DFL_AUTH_MODE           -1
+#define DFL_SET_HOSTNAME        1
 #define DFL_MFL_CODE            MBEDTLS_SSL_MAX_FRAG_LEN_NONE
 #define DFL_TRUNC_HMAC          -1
 #define DFL_RECSPLIT            -1
@@ -76,6 +77,7 @@
 #define DFL_RECO_SERVER_NAME    NULL
 #define DFL_RECO_DELAY          0
 #define DFL_RECO_MODE           1
+#define DFL_RENEGO_DELAY        -2
 #define DFL_CID_ENABLED         0
 #define DFL_CID_VALUE           ""
 #define DFL_CID_ENABLED_RENEGO  -1
@@ -103,6 +105,8 @@
 #define DFL_NSS_KEYLOG          0
 #define DFL_NSS_KEYLOG_FILE     NULL
 #define DFL_SKIP_CLOSE_NOTIFY   0
+#define DFL_EXP_LABEL           NULL
+#define DFL_EXP_LEN             20
 #define DFL_QUERY_CONFIG_MODE   0
 #define DFL_USE_SRTP            0
 #define DFL_SRTP_FORCE_PROFILE  0
@@ -308,7 +312,8 @@
 #if defined(MBEDTLS_SSL_RENEGOTIATION)
 #define USAGE_RENEGO \
     "    renegotiation=%%d    default: 0 (disabled)\n"      \
-    "    renegotiate=%%d      default: 0 (disabled)\n"
+    "    renegotiate=%%d      default: 0 (disabled)\n"      \
+    "    renego_delay=%%d     default: -2 (library default)\n"
 #else
 #define USAGE_RENEGO ""
 #endif
@@ -372,6 +377,16 @@
 #define USAGE_TLS1_3_KEY_EXCHANGE_MODES ""
 #endif /* MBEDTLS_SSL_PROTO_TLS1_3 */
 
+#if defined(MBEDTLS_SSL_KEYING_MATERIAL_EXPORT)
+#define USAGE_EXPORT \
+    "    exp_label=%%s       Label to input into TLS-Exporter\n" \
+    "                         default: None (don't try to export a key)\n" \
+    "    exp_len=%%d         Length of key to extract from TLS-Exporter \n" \
+    "                         default: 20\n"
+#else
+#define USAGE_EXPORT ""
+#endif /* defined(MBEDTLS_SSL_KEYING_MATERIAL_EXPORT) */
+
 /* USAGE is arbitrarily split to stay under the portable string literal
  * length limit: 4095 bytes in C99. */
 #define USAGE1 \
@@ -405,6 +420,9 @@
 #define USAGE2 \
     "    auth_mode=%%s        default: (library default: none)\n" \
     "                        options: none, optional, required\n" \
+    "    set_hostname=%%s     call mbedtls_ssl_set_hostname()?" \
+    "                        options: no, server_name, NULL\n" \
+    "                        default: server_name (but ignored if certs disabled)\n"  \
     USAGE_IO                                                \
     USAGE_KEY_OPAQUE                                        \
     USAGE_CA_CALLBACK                                       \
@@ -459,6 +477,7 @@
     "                                otherwise. The expansion of the macro\n" \
     "                                is printed if it is defined\n"           \
     USAGE_SERIALIZATION                                                       \
+    USAGE_EXPORT                                                              \
     "\n"
 
 /*
@@ -507,6 +526,8 @@
     int max_version;            /* maximum protocol version accepted        */
     int allow_sha1;             /* flag for SHA-1 support                   */
     int auth_mode;              /* verify mode for connection               */
+    int set_hostname;           /* call mbedtls_ssl_set_hostname()?         */
+                                /* 0=no, 1=yes, -1=NULL */
     unsigned char mfl_code;     /* code for maximum fragment length         */
     int trunc_hmac;             /* negotiate truncated hmac or not          */
     int recsplit;               /* enable record splitting?                 */
@@ -545,6 +566,8 @@
                                  * after renegotiation                      */
     int reproducible;           /* make communication reproducible          */
     int skip_close_notify;      /* skip sending the close_notify alert      */
+    const char *exp_label;      /* label to input into mbedtls_ssl_export_keying_material() */
+    int exp_len;                /* Length of key to export using mbedtls_ssl_export_keying_material() */
 #if defined(MBEDTLS_SSL_EARLY_DATA)
     int early_data;             /* early data enablement flag               */
 #endif
@@ -957,11 +980,13 @@
     opt.renegotiation       = DFL_RENEGOTIATION;
     opt.allow_legacy        = DFL_ALLOW_LEGACY;
     opt.renegotiate         = DFL_RENEGOTIATE;
+    opt.renego_delay        = DFL_RENEGO_DELAY;
     opt.exchanges           = DFL_EXCHANGES;
     opt.min_version         = DFL_MIN_VERSION;
     opt.max_version         = DFL_MAX_VERSION;
     opt.allow_sha1          = DFL_SHA1;
     opt.auth_mode           = DFL_AUTH_MODE;
+    opt.set_hostname        = DFL_SET_HOSTNAME;
     opt.mfl_code            = DFL_MFL_CODE;
     opt.trunc_hmac          = DFL_TRUNC_HMAC;
     opt.recsplit            = DFL_RECSPLIT;
@@ -994,6 +1019,8 @@
     opt.nss_keylog          = DFL_NSS_KEYLOG;
     opt.nss_keylog_file     = DFL_NSS_KEYLOG_FILE;
     opt.skip_close_notify   = DFL_SKIP_CLOSE_NOTIFY;
+    opt.exp_label           = DFL_EXP_LABEL;
+    opt.exp_len             = DFL_EXP_LEN;
     opt.query_config_mode   = DFL_QUERY_CONFIG_MODE;
     opt.use_srtp            = DFL_USE_SRTP;
     opt.force_srtp_profile  = DFL_SRTP_FORCE_PROFILE;
@@ -1193,6 +1220,8 @@
                     break;
                 default: goto usage;
             }
+        } else if (strcmp(p, "renego_delay") == 0) {
+            opt.renego_delay = (atoi(q));
         } else if (strcmp(p, "renegotiate") == 0) {
             opt.renegotiate = atoi(q);
             if (opt.renegotiate < 0 || opt.renegotiate > 1) {
@@ -1359,6 +1388,16 @@
             } else {
                 goto usage;
             }
+        } else if (strcmp(p, "set_hostname") == 0) {
+            if (strcmp(q, "no") == 0) {
+                opt.set_hostname = 0;
+            } else if (strcmp(q, "server_name") == 0) {
+                opt.set_hostname = 1;
+            } else if (strcmp(q, "NULL") == 0) {
+                opt.set_hostname = -1;
+            } else {
+                goto usage;
+            }
         } else if (strcmp(p, "max_frag_len") == 0) {
             if (strcmp(q, "512") == 0) {
                 opt.mfl_code = MBEDTLS_SSL_MAX_FRAG_LEN_512;
@@ -1438,6 +1477,10 @@
             if (opt.skip_close_notify < 0 || opt.skip_close_notify > 1) {
                 goto usage;
             }
+        } else if (strcmp(p, "exp_label") == 0) {
+            opt.exp_label = q;
+        } else if (strcmp(p, "exp_len") == 0) {
+            opt.exp_len = atoi(q);
         } else if (strcmp(p, "use_srtp") == 0) {
             opt.use_srtp = atoi(q);
         } else if (strcmp(p, "srtp_force_profile") == 0) {
@@ -1966,6 +2009,9 @@
     }
 #if defined(MBEDTLS_SSL_RENEGOTIATION)
     mbedtls_ssl_conf_renegotiation(&conf, opt.renegotiation);
+    if (opt.renego_delay != DFL_RENEGO_DELAY) {
+        mbedtls_ssl_conf_renegotiation_enforced(&conf, opt.renego_delay);
+    }
 #endif
 
 #if defined(MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED)
@@ -2073,10 +2119,24 @@
 #endif /* MBEDTLS_SSL_DTLS_SRTP */
 
 #if defined(MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED)
-    if ((ret = mbedtls_ssl_set_hostname(&ssl, opt.server_name)) != 0) {
-        mbedtls_printf(" failed\n  ! mbedtls_ssl_set_hostname returned %d\n\n",
-                       ret);
-        goto exit;
+    switch (opt.set_hostname) {
+        case -1:
+            if ((ret = mbedtls_ssl_set_hostname(&ssl, NULL)) != 0) {
+                mbedtls_printf(" failed\n  ! mbedtls_ssl_set_hostname returned %d\n\n",
+                               ret);
+                goto exit;
+            }
+            break;
+        case 0:
+            /* Skip the call */
+            break;
+        default:
+            if ((ret = mbedtls_ssl_set_hostname(&ssl, opt.server_name)) != 0) {
+                mbedtls_printf(" failed\n  ! mbedtls_ssl_set_hostname returned %d\n\n",
+                               ret);
+                goto exit;
+            }
+            break;
     }
 #endif
 
@@ -2510,6 +2570,8 @@
         }
         mbedtls_printf(" ok\n");
     }
+
+
 #endif /* MBEDTLS_SSL_RENEGOTIATION */
 
 #if defined(MBEDTLS_SSL_DTLS_CONNECTION_ID)
@@ -2519,6 +2581,33 @@
     }
 #endif /* MBEDTLS_SSL_DTLS_CONNECTION_ID */
 
+#if defined(MBEDTLS_SSL_KEYING_MATERIAL_EXPORT)
+    if (opt.exp_label != NULL && opt.exp_len > 0) {
+        unsigned char *exported_key = mbedtls_calloc((size_t) opt.exp_len, sizeof(unsigned char));
+        if (exported_key == NULL) {
+            mbedtls_printf("Could not allocate %d bytes\n", opt.exp_len);
+            ret = 3;
+            goto exit;
+        }
+        ret = mbedtls_ssl_export_keying_material(&ssl, exported_key, (size_t) opt.exp_len,
+                                                 opt.exp_label, strlen(opt.exp_label),
+                                                 NULL, 0, 0);
+        if (ret != 0) {
+            mbedtls_free(exported_key);
+            goto exit;
+        }
+        mbedtls_printf("Exporting key of length %d with label \"%s\": 0x",
+                       opt.exp_len,
+                       opt.exp_label);
+        for (i = 0; i < opt.exp_len; i++) {
+            mbedtls_printf("%02X", exported_key[i]);
+        }
+        mbedtls_printf("\n\n");
+        fflush(stdout);
+        mbedtls_free(exported_key);
+    }
+#endif /* defined(MBEDTLS_SSL_KEYING_MATERIAL_EXPORT) */
+
     /*
      * 6. Write the GET request
      */
diff --git a/programs/ssl/ssl_context_info.c b/programs/ssl/ssl_context_info.c
index 51e8781..b9a0fe8 100644
--- a/programs/ssl/ssl_context_info.c
+++ b/programs/ssl/ssl_context_info.c
@@ -743,6 +743,13 @@
  *  uint8 alpn_chosen_len;
  *  uint8 alpn_chosen<0..2^8-1> // ALPN: negotiated application protocol
  *
+ * Note: In the mbedtls_ssl_context structure, badmac_seen is called
+ * badmac_seen_or_in_hsfraglen since Mbed TLS 3.6.2. The field contains
+ * the badmac_seen value in DTLS, and a handshake parsing intermediate
+ * value in non-DTLS TLS. The value is only meaningful for DTLS and should
+ * not be saved in non-DTLS TLS, so in this program, the context info file
+ * filed remains badmac_seen.
+ *
  * /p ssl   pointer to serialized session
  * /p len   number of bytes in the buffer
  */
diff --git a/programs/ssl/ssl_server2.c b/programs/ssl/ssl_server2.c
index 5de734f..e953949 100644
--- a/programs/ssl/ssl_server2.c
+++ b/programs/ssl/ssl_server2.c
@@ -71,6 +71,8 @@
 #define DFL_NBIO                0
 #define DFL_EVENT               0
 #define DFL_READ_TIMEOUT        0
+#define DFL_EXP_LABEL           NULL
+#define DFL_EXP_LEN             20
 #define DFL_CA_FILE             ""
 #define DFL_CA_PATH             ""
 #define DFL_CRT_FILE            ""
@@ -476,6 +478,16 @@
 #define USAGE_SERIALIZATION ""
 #endif
 
+#if defined(MBEDTLS_SSL_KEYING_MATERIAL_EXPORT)
+#define USAGE_EXPORT \
+    "    exp_label=%%s       Label to input into TLS-Exporter\n" \
+    "                         default: None (don't try to export a key)\n" \
+    "    exp_len=%%d         Length of key to extract from TLS-Exporter \n" \
+    "                         default: 20\n"
+#else
+#define USAGE_EXPORT ""
+#endif
+
 #define USAGE_KEY_OPAQUE_ALGS \
     "    key_opaque_algs=%%s  Allowed opaque key 1 algorithms.\n"                      \
     "                        comma-separated pair of values among the following:\n"    \
@@ -583,6 +595,7 @@
     "                                otherwise. The expansion of the macro\n" \
     "                                is printed if it is defined\n"           \
     USAGE_SERIALIZATION                                                       \
+    USAGE_EXPORT                                                              \
     "\n"
 
 #define PUT_UINT64_BE(out_be, in_le, i)                                   \
@@ -610,6 +623,8 @@
     int nbio;                   /* should I/O be blocking?                  */
     int event;                  /* loop or event-driven IO? level or edge triggered? */
     uint32_t read_timeout;      /* timeout on mbedtls_ssl_read() in milliseconds    */
+    const char *exp_label;      /* label to input into mbedtls_ssl_export_keying_material() */
+    int exp_len;                /* Length of key to export using mbedtls_ssl_export_keying_material() */
     int response_size;          /* pad response with header to requested size */
     uint16_t buffer_size;       /* IO buffer size */
     const char *ca_file;        /* the file with the CA certificate(s)      */
@@ -1699,6 +1714,8 @@
     opt.cid_val             = DFL_CID_VALUE;
     opt.cid_val_renego      = DFL_CID_VALUE_RENEGO;
     opt.read_timeout        = DFL_READ_TIMEOUT;
+    opt.exp_label           = DFL_EXP_LABEL;
+    opt.exp_len             = DFL_EXP_LEN;
     opt.ca_file             = DFL_CA_FILE;
     opt.ca_path             = DFL_CA_PATH;
     opt.crt_file            = DFL_CRT_FILE;
@@ -1877,6 +1894,10 @@
             }
         } else if (strcmp(p, "read_timeout") == 0) {
             opt.read_timeout = atoi(q);
+        } else if (strcmp(p, "exp_label") == 0) {
+            opt.exp_label = q;
+        } else if (strcmp(p, "exp_len") == 0) {
+            opt.exp_len = atoi(q);
         } else if (strcmp(p, "buffer_size") == 0) {
             opt.buffer_size = atoi(q);
             if (opt.buffer_size < 1) {
@@ -3642,6 +3663,33 @@
         mbedtls_printf("\n");
     }
 
+#if defined(MBEDTLS_SSL_KEYING_MATERIAL_EXPORT)
+    if (opt.exp_label != NULL && opt.exp_len > 0) {
+        unsigned char *exported_key = mbedtls_calloc((size_t) opt.exp_len, sizeof(unsigned char));
+        if (exported_key == NULL) {
+            mbedtls_printf("Could not allocate %d bytes\n", opt.exp_len);
+            ret = 3;
+            goto exit;
+        }
+        ret = mbedtls_ssl_export_keying_material(&ssl, exported_key, (size_t) opt.exp_len,
+                                                 opt.exp_label, strlen(opt.exp_label),
+                                                 NULL, 0, 0);
+        if (ret != 0) {
+            mbedtls_free(exported_key);
+            goto exit;
+        }
+        mbedtls_printf("Exporting key of length %d with label \"%s\": 0x",
+                       opt.exp_len,
+                       opt.exp_label);
+        for (i = 0; i < opt.exp_len; i++) {
+            mbedtls_printf("%02X", exported_key[i]);
+        }
+        mbedtls_printf("\n\n");
+        fflush(stdout);
+        mbedtls_free(exported_key);
+    }
+#endif /* defined(MBEDTLS_SSL_KEYING_MATERIAL_EXPORT) */
+
 #if defined(MBEDTLS_SSL_DTLS_SRTP)
     else if (opt.use_srtp != 0) {
         size_t j = 0;
diff --git a/programs/ssl/ssl_test_common_source.c b/programs/ssl/ssl_test_common_source.c
index 6d333e8..f634b3e 100644
--- a/programs/ssl/ssl_test_common_source.c
+++ b/programs/ssl/ssl_test_common_source.c
@@ -315,7 +315,7 @@
 };
 #endif /* MBEDTLS_X509_CRT_PARSE_C */
 
-#if defined(MBEDTLS_X509_CRT_PARSE_C)
+#if defined(MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED)
 /** Functionally equivalent to mbedtls_x509_crt_verify_info, see that function
  *  for more info.
  */
@@ -350,9 +350,7 @@
     return (int) (size - n);
 #endif /* MBEDTLS_X509_REMOVE_INFO */
 }
-#endif /* MBEDTLS_X509_CRT_PARSE_C */
 
-#if defined(MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED)
 static void mbedtls_print_supported_sig_algs(void)
 {
     mbedtls_printf("supported signature algorithms:\n");
diff --git a/programs/ssl/ssl_test_lib.h b/programs/ssl/ssl_test_lib.h
index 1da2dfb..d7fe80f 100644
--- a/programs/ssl/ssl_test_lib.h
+++ b/programs/ssl/ssl_test_lib.h
@@ -66,7 +66,7 @@
 
 #include <test/helpers.h>
 
-#include "../test/query_config.h"
+#include "query_config.h"
 
 #define ALPN_LIST_SIZE    10
 #define GROUP_LIST_SIZE   25
@@ -243,8 +243,8 @@
  * - free the provided PK context and re-initilize it as an opaque PK context
  *   wrapping the PSA key imported in the above step.
  *
- * \param[in/out] pk    On input the non-opaque PK context which contains the
- *                      key to be wrapped. On output the re-initialized PK
+ * \param[in,out] pk    On input, the non-opaque PK context which contains the
+ *                      key to be wrapped. On output, the re-initialized PK
  *                      context which represents the opaque version of the one
  *                      provided as input.
  * \param[in] psa_alg   The primary algorithm that will be associated to the
diff --git a/programs/test/CMakeLists.txt b/programs/test/CMakeLists.txt
index 70d827b..4511cdb 100644
--- a/programs/test/CMakeLists.txt
+++ b/programs/test/CMakeLists.txt
@@ -68,15 +68,22 @@
 endif()
 
 foreach(exe IN LISTS executables_libs executables_mbedcrypto)
+    set(source ${exe}.c)
     set(extra_sources "")
+    if(NOT EXISTS ${source} AND
+       EXISTS ${MBEDTLS_FRAMEWORK_DIR}/tests/programs/${source})
+         set(source ${MBEDTLS_FRAMEWORK_DIR}/tests/programs/${source})
+    endif()
+
     if(exe STREQUAL "query_compile_time_config")
         list(APPEND extra_sources
-            ${CMAKE_CURRENT_SOURCE_DIR}/query_config.h
+            ${MBEDTLS_FRAMEWORK_DIR}/tests/programs/query_config.h
             ${CMAKE_CURRENT_BINARY_DIR}/query_config.c)
     endif()
-    add_executable(${exe} ${exe}.c $<TARGET_OBJECTS:mbedtls_test>
+    add_executable(${exe} ${source} $<TARGET_OBJECTS:mbedtls_test>
         ${extra_sources})
-    target_include_directories(${exe} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../framework/tests/include)
+    target_include_directories(${exe} PRIVATE ${MBEDTLS_FRAMEWORK_DIR}/tests/include)
+    target_include_directories(${exe} PRIVATE ${MBEDTLS_FRAMEWORK_DIR}/tests/programs)
     target_include_directories(${exe} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../library)
     if(exe STREQUAL "query_compile_time_config")
         target_include_directories(${exe} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
diff --git a/programs/test/dlopen_demo.sh b/programs/test/dlopen_demo.sh
deleted file mode 100755
index 7280f1d..0000000
--- a/programs/test/dlopen_demo.sh
+++ /dev/null
@@ -1,42 +0,0 @@
-#!/bin/sh
-
-# Run the shared library dynamic loading demo program.
-# This is only expected to work when Mbed TLS is built as a shared library.
-
-# Copyright The Mbed TLS Contributors
-# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
-
-. "${0%/*}/../demo_common.sh"
-
-msg "Test the dynamic loading of libmbed*"
-
-program="$programs_dir/test/dlopen"
-library_dir="$root_dir/library"
-
-# Skip this test if we don't have a shared library build. Detect this
-# through the absence of the demo program.
-if [ ! -e "$program" ]; then
-    msg "$0: this demo requires a shared library build."
-    # Exit with a success status so that this counts as a pass for run_demos.py.
-    exit
-fi
-
-# ELF-based Unix-like (Linux, *BSD, Solaris, ...)
-if [ -n "${LD_LIBRARY_PATH-}" ]; then
-    LD_LIBRARY_PATH="$library_dir:$LD_LIBRARY_PATH"
-else
-    LD_LIBRARY_PATH="$library_dir"
-fi
-export LD_LIBRARY_PATH
-
-# OSX/macOS
-if [ -n "${DYLD_LIBRARY_PATH-}" ]; then
-    DYLD_LIBRARY_PATH="$library_dir:$DYLD_LIBRARY_PATH"
-else
-    DYLD_LIBRARY_PATH="$library_dir"
-fi
-export DYLD_LIBRARY_PATH
-
-msg "Running dynamic loading test program: $program"
-msg "Loading libraries from: $library_dir"
-"$program"
diff --git a/programs/test/metatest.c b/programs/test/metatest.c
deleted file mode 100644
index f39cb54..0000000
--- a/programs/test/metatest.c
+++ /dev/null
@@ -1,484 +0,0 @@
-/** \file metatest.c
- *
- *  \brief Test features of the test framework.
- *
- * When you run this program, it runs a single "meta-test". A meta-test
- * performs an operation which should be caught as a failure by our
- * test framework. The meta-test passes if this program calls `exit` with
- * a nonzero status, or aborts, or is terminated by a signal, or if the
- * framework running the program considers the run an error (this happens
- * with Valgrind for a memory leak). The non-success of the meta-test
- * program means that the test failure has been caught correctly.
- *
- * Some failures are purely functional: the logic of the code causes the
- * test result to be set to FAIL. Other failures come from extra
- * instrumentation which is not present in a normal build; for example,
- * Asan or Valgrind to detect memory leaks. This is reflected by the
- * "platform" associated with each meta-test.
- *
- * Use the companion script `tests/scripts/run-metatests.sh` to run all
- * the meta-tests for a given platform and validate that they trigger a
- * detected failure as expected.
- */
-
-/*
- *  Copyright The Mbed TLS Contributors
- *  SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
- */
-
-
-#include <mbedtls/debug.h>
-#include <mbedtls/platform.h>
-#include <mbedtls/platform_util.h>
-#include "test/helpers.h"
-#include "test/threading_helpers.h"
-#include "test/macros.h"
-#include "test/memory.h"
-#include "common.h"
-
-#include <stdio.h>
-#include <string.h>
-
-#if defined(MBEDTLS_THREADING_C)
-#include <mbedtls/threading.h>
-#endif
-
-
-/* This is an external variable, so the compiler doesn't know that we're never
- * changing its value.
- */
-volatile int false_but_the_compiler_does_not_know = 0;
-
-/* Hide calls to calloc/free from static checkers such as
- * `gcc-12 -Wuse-after-free`, to avoid compile-time complaints about
- * code where we do mean to cause a runtime error. */
-void * (* volatile calloc_but_the_compiler_does_not_know)(size_t, size_t) = mbedtls_calloc;
-void(*volatile free_but_the_compiler_does_not_know)(void *) = mbedtls_free;
-
-/* Set n bytes at the address p to all-bits-zero, in such a way that
- * the compiler should not know that p is all-bits-zero. */
-static void set_to_zero_but_the_compiler_does_not_know(volatile void *p, size_t n)
-{
-    memset((void *) p, false_but_the_compiler_does_not_know, n);
-}
-
-/* Simulate an access to the given object, to avoid compiler optimizations
- * in code that prepares or consumes the object. */
-static void do_nothing_with_object(void *p)
-{
-    (void) p;
-}
-void(*volatile do_nothing_with_object_but_the_compiler_does_not_know)(void *) =
-    do_nothing_with_object;
-
-
-/****************************************************************/
-/* Test framework features */
-/****************************************************************/
-
-static void meta_test_fail(const char *name)
-{
-    (void) name;
-    mbedtls_test_fail("Forced test failure", __LINE__, __FILE__);
-}
-
-static void meta_test_not_equal(const char *name)
-{
-    int left = 20;
-    int right = 10;
-
-    (void) name;
-
-    TEST_EQUAL(left, right);
-exit:
-    ;
-}
-
-static void meta_test_not_le_s(const char *name)
-{
-    int left = 20;
-    int right = 10;
-
-    (void) name;
-
-    TEST_LE_S(left, right);
-exit:
-    ;
-}
-
-static void meta_test_not_le_u(const char *name)
-{
-    size_t left = 20;
-    size_t right = 10;
-
-    (void) name;
-
-    TEST_LE_U(left, right);
-exit:
-    ;
-}
-
-/****************************************************************/
-/* Platform features */
-/****************************************************************/
-
-static void null_pointer_dereference(const char *name)
-{
-    (void) name;
-    volatile char *volatile p;
-    set_to_zero_but_the_compiler_does_not_know(&p, sizeof(p));
-    /* Undefined behavior (read from null data pointer) */
-    mbedtls_printf("%p -> %u\n", (void *) p, (unsigned) *p);
-}
-
-static void null_pointer_call(const char *name)
-{
-    (void) name;
-    unsigned(*volatile p)(void);
-    set_to_zero_but_the_compiler_does_not_know(&p, sizeof(p));
-    /* Undefined behavior (execute null function pointer) */
-    /* The pointer representation may be truncated, but we don't care:
-     * the only point of printing it is to have some use of the pointer
-     * to dissuade the compiler from optimizing it away. */
-    mbedtls_printf("%lx() -> %u\n", (unsigned long) (uintptr_t) p, p());
-}
-
-
-/****************************************************************/
-/* Memory */
-/****************************************************************/
-
-static void read_after_free(const char *name)
-{
-    (void) name;
-    volatile char *p = calloc_but_the_compiler_does_not_know(1, 1);
-    *p = 'a';
-    free_but_the_compiler_does_not_know((void *) p);
-    /* Undefined behavior (read after free) */
-    mbedtls_printf("%u\n", (unsigned) *p);
-}
-
-static void double_free(const char *name)
-{
-    (void) name;
-    volatile char *p = calloc_but_the_compiler_does_not_know(1, 1);
-    *p = 'a';
-    free_but_the_compiler_does_not_know((void *) p);
-    /* Undefined behavior (double free) */
-    free_but_the_compiler_does_not_know((void *) p);
-}
-
-static void read_uninitialized_stack(const char *name)
-{
-    (void) name;
-    char buf[1];
-    if (false_but_the_compiler_does_not_know) {
-        buf[0] = '!';
-    }
-    char *volatile p = buf;
-    if (*p != 0) {
-        /* Unspecified result (read from uninitialized memory) */
-        mbedtls_printf("%u\n", (unsigned) *p);
-    }
-}
-
-static void memory_leak(const char *name)
-{
-    (void) name;
-    volatile char *p = calloc_but_the_compiler_does_not_know(1, 1);
-    mbedtls_printf("%u\n", (unsigned) *p);
-    /* Leak of a heap object */
-}
-
-/* name = "test_memory_poison_%(start)_%(offset)_%(count)_%(direction)"
- * Poison a region starting at start from an 8-byte aligned origin,
- * encompassing count bytes. Access the region at offset from the start.
- * %(start), %(offset) and %(count) are decimal integers.
- * %(direction) is either the character 'r' for read or 'w' for write.
- */
-static void test_memory_poison(const char *name)
-{
-    size_t start = 0, offset = 0, count = 0;
-    char direction = 'r';
-    if (sscanf(name,
-               "%*[^0-9]%" MBEDTLS_PRINTF_SIZET
-               "%*[^0-9]%" MBEDTLS_PRINTF_SIZET
-               "%*[^0-9]%" MBEDTLS_PRINTF_SIZET
-               "_%c",
-               &start, &offset, &count, &direction) != 4) {
-        mbedtls_fprintf(stderr, "%s: Bad name format: %s\n", __func__, name);
-        return;
-    }
-
-    union {
-        long long ll;
-        unsigned char buf[32];
-    } aligned;
-    memset(aligned.buf, 'a', sizeof(aligned.buf));
-
-    if (start > sizeof(aligned.buf)) {
-        mbedtls_fprintf(stderr,
-                        "%s: start=%" MBEDTLS_PRINTF_SIZET
-                        " > size=%" MBEDTLS_PRINTF_SIZET,
-                        __func__, start, sizeof(aligned.buf));
-        return;
-    }
-    if (start + count > sizeof(aligned.buf)) {
-        mbedtls_fprintf(stderr,
-                        "%s: start+count=%" MBEDTLS_PRINTF_SIZET
-                        " > size=%" MBEDTLS_PRINTF_SIZET,
-                        __func__, start + count, sizeof(aligned.buf));
-        return;
-    }
-    if (offset >= count) {
-        mbedtls_fprintf(stderr,
-                        "%s: offset=%" MBEDTLS_PRINTF_SIZET
-                        " >= count=%" MBEDTLS_PRINTF_SIZET,
-                        __func__, offset, count);
-        return;
-    }
-
-    MBEDTLS_TEST_MEMORY_POISON(aligned.buf + start, count);
-
-    if (direction == 'w') {
-        aligned.buf[start + offset] = 'b';
-        do_nothing_with_object_but_the_compiler_does_not_know(aligned.buf);
-    } else {
-        do_nothing_with_object_but_the_compiler_does_not_know(aligned.buf);
-        mbedtls_printf("%u\n", (unsigned) aligned.buf[start + offset]);
-    }
-}
-
-
-/****************************************************************/
-/* Threading */
-/****************************************************************/
-
-static void mutex_lock_not_initialized(const char *name)
-{
-    (void) name;
-#if defined(MBEDTLS_THREADING_C)
-    mbedtls_threading_mutex_t mutex;
-    memset(&mutex, 0, sizeof(mutex));
-    /* This mutex usage error is detected by our test framework's mutex usage
-     * verification framework. See framework/tests/src/threading_helpers.c. Other
-     * threading implementations (e.g. pthread without our instrumentation)
-     * might consider this normal usage. */
-    TEST_ASSERT(mbedtls_mutex_lock(&mutex) == 0);
-exit:
-    ;
-#endif
-}
-
-static void mutex_unlock_not_initialized(const char *name)
-{
-    (void) name;
-#if defined(MBEDTLS_THREADING_C)
-    mbedtls_threading_mutex_t mutex;
-    memset(&mutex, 0, sizeof(mutex));
-    /* This mutex usage error is detected by our test framework's mutex usage
-     * verification framework. See framework/tests/src/threading_helpers.c. Other
-     * threading implementations (e.g. pthread without our instrumentation)
-     * might consider this normal usage. */
-    TEST_ASSERT(mbedtls_mutex_unlock(&mutex) == 0);
-exit:
-    ;
-#endif
-}
-
-static void mutex_free_not_initialized(const char *name)
-{
-    (void) name;
-#if defined(MBEDTLS_THREADING_C)
-    mbedtls_threading_mutex_t mutex;
-    memset(&mutex, 0, sizeof(mutex));
-    /* This mutex usage error is detected by our test framework's mutex usage
-     * verification framework. See framework/tests/src/threading_helpers.c. Other
-     * threading implementations (e.g. pthread without our instrumentation)
-     * might consider this normal usage. */
-    mbedtls_mutex_free(&mutex);
-#endif
-}
-
-static void mutex_double_init(const char *name)
-{
-    (void) name;
-#if defined(MBEDTLS_THREADING_C)
-    mbedtls_threading_mutex_t mutex;
-    mbedtls_mutex_init(&mutex);
-    /* This mutex usage error is detected by our test framework's mutex usage
-     * verification framework. See framework/tests/src/threading_helpers.c. Other
-     * threading implementations (e.g. pthread without our instrumentation)
-     * might consider this normal usage. */
-    mbedtls_mutex_init(&mutex);
-    mbedtls_mutex_free(&mutex);
-#endif
-}
-
-static void mutex_double_free(const char *name)
-{
-    (void) name;
-#if defined(MBEDTLS_THREADING_C)
-    mbedtls_threading_mutex_t mutex;
-    mbedtls_mutex_init(&mutex);
-    mbedtls_mutex_free(&mutex);
-    /* This mutex usage error is detected by our test framework's mutex usage
-     * verification framework. See framework/tests/src/threading_helpers.c. Other
-     * threading implementations (e.g. pthread without our instrumentation)
-     * might consider this normal usage. */
-    mbedtls_mutex_free(&mutex);
-#endif
-}
-
-static void mutex_leak(const char *name)
-{
-    (void) name;
-#if defined(MBEDTLS_THREADING_C)
-    mbedtls_threading_mutex_t mutex;
-    mbedtls_mutex_init(&mutex);
-#endif
-    /* This mutex usage error is detected by our test framework's mutex usage
-     * verification framework. See framework/tests/src/threading_helpers.c. Other
-     * threading implementations (e.g. pthread without our instrumentation)
-     * might consider this normal usage. */
-}
-
-
-/****************************************************************/
-/* Command line entry point */
-/****************************************************************/
-
-typedef struct {
-    /** Command line argument that will trigger that metatest.
-     *
-     * Conventionally matches "[a-z0-9_]+". */
-    const char *name;
-
-    /** Platform under which that metatest is valid.
-     *
-     * - "any": should work anywhere.
-     * - "asan": triggers ASan (Address Sanitizer).
-     * - "msan": triggers MSan (Memory Sanitizer).
-     * - "pthread": requires MBEDTLS_THREADING_PTHREAD and MBEDTLS_TEST_HOOKS,
-     *   which enables MBEDTLS_TEST_MUTEX_USAGE internally in the test
-     *   framework (see framework/tests/src/threading_helpers.c).
-     */
-    const char *platform;
-
-    /** Function that performs the metatest.
-     *
-     * The function receives the name as an argument. This allows using the
-     * same function to perform multiple variants of a test based on the name.
-     *
-     * When executed on a conforming platform, the function is expected to
-     * either cause a test failure (mbedtls_test_fail()), or cause the
-     * program to abort in some way (e.g. by causing a segfault or by
-     * triggering a sanitizer).
-     *
-     * When executed on a non-conforming platform, the function may return
-     * normally or may have unpredictable behavior.
-     */
-    void (*entry_point)(const char *name);
-} metatest_t;
-
-/* The list of available meta-tests. Remember to register new functions here!
- *
- * Note that we always compile all the functions, so that `metatest --list`
- * will always list all the available meta-tests.
- *
- * See the documentation of metatest_t::platform for the meaning of
- * platform values.
- */
-metatest_t metatests[] = {
-    { "test_fail", "any", meta_test_fail },
-    { "test_not_equal", "any", meta_test_not_equal },
-    { "test_not_le_s", "any", meta_test_not_le_s },
-    { "test_not_le_u", "any", meta_test_not_le_u },
-    { "null_dereference", "any", null_pointer_dereference },
-    { "null_call", "any", null_pointer_call },
-    { "read_after_free", "asan", read_after_free },
-    { "double_free", "asan", double_free },
-    { "read_uninitialized_stack", "msan", read_uninitialized_stack },
-    { "memory_leak", "asan", memory_leak },
-    { "test_memory_poison_0_0_8_r", "poison", test_memory_poison },
-    { "test_memory_poison_0_0_8_w", "poison", test_memory_poison },
-    { "test_memory_poison_0_7_8_r", "poison", test_memory_poison },
-    { "test_memory_poison_0_7_8_w", "poison", test_memory_poison },
-    { "test_memory_poison_0_0_1_r", "poison", test_memory_poison },
-    { "test_memory_poison_0_0_1_w", "poison", test_memory_poison },
-    { "test_memory_poison_0_1_2_r", "poison", test_memory_poison },
-    { "test_memory_poison_0_1_2_w", "poison", test_memory_poison },
-    { "test_memory_poison_7_0_8_r", "poison", test_memory_poison },
-    { "test_memory_poison_7_0_8_w", "poison", test_memory_poison },
-    { "test_memory_poison_7_7_8_r", "poison", test_memory_poison },
-    { "test_memory_poison_7_7_8_w", "poison", test_memory_poison },
-    { "test_memory_poison_7_0_1_r", "poison", test_memory_poison },
-    { "test_memory_poison_7_0_1_w", "poison", test_memory_poison },
-    { "test_memory_poison_7_1_2_r", "poison", test_memory_poison },
-    { "test_memory_poison_7_1_2_w", "poison", test_memory_poison },
-    { "mutex_lock_not_initialized", "pthread", mutex_lock_not_initialized },
-    { "mutex_unlock_not_initialized", "pthread", mutex_unlock_not_initialized },
-    { "mutex_free_not_initialized", "pthread", mutex_free_not_initialized },
-    { "mutex_double_init", "pthread", mutex_double_init },
-    { "mutex_double_free", "pthread", mutex_double_free },
-    { "mutex_leak", "pthread", mutex_leak },
-    { NULL, NULL, NULL }
-};
-
-static void help(FILE *out, const char *argv0)
-{
-    mbedtls_fprintf(out, "Usage: %s list|TEST\n", argv0);
-    mbedtls_fprintf(out, "Run a meta-test that should cause a test failure.\n");
-    mbedtls_fprintf(out, "With 'list', list the available tests and their platform requirement.\n");
-}
-
-int main(int argc, char *argv[])
-{
-    const char *argv0 = argc > 0 ? argv[0] : "metatest";
-    if (argc != 2) {
-        help(stderr, argv0);
-        mbedtls_exit(MBEDTLS_EXIT_FAILURE);
-    }
-
-    /* Support "-help", "--help", "--list", etc. */
-    const char *command = argv[1];
-    while (*command == '-') {
-        ++command;
-    }
-
-    if (strcmp(argv[1], "help") == 0) {
-        help(stdout, argv0);
-        mbedtls_exit(MBEDTLS_EXIT_SUCCESS);
-    }
-    if (strcmp(argv[1], "list") == 0) {
-        for (const metatest_t *p = metatests; p->name != NULL; p++) {
-            mbedtls_printf("%s %s\n", p->name, p->platform);
-        }
-        mbedtls_exit(MBEDTLS_EXIT_SUCCESS);
-    }
-
-#if defined(MBEDTLS_TEST_MUTEX_USAGE)
-    mbedtls_test_mutex_usage_init();
-#endif
-
-    for (const metatest_t *p = metatests; p->name != NULL; p++) {
-        if (strcmp(argv[1], p->name) == 0) {
-            mbedtls_printf("Running metatest %s...\n", argv[1]);
-            p->entry_point(argv[1]);
-#if defined(MBEDTLS_TEST_MUTEX_USAGE)
-            mbedtls_test_mutex_usage_check();
-#endif
-            int result = (int) mbedtls_test_get_result();
-
-            mbedtls_printf("Running metatest %s... done, result=%d\n",
-                           argv[1], result);
-            mbedtls_exit(result == MBEDTLS_TEST_RESULT_SUCCESS ?
-                         MBEDTLS_EXIT_SUCCESS :
-                         MBEDTLS_EXIT_FAILURE);
-        }
-    }
-
-    mbedtls_fprintf(stderr, "%s: FATAL: No such metatest: %s\n",
-                    argv0, command);
-    mbedtls_exit(MBEDTLS_EXIT_FAILURE);
-}
diff --git a/programs/test/query_compile_time_config.c b/programs/test/query_compile_time_config.c
deleted file mode 100644
index a70e6da..0000000
--- a/programs/test/query_compile_time_config.c
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- *  Query the Mbed TLS compile time configuration
- *
- *  Copyright The Mbed TLS Contributors
- *  SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
- */
-
-#include "mbedtls/build_info.h"
-
-#include "mbedtls/platform.h"
-
-#define USAGE                                                                   \
-    "usage: %s [ -all | -any | -l ] <MBEDTLS_CONFIG> ...\n\n"                   \
-    "This program takes command line arguments which correspond to\n"           \
-    "the string representation of Mbed TLS compile time configurations.\n\n"    \
-    "If \"--all\" and \"--any\" are not used, then, if all given arguments\n"   \
-    "are defined in the Mbed TLS build, 0 is returned; otherwise 1 is\n"        \
-    "returned. Macro expansions of configurations will be printed (if any).\n"                                 \
-    "-l\tPrint all available configuration.\n"                                  \
-    "-all\tReturn 0 if all configurations are defined. Otherwise, return 1\n"   \
-    "-any\tReturn 0 if any configuration is defined. Otherwise, return 1\n"     \
-    "-h\tPrint this usage\n"
-
-#include <string.h>
-#include "query_config.h"
-
-int main(int argc, char *argv[])
-{
-    int i;
-
-    if (argc < 2 || strcmp(argv[1], "-h") == 0) {
-        mbedtls_printf(USAGE, argv[0]);
-        return MBEDTLS_EXIT_FAILURE;
-    }
-
-    if (strcmp(argv[1], "-l") == 0) {
-        list_config();
-        return 0;
-    }
-
-    if (strcmp(argv[1], "-all") == 0) {
-        for (i = 2; i < argc; i++) {
-            if (query_config(argv[i]) != 0) {
-                return 1;
-            }
-        }
-        return 0;
-    }
-
-    if (strcmp(argv[1], "-any") == 0) {
-        for (i = 2; i < argc; i++) {
-            if (query_config(argv[i]) == 0) {
-                return 0;
-            }
-        }
-        return 1;
-    }
-
-    for (i = 1; i < argc; i++) {
-        if (query_config(argv[i]) != 0) {
-            return 1;
-        }
-    }
-
-    return 0;
-}
diff --git a/programs/test/query_config.h b/programs/test/query_config.h
deleted file mode 100644
index 43f120b..0000000
--- a/programs/test/query_config.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- *  Query Mbed TLS compile time configurations from mbedtls_config.h
- *
- *  Copyright The Mbed TLS Contributors
- *  SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
- */
-
-#ifndef MBEDTLS_PROGRAMS_TEST_QUERY_CONFIG_H
-#define MBEDTLS_PROGRAMS_TEST_QUERY_CONFIG_H
-
-#include "mbedtls/build_info.h"
-
-/** Check whether a given configuration symbol is enabled.
- *
- * \param config    The symbol to query (e.g. "MBEDTLS_RSA_C").
- * \return          \c 0 if the symbol was defined at compile time
- *                  (in MBEDTLS_CONFIG_FILE or mbedtls_config.h),
- *                  \c 1 otherwise.
- *
- * \note            This function is defined in `programs/test/query_config.c`
- *                  which is automatically generated by
- *                  `scripts/generate_query_config.pl`.
- */
-int query_config(const char *config);
-
-/** List all enabled configuration symbols
- *
- * \note            This function is defined in `programs/test/query_config.c`
- *                  which is automatically generated by
- *                  `scripts/generate_query_config.pl`.
- */
-void list_config(void);
-
-#endif /* MBEDTLS_PROGRAMS_TEST_QUERY_CONFIG_H */
diff --git a/programs/test/query_included_headers.c b/programs/test/query_included_headers.c
deleted file mode 100644
index cdafa16..0000000
--- a/programs/test/query_included_headers.c
+++ /dev/null
@@ -1,29 +0,0 @@
-/* Ad hoc report on included headers. */
-/*
- *  Copyright The Mbed TLS Contributors
- *  SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
- */
-
-#include <psa/crypto.h>
-#include <mbedtls/platform.h>
-
-int main(void)
-{
-
-    /* Which PSA platform header? */
-#if defined(PSA_CRYPTO_PLATFORM_H)
-    mbedtls_printf("PSA_CRYPTO_PLATFORM_H\n");
-#endif
-#if defined(PSA_CRYPTO_PLATFORM_ALT_H)
-    mbedtls_printf("PSA_CRYPTO_PLATFORM_ALT_H\n");
-#endif
-
-    /* Which PSA struct header? */
-#if defined(PSA_CRYPTO_STRUCT_H)
-    mbedtls_printf("PSA_CRYPTO_STRUCT_H\n");
-#endif
-#if defined(PSA_CRYPTO_STRUCT_ALT_H)
-    mbedtls_printf("PSA_CRYPTO_STRUCT_ALT_H\n");
-#endif
-
-}
diff --git a/programs/test/zeroize.c b/programs/test/zeroize.c
deleted file mode 100644
index c1cee0d..0000000
--- a/programs/test/zeroize.c
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Zeroize application for debugger-driven testing
- *
- * This is a simple test application used for debugger-driven testing to check
- * whether calls to mbedtls_platform_zeroize() are being eliminated by compiler
- * optimizations. This application is used by the GDB script at
- * tests/scripts/test_zeroize.gdb: the script sets a breakpoint at the last
- * return statement in the main() function of this program. The debugger
- * facilities are then used to manually inspect the memory and verify that the
- * call to mbedtls_platform_zeroize() was not eliminated.
- *
- *  Copyright The Mbed TLS Contributors
- *  SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
- */
-
-#include "mbedtls/build_info.h"
-
-#include <stdio.h>
-
-#include "mbedtls/platform.h"
-
-#include "mbedtls/platform_util.h"
-
-#define BUFFER_LEN 1024
-
-static void usage(void)
-{
-    mbedtls_printf("Zeroize is a simple program to assist with testing\n");
-    mbedtls_printf("the mbedtls_platform_zeroize() function by using the\n");
-    mbedtls_printf("debugger. This program takes a file as input and\n");
-    mbedtls_printf("prints the first %d characters. Usage:\n\n", BUFFER_LEN);
-    mbedtls_printf("       zeroize <FILE>\n");
-}
-
-int main(int argc, char **argv)
-{
-    int exit_code = MBEDTLS_EXIT_FAILURE;
-    FILE *fp;
-    char buf[BUFFER_LEN];
-    char *p = buf;
-    char *end = p + BUFFER_LEN;
-    int c;
-
-    if (argc != 2) {
-        mbedtls_printf("This program takes exactly 1 argument\n");
-        usage();
-        mbedtls_exit(exit_code);
-    }
-
-    fp = fopen(argv[1], "r");
-    if (fp == NULL) {
-        mbedtls_printf("Could not open file '%s'\n", argv[1]);
-        mbedtls_exit(exit_code);
-    }
-
-    while ((c = fgetc(fp)) != EOF && p < end - 1) {
-        *p++ = (char) c;
-    }
-    *p = '\0';
-
-    if (p - buf != 0) {
-        mbedtls_printf("%s\n", buf);
-        exit_code = MBEDTLS_EXIT_SUCCESS;
-    } else {
-        mbedtls_printf("The file is empty!\n");
-    }
-
-    fclose(fp);
-    mbedtls_platform_zeroize(buf, sizeof(buf));
-
-    mbedtls_exit(exit_code);   // GDB_BREAK_HERE -- don't remove this comment!
-}
diff --git a/scripts/apidoc_full.sh b/scripts/apidoc_full.sh
deleted file mode 100755
index 34daf37..0000000
--- a/scripts/apidoc_full.sh
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/sh
-
-# Generate doxygen documentation with a full mbedtls_config.h (this ensures that every
-# available flag is documented, and avoids warnings about documentation
-# without a corresponding #define).
-#
-# /!\ This must not be a Makefile target, as it would create a race condition
-# when multiple targets are invoked in the same parallel build.
-#
-# Copyright The Mbed TLS Contributors
-# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
-
-set -eu
-
-CONFIG_H='include/mbedtls/mbedtls_config.h'
-
-if [ -r $CONFIG_H ]; then :; else
-    echo "$CONFIG_H not found" >&2
-    exit 1
-fi
-
-CONFIG_BAK=${CONFIG_H}.bak
-cp -p $CONFIG_H $CONFIG_BAK
-
-scripts/config.py realfull
-make apidoc
-
-mv $CONFIG_BAK $CONFIG_H
diff --git a/scripts/assemble_changelog.py b/scripts/assemble_changelog.py
deleted file mode 100755
index 07e6fc5..0000000
--- a/scripts/assemble_changelog.py
+++ /dev/null
@@ -1,534 +0,0 @@
-#!/usr/bin/env python3
-
-"""Assemble Mbed TLS change log entries into the change log file.
-
-Add changelog entries to the first level-2 section.
-Create a new level-2 section for unreleased changes if needed.
-Remove the input files unless --keep-entries is specified.
-
-In each level-3 section, entries are sorted in chronological order
-(oldest first). From oldest to newest:
-* Merged entry files are sorted according to their merge date (date of
-  the merge commit that brought the commit that created the file into
-  the target branch).
-* Committed but unmerged entry files are sorted according to the date
-  of the commit that adds them.
-* Uncommitted entry files are sorted according to their modification time.
-
-You must run this program from within a git working directory.
-"""
-
-# Copyright The Mbed TLS Contributors
-# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
-
-import argparse
-from collections import OrderedDict, namedtuple
-import datetime
-import functools
-import glob
-import os
-import re
-import subprocess
-import sys
-
-class InputFormatError(Exception):
-    def __init__(self, filename, line_number, message, *args, **kwargs):
-        message = '{}:{}: {}'.format(filename, line_number,
-                                     message.format(*args, **kwargs))
-        super().__init__(message)
-
-class CategoryParseError(Exception):
-    def __init__(self, line_offset, error_message):
-        self.line_offset = line_offset
-        self.error_message = error_message
-        super().__init__('{}: {}'.format(line_offset, error_message))
-
-class LostContent(Exception):
-    def __init__(self, filename, line):
-        message = ('Lost content from {}: "{}"'.format(filename, line))
-        super().__init__(message)
-
-class FilePathError(Exception):
-    def __init__(self, filenames):
-        message = ('Changelog filenames do not end with .txt: {}'.format(", ".join(filenames)))
-        super().__init__(message)
-
-# The category names we use in the changelog.
-# If you edit this, update ChangeLog.d/README.md.
-STANDARD_CATEGORIES = (
-    'API changes',
-    'Default behavior changes',
-    'Requirement changes',
-    'New deprecations',
-    'Removals',
-    'Features',
-    'Security',
-    'Bugfix',
-    'Changes',
-)
-
-# The maximum line length for an entry
-MAX_LINE_LENGTH = 80
-
-CategoryContent = namedtuple('CategoryContent', [
-    'name', 'title_line', # Title text and line number of the title
-    'body', 'body_line', # Body text and starting line number of the body
-])
-
-class ChangelogFormat:
-    """Virtual class documenting how to write a changelog format class."""
-
-    @classmethod
-    def extract_top_version(cls, changelog_file_content):
-        """Split out the top version section.
-
-        If the top version is already released, create a new top
-        version section for an unreleased version.
-
-        Return ``(header, top_version_title, top_version_body, trailer)``
-        where the "top version" is the existing top version section if it's
-        for unreleased changes, and a newly created section otherwise.
-        To assemble the changelog after modifying top_version_body,
-        concatenate the four pieces.
-        """
-        raise NotImplementedError
-
-    @classmethod
-    def version_title_text(cls, version_title):
-        """Return the text of a formatted version section title."""
-        raise NotImplementedError
-
-    @classmethod
-    def split_categories(cls, version_body):
-        """Split a changelog version section body into categories.
-
-        Return a list of `CategoryContent` the name is category title
-        without any formatting.
-        """
-        raise NotImplementedError
-
-    @classmethod
-    def format_category(cls, title, body):
-        """Construct the text of a category section from its title and body."""
-        raise NotImplementedError
-
-class TextChangelogFormat(ChangelogFormat):
-    """The traditional Mbed TLS changelog format."""
-
-    _unreleased_version_text = '= {} x.x.x branch released xxxx-xx-xx'
-    @classmethod
-    def is_released_version(cls, title):
-        # Look for an incomplete release date
-        return not re.search(r'[0-9x]{4}-[0-9x]{2}-[0-9x]?x', title)
-
-    _top_version_re = re.compile(r'(?:\A|\n)(=[^\n]*\n+)(.*?\n)(?:=|$)',
-                                 re.DOTALL)
-    _name_re = re.compile(r'=\s(.*)\s[0-9x]+\.', re.DOTALL)
-    @classmethod
-    def extract_top_version(cls, changelog_file_content):
-        """A version section starts with a line starting with '='."""
-        m = re.search(cls._top_version_re, changelog_file_content)
-        top_version_start = m.start(1)
-        top_version_end = m.end(2)
-        top_version_title = m.group(1)
-        top_version_body = m.group(2)
-        name = re.match(cls._name_re, top_version_title).group(1)
-        if cls.is_released_version(top_version_title):
-            top_version_end = top_version_start
-            top_version_title = cls._unreleased_version_text.format(name) + '\n\n'
-            top_version_body = ''
-        return (changelog_file_content[:top_version_start],
-                top_version_title, top_version_body,
-                changelog_file_content[top_version_end:])
-
-    @classmethod
-    def version_title_text(cls, version_title):
-        return re.sub(r'\n.*', version_title, re.DOTALL)
-
-    _category_title_re = re.compile(r'(^\w.*)\n+', re.MULTILINE)
-    @classmethod
-    def split_categories(cls, version_body):
-        """A category title is a line with the title in column 0."""
-        if not version_body:
-            return []
-        title_matches = list(re.finditer(cls._category_title_re, version_body))
-        if not title_matches or title_matches[0].start() != 0:
-            # There is junk before the first category.
-            raise CategoryParseError(0, 'Junk found where category expected')
-        title_starts = [m.start(1) for m in title_matches]
-        body_starts = [m.end(0) for m in title_matches]
-        body_ends = title_starts[1:] + [len(version_body)]
-        bodies = [version_body[body_start:body_end].rstrip('\n') + '\n'
-                  for (body_start, body_end) in zip(body_starts, body_ends)]
-        title_lines = [version_body[:pos].count('\n') for pos in title_starts]
-        body_lines = [version_body[:pos].count('\n') for pos in body_starts]
-        return [CategoryContent(title_match.group(1), title_line,
-                                body, body_line)
-                for title_match, title_line, body, body_line
-                in zip(title_matches, title_lines, bodies, body_lines)]
-
-    @classmethod
-    def format_category(cls, title, body):
-        # `split_categories` ensures that each body ends with a newline.
-        # Make sure that there is additionally a blank line between categories.
-        if not body.endswith('\n\n'):
-            body += '\n'
-        return title + '\n' + body
-
-class ChangeLog:
-    """An Mbed TLS changelog.
-
-    A changelog file consists of some header text followed by one or
-    more version sections. The version sections are in reverse
-    chronological order. Each version section consists of a title and a body.
-
-    The body of a version section consists of zero or more category
-    subsections. Each category subsection consists of a title and a body.
-
-    A changelog entry file has the same format as the body of a version section.
-
-    A `ChangelogFormat` object defines the concrete syntax of the changelog.
-    Entry files must have the same format as the changelog file.
-    """
-
-    # Only accept dotted version numbers (e.g. "3.1", not "3").
-    # Refuse ".x" in a version number where x is a letter: this indicates
-    # a version that is not yet released. Something like "3.1a" is accepted.
-    _version_number_re = re.compile(r'[0-9]+\.[0-9A-Za-z.]+')
-    _incomplete_version_number_re = re.compile(r'.*\.[A-Za-z]')
-    _only_url_re = re.compile(r'^\s*\w+://\S+\s*$')
-    _has_url_re = re.compile(r'.*://.*')
-
-    def add_categories_from_text(self, filename, line_offset,
-                                 text, allow_unknown_category):
-        """Parse a version section or entry file."""
-        try:
-            categories = self.format.split_categories(text)
-        except CategoryParseError as e:
-            raise InputFormatError(filename, line_offset + e.line_offset,
-                                   e.error_message)
-        for category in categories:
-            if not allow_unknown_category and \
-               category.name not in self.categories:
-                raise InputFormatError(filename,
-                                       line_offset + category.title_line,
-                                       'Unknown category: "{}"',
-                                       category.name)
-
-            body_split = category.body.splitlines()
-
-            for line_number, line in enumerate(body_split, 1):
-                if not self._only_url_re.match(line) and \
-                   len(line) > MAX_LINE_LENGTH:
-                    long_url_msg = '. URL exceeding length limit must be alone in its line.' \
-                        if self._has_url_re.match(line) else ""
-                    raise InputFormatError(filename,
-                                           category.body_line + line_number,
-                                           'Line is longer than allowed: '
-                                           'Length {} (Max {}){}',
-                                           len(line), MAX_LINE_LENGTH,
-                                           long_url_msg)
-
-            self.categories[category.name] += category.body
-
-    def __init__(self, input_stream, changelog_format):
-        """Create a changelog object.
-
-        Populate the changelog object from the content of the file
-        input_stream.
-        """
-        self.format = changelog_format
-        whole_file = input_stream.read()
-        (self.header,
-         self.top_version_title, top_version_body,
-         self.trailer) = self.format.extract_top_version(whole_file)
-        # Split the top version section into categories.
-        self.categories = OrderedDict()
-        for category in STANDARD_CATEGORIES:
-            self.categories[category] = ''
-        offset = (self.header + self.top_version_title).count('\n') + 1
-
-        self.add_categories_from_text(input_stream.name, offset,
-                                      top_version_body, True)
-
-    def add_file(self, input_stream):
-        """Add changelog entries from a file.
-        """
-        self.add_categories_from_text(input_stream.name, 1,
-                                      input_stream.read(), False)
-
-    def write(self, filename):
-        """Write the changelog to the specified file.
-        """
-        with open(filename, 'w', encoding='utf-8') as out:
-            out.write(self.header)
-            out.write(self.top_version_title)
-            for title, body in self.categories.items():
-                if not body:
-                    continue
-                out.write(self.format.format_category(title, body))
-            out.write(self.trailer)
-
-
-@functools.total_ordering
-class EntryFileSortKey:
-    """This classes defines an ordering on changelog entry files: older < newer.
-
-    * Merged entry files are sorted according to their merge date (date of
-      the merge commit that brought the commit that created the file into
-      the target branch).
-    * Committed but unmerged entry files are sorted according to the date
-      of the commit that adds them.
-    * Uncommitted entry files are sorted according to their modification time.
-
-    This class assumes that the file is in a git working directory with
-    the target branch checked out.
-    """
-
-    # Categories of files. A lower number is considered older.
-    MERGED = 0
-    COMMITTED = 1
-    LOCAL = 2
-
-    @staticmethod
-    def creation_hash(filename):
-        """Return the git commit id at which the given file was created.
-
-        Return None if the file was never checked into git.
-        """
-        hashes = subprocess.check_output(['git', 'log', '--format=%H',
-                                          '--follow',
-                                          '--', filename])
-        m = re.search('(.+)$', hashes.decode('ascii'))
-        if not m:
-            # The git output is empty. This means that the file was
-            # never checked in.
-            return None
-        # The last commit in the log is the oldest one, which is when the
-        # file was created.
-        return m.group(0)
-
-    @staticmethod
-    def list_merges(some_hash, target, *options):
-        """List merge commits from some_hash to target.
-
-        Pass options to git to select which commits are included.
-        """
-        text = subprocess.check_output(['git', 'rev-list',
-                                        '--merges', *options,
-                                        '..'.join([some_hash, target])])
-        return text.decode('ascii').rstrip('\n').split('\n')
-
-    @classmethod
-    def merge_hash(cls, some_hash):
-        """Return the git commit id at which the given commit was merged.
-
-        Return None if the given commit was never merged.
-        """
-        target = 'HEAD'
-        # List the merges from some_hash to the target in two ways.
-        # The ancestry list is the ones that are both descendants of
-        # some_hash and ancestors of the target.
-        ancestry = frozenset(cls.list_merges(some_hash, target,
-                                             '--ancestry-path'))
-        # The first_parents list only contains merges that are directly
-        # on the target branch. We want it in reverse order (oldest first).
-        first_parents = cls.list_merges(some_hash, target,
-                                        '--first-parent', '--reverse')
-        # Look for the oldest merge commit that's both on the direct path
-        # and directly on the target branch. That's the place where some_hash
-        # was merged on the target branch. See
-        # https://stackoverflow.com/questions/8475448/find-merge-commit-which-include-a-specific-commit
-        for commit in first_parents:
-            if commit in ancestry:
-                return commit
-        return None
-
-    @staticmethod
-    def commit_timestamp(commit_id):
-        """Return the timestamp of the given commit."""
-        text = subprocess.check_output(['git', 'show', '-s',
-                                        '--format=%ct',
-                                        commit_id])
-        return datetime.datetime.utcfromtimestamp(int(text))
-
-    @staticmethod
-    def file_timestamp(filename):
-        """Return the modification timestamp of the given file."""
-        mtime = os.stat(filename).st_mtime
-        return datetime.datetime.fromtimestamp(mtime)
-
-    def __init__(self, filename):
-        """Determine position of the file in the changelog entry order.
-
-        This constructor returns an object that can be used with comparison
-        operators, with `sort` and `sorted`, etc. Older entries are sorted
-        before newer entries.
-        """
-        self.filename = filename
-        creation_hash = self.creation_hash(filename)
-        if not creation_hash:
-            self.category = self.LOCAL
-            self.datetime = self.file_timestamp(filename)
-            return
-        merge_hash = self.merge_hash(creation_hash)
-        if not merge_hash:
-            self.category = self.COMMITTED
-            self.datetime = self.commit_timestamp(creation_hash)
-            return
-        self.category = self.MERGED
-        self.datetime = self.commit_timestamp(merge_hash)
-
-    def sort_key(self):
-        """"Return a concrete sort key for this entry file sort key object.
-
-        ``ts1 < ts2`` is implemented as ``ts1.sort_key() < ts2.sort_key()``.
-        """
-        return (self.category, self.datetime, self.filename)
-
-    def __eq__(self, other):
-        return self.sort_key() == other.sort_key()
-
-    def __lt__(self, other):
-        return self.sort_key() < other.sort_key()
-
-
-def check_output(generated_output_file, main_input_file, merged_files):
-    """Make sanity checks on the generated output.
-
-    The intent of these sanity checks is to have reasonable confidence
-    that no content has been lost.
-
-    The sanity check is that every line that is present in an input file
-    is also present in an output file. This is not perfect but good enough
-    for now.
-    """
-    with open(generated_output_file, 'r', encoding='utf-8') as fd:
-        generated_output = set(fd)
-        for line in open(main_input_file, 'r', encoding='utf-8'):
-            if line not in generated_output:
-                raise LostContent('original file', line)
-        for merged_file in merged_files:
-            for line in open(merged_file, 'r', encoding='utf-8'):
-                if line not in generated_output:
-                    raise LostContent(merged_file, line)
-
-def finish_output(changelog, output_file, input_file, merged_files):
-    """Write the changelog to the output file.
-
-    The input file and the list of merged files are used only for sanity
-    checks on the output.
-    """
-    if os.path.exists(output_file) and not os.path.isfile(output_file):
-        # The output is a non-regular file (e.g. pipe). Write to it directly.
-        output_temp = output_file
-    else:
-        # The output is a regular file. Write to a temporary file,
-        # then move it into place atomically.
-        output_temp = output_file + '.tmp'
-    changelog.write(output_temp)
-    check_output(output_temp, input_file, merged_files)
-    if output_temp != output_file:
-        os.rename(output_temp, output_file)
-
-def remove_merged_entries(files_to_remove):
-    for filename in files_to_remove:
-        os.remove(filename)
-
-def list_files_to_merge(options):
-    """List the entry files to merge, oldest first.
-
-    "Oldest" is defined by `EntryFileSortKey`.
-
-    Also check for required .txt extension
-    """
-    files_to_merge = glob.glob(os.path.join(options.dir, '*'))
-
-    # Ignore 00README.md
-    readme = os.path.join(options.dir, "00README.md")
-    if readme in files_to_merge:
-        files_to_merge.remove(readme)
-
-    # Identify files without the required .txt extension
-    bad_files = [x for x in files_to_merge if not x.endswith(".txt")]
-    if bad_files:
-        raise FilePathError(bad_files)
-
-    files_to_merge.sort(key=EntryFileSortKey)
-    return files_to_merge
-
-def merge_entries(options):
-    """Merge changelog entries into the changelog file.
-
-    Read the changelog file from options.input.
-    Check that all entries have a .txt extension
-    Read entries to merge from the directory options.dir.
-    Write the new changelog to options.output.
-    Remove the merged entries if options.keep_entries is false.
-    """
-    with open(options.input, 'r', encoding='utf-8') as input_file:
-        changelog = ChangeLog(input_file, TextChangelogFormat)
-    files_to_merge = list_files_to_merge(options)
-    if not files_to_merge:
-        sys.stderr.write('There are no pending changelog entries.\n')
-        return
-    for filename in files_to_merge:
-        with open(filename, 'r', encoding='utf-8') as input_file:
-            changelog.add_file(input_file)
-    finish_output(changelog, options.output, options.input, files_to_merge)
-    if not options.keep_entries:
-        remove_merged_entries(files_to_merge)
-
-def show_file_timestamps(options):
-    """List the files to merge and their timestamp.
-
-    This is only intended for debugging purposes.
-    """
-    files = list_files_to_merge(options)
-    for filename in files:
-        ts = EntryFileSortKey(filename)
-        print(ts.category, ts.datetime, filename)
-
-def set_defaults(options):
-    """Add default values for missing options."""
-    output_file = getattr(options, 'output', None)
-    if output_file is None:
-        options.output = options.input
-    if getattr(options, 'keep_entries', None) is None:
-        options.keep_entries = (output_file is not None)
-
-def main():
-    """Command line entry point."""
-    parser = argparse.ArgumentParser(description=__doc__)
-    parser.add_argument('--dir', '-d', metavar='DIR',
-                        default='ChangeLog.d',
-                        help='Directory to read entries from'
-                             ' (default: ChangeLog.d)')
-    parser.add_argument('--input', '-i', metavar='FILE',
-                        default='ChangeLog',
-                        help='Existing changelog file to read from and augment'
-                             ' (default: ChangeLog)')
-    parser.add_argument('--keep-entries',
-                        action='store_true', dest='keep_entries', default=None,
-                        help='Keep the files containing entries'
-                             ' (default: remove them if --output/-o is not specified)')
-    parser.add_argument('--no-keep-entries',
-                        action='store_false', dest='keep_entries',
-                        help='Remove the files containing entries after they are merged'
-                             ' (default: remove them if --output/-o is not specified)')
-    parser.add_argument('--output', '-o', metavar='FILE',
-                        help='Output changelog file'
-                             ' (default: overwrite the input)')
-    parser.add_argument('--list-files-only',
-                        action='store_true',
-                        help=('Only list the files that would be processed '
-                              '(with some debugging information)'))
-    options = parser.parse_args()
-    set_defaults(options)
-    if options.list_files_only:
-        show_file_timestamps(options)
-        return
-    merge_entries(options)
-
-if __name__ == '__main__':
-    main()
diff --git a/scripts/code_style.py b/scripts/code_style.py
deleted file mode 100755
index e98fb2b..0000000
--- a/scripts/code_style.py
+++ /dev/null
@@ -1,277 +0,0 @@
-#!/usr/bin/env python3
-"""Check or fix the code style by running Uncrustify.
-
-This script must be run from the root of a Git work tree containing Mbed TLS.
-"""
-# Copyright The Mbed TLS Contributors
-# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
-import argparse
-import os
-import re
-import subprocess
-import sys
-from typing import FrozenSet, List, Optional
-
-UNCRUSTIFY_SUPPORTED_VERSION = "0.75.1"
-CONFIG_FILE = ".uncrustify.cfg"
-UNCRUSTIFY_EXE = "uncrustify"
-UNCRUSTIFY_ARGS = ["-c", CONFIG_FILE]
-CHECK_GENERATED_FILES = "tests/scripts/check-generated-files.sh"
-
-def print_err(*args):
-    print("Error: ", *args, file=sys.stderr)
-
-# Print the file names that will be skipped and the help message
-def print_skip(files_to_skip):
-    print()
-    print(*files_to_skip, sep=", SKIP\n", end=", SKIP\n")
-    print("Warning: The listed files will be skipped because\n"
-          "they are not known to git.")
-    print()
-
-# Match FILENAME(s) in "check SCRIPT (FILENAME...)"
-CHECK_CALL_RE = re.compile(r"\n\s*check\s+[^\s#$&*?;|]+([^\n#$&*?;|]+)",
-                           re.ASCII)
-def list_generated_files() -> FrozenSet[str]:
-    """Return the names of generated files.
-
-    We don't reformat generated files, since the result might be different
-    from the output of the generator. Ideally the result of the generator
-    would conform to the code style, but this would be difficult, especially
-    with respect to the placement of line breaks in long logical lines.
-    """
-    # Parse check-generated-files.sh to get an up-to-date list of
-    # generated files. Read the file rather than calling it so that
-    # this script only depends on Git, Python and uncrustify, and not other
-    # tools such as sh or grep which might not be available on Windows.
-    # This introduces a limitation: check-generated-files.sh must have
-    # the expected format and must list the files explicitly, not through
-    # wildcards or command substitution.
-    content = open(CHECK_GENERATED_FILES, encoding="utf-8").read()
-    checks = re.findall(CHECK_CALL_RE, content)
-    return frozenset(word for s in checks for word in s.split())
-
-# Check for comment string indicating an auto-generated file
-AUTOGEN_RE = re.compile(r"Warning[ :-]+This file is (now )?auto[ -]?generated",
-                        re.ASCII | re.IGNORECASE)
-def is_file_autogenerated(filename):
-    content = open(filename, encoding="utf-8").read()
-    return AUTOGEN_RE.search(content) is not None
-
-def get_src_files(since: Optional[str]) -> List[str]:
-    """
-    Use git to get a list of the source files.
-
-    The optional argument since is a commit, indicating to only list files
-    that have changed since that commit. Without this argument, list all
-    files known to git.
-
-    Only C files are included, and certain files (generated, or 3rdparty)
-    are excluded.
-    """
-    file_patterns = ["*.[hc]",
-                     "tests/suites/*.function",
-                     "scripts/data_files/*.fmt"]
-    output = subprocess.check_output(["git", "ls-files"] + file_patterns,
-                                     universal_newlines=True)
-    src_files = output.split()
-
-    # When this script is called from a git hook, some environment variables
-    # are set by default which force all git commands to use the main repository
-    # (i.e. prevent us from performing commands on the framework repo).
-    # Create an environment without these variables for running commands on the
-    # framework repo.
-    framework_env = os.environ.copy()
-    # Get a list of environment vars that git sets
-    git_env_vars = subprocess.check_output(["git", "rev-parse", "--local-env-vars"],
-                                           universal_newlines=True)
-    # Remove the vars from the environment
-    for var in git_env_vars.split():
-        framework_env.pop(var, None)
-
-    output = subprocess.check_output(["git", "-C", "framework", "ls-files"]
-                                     + file_patterns,
-                                     universal_newlines=True,
-                                     env=framework_env)
-    framework_src_files = output.split()
-
-    if since:
-        # get all files changed in commits since the starting point in ...
-        # ... the main repository
-        cmd = ["git", "log", since + "..HEAD", "--ignore-submodules",
-               "--name-only", "--pretty=", "--"] + src_files
-        output = subprocess.check_output(cmd, universal_newlines=True)
-        committed_changed_files = output.split()
-
-        # ... the framework submodule
-        framework_since = get_submodule_hash(since, "framework")
-        cmd = ["git", "-C", "framework", "log", framework_since + "..HEAD",
-               "--name-only", "--pretty=", "--"] + framework_src_files
-        output = subprocess.check_output(cmd, universal_newlines=True,
-                                         env=framework_env)
-        committed_changed_files += ["framework/" + s for s in output.split()]
-
-        # and also get all files with uncommitted changes in ...
-        # ... the main repository
-        cmd = ["git", "diff", "--name-only", "--"] + src_files
-        output = subprocess.check_output(cmd, universal_newlines=True)
-        uncommitted_changed_files = output.split()
-        # ... the framework submodule
-        cmd = ["git", "-C", "framework", "diff", "--name-only", "--"] + \
-              framework_src_files
-        output = subprocess.check_output(cmd, universal_newlines=True,
-                                         env=framework_env)
-        uncommitted_changed_files += ["framework/" + s for s in output.split()]
-
-        src_files = committed_changed_files + uncommitted_changed_files
-    else:
-        src_files += ["framework/" + s for s in framework_src_files]
-
-    generated_files = list_generated_files()
-    # Don't correct style for third-party files (and, for simplicity,
-    # companion files in the same subtree), or for automatically
-    # generated files (we're correcting the templates instead).
-    src_files = [filename for filename in src_files
-                 if not (filename.startswith("3rdparty/") or
-                         filename in generated_files or
-                         is_file_autogenerated(filename))]
-    return src_files
-
-def get_submodule_hash(commit: str, submodule: str) -> str:
-    """Get the commit hash of a submodule at a given commit in the Git repository."""
-    cmd = ["git", "ls-tree", commit, submodule]
-    output = subprocess.check_output(cmd, universal_newlines=True)
-    return output.split()[2]
-
-def get_uncrustify_version() -> str:
-    """
-    Get the version string from Uncrustify
-    """
-    result = subprocess.run([UNCRUSTIFY_EXE, "--version"],
-                            stdout=subprocess.PIPE, stderr=subprocess.PIPE,
-                            check=False)
-    if result.returncode != 0:
-        print_err("Could not get Uncrustify version:", str(result.stderr, "utf-8"))
-        return ""
-    else:
-        return str(result.stdout, "utf-8")
-
-def check_style_is_correct(src_file_list: List[str]) -> bool:
-    """
-    Check the code style and output a diff for each file whose style is
-    incorrect.
-    """
-    style_correct = True
-    for src_file in src_file_list:
-        uncrustify_cmd = [UNCRUSTIFY_EXE] + UNCRUSTIFY_ARGS + [src_file]
-        result = subprocess.run(uncrustify_cmd, stdout=subprocess.PIPE,
-                                stderr=subprocess.PIPE, check=False)
-        if result.returncode != 0:
-            print_err("Uncrustify returned " + str(result.returncode) +
-                      " correcting file " + src_file)
-            return False
-
-        # Uncrustify makes changes to the code and places the result in a new
-        # file with the extension ".uncrustify". To get the changes (if any)
-        # simply diff the 2 files.
-        diff_cmd = ["diff", "-u", src_file, src_file + ".uncrustify"]
-        cp = subprocess.run(diff_cmd, check=False)
-
-        if cp.returncode == 1:
-            print(src_file + " changed - code style is incorrect.")
-            style_correct = False
-        elif cp.returncode != 0:
-            raise subprocess.CalledProcessError(cp.returncode, cp.args,
-                                                cp.stdout, cp.stderr)
-
-        # Tidy up artifact
-        os.remove(src_file + ".uncrustify")
-
-    return style_correct
-
-def fix_style_single_pass(src_file_list: List[str]) -> bool:
-    """
-    Run Uncrustify once over the source files.
-    """
-    code_change_args = UNCRUSTIFY_ARGS + ["--no-backup"]
-    for src_file in src_file_list:
-        uncrustify_cmd = [UNCRUSTIFY_EXE] + code_change_args + [src_file]
-        result = subprocess.run(uncrustify_cmd, check=False)
-        if result.returncode != 0:
-            print_err("Uncrustify with file returned: " +
-                      str(result.returncode) + " correcting file " +
-                      src_file)
-            return False
-    return True
-
-def fix_style(src_file_list: List[str]) -> int:
-    """
-    Fix the code style. This takes 2 passes of Uncrustify.
-    """
-    if not fix_style_single_pass(src_file_list):
-        return 1
-    if not fix_style_single_pass(src_file_list):
-        return 1
-
-    # Guard against future changes that cause the codebase to require
-    # more passes.
-    if not check_style_is_correct(src_file_list):
-        print_err("Code style still incorrect after second run of Uncrustify.")
-        return 1
-    else:
-        return 0
-
-def main() -> int:
-    """
-    Main with command line arguments.
-    """
-    uncrustify_version = get_uncrustify_version().strip()
-    if UNCRUSTIFY_SUPPORTED_VERSION not in uncrustify_version:
-        print("Warning: Using unsupported Uncrustify version '" +
-              uncrustify_version + "'")
-        print("Note: The only supported version is " +
-              UNCRUSTIFY_SUPPORTED_VERSION)
-
-    parser = argparse.ArgumentParser()
-    parser.add_argument('-f', '--fix', action='store_true',
-                        help=('modify source files to fix the code style '
-                              '(default: print diff, do not modify files)'))
-    parser.add_argument('-s', '--since', metavar='COMMIT', const='development', nargs='?',
-                        help=('only check files modified since the specified commit'
-                              ' (e.g. --since=HEAD~3 or --since=development). If no'
-                              ' commit is specified, default to development.'))
-    # --subset is almost useless: it only matters if there are no files
-    # ('code_style.py' without arguments checks all files known to Git,
-    # 'code_style.py --subset' does nothing). In particular,
-    # 'code_style.py --fix --subset ...' is intended as a stable ("porcelain")
-    # way to restyle a possibly empty set of files.
-    parser.add_argument('--subset', action='store_true',
-                        help='only check the specified files (default with non-option arguments)')
-    parser.add_argument('operands', nargs='*', metavar='FILE',
-                        help='files to check (files MUST be known to git, if none: check all)')
-
-    args = parser.parse_args()
-
-    covered = frozenset(get_src_files(args.since))
-    # We only check files that are known to git
-    if args.subset or args.operands:
-        src_files = [f for f in args.operands if f in covered]
-        skip_src_files = [f for f in args.operands if f not in covered]
-        if skip_src_files:
-            print_skip(skip_src_files)
-    else:
-        src_files = list(covered)
-
-    if args.fix:
-        # Fix mode
-        return fix_style(src_files)
-    else:
-        # Check mode
-        if check_style_is_correct(src_files):
-            print("Checked {} files, style ok.".format(len(src_files)))
-            return 0
-        else:
-            return 1
-
-if __name__ == '__main__':
-    sys.exit(main())
diff --git a/scripts/config.py b/scripts/config.py
index ef13062..c266d89 100755
--- a/scripts/config.py
+++ b/scripts/config.py
@@ -48,8 +48,6 @@
     return True
 
 PSA_UNSUPPORTED_FEATURE = frozenset([
-    'PSA_WANT_ALG_CBC_MAC',
-    'PSA_WANT_ALG_XTS',
     'PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_DERIVE',
     'PSA_WANT_KEY_TYPE_DH_KEY_PAIR_DERIVE'
 ])
@@ -59,13 +57,8 @@
     'PSA_WANT_KEY_TYPE_RSA_KEY_PAIR'
 ])
 
-PSA_UNSTABLE_FEATURE = frozenset([
-    'PSA_WANT_ECC_SECP_K1_224'
-])
-
 EXCLUDE_FROM_CRYPTO = PSA_UNSUPPORTED_FEATURE | \
-                      PSA_DEPRECATED_FEATURE | \
-                      PSA_UNSTABLE_FEATURE
+                      PSA_DEPRECATED_FEATURE
 
 # The goal of the full configuration is to have everything that can be tested
 # together. This includes deprecated or insecure options. It excludes:
@@ -236,6 +229,7 @@
 
 DEPRECATED = frozenset([
     'MBEDTLS_PSA_CRYPTO_SE_C',
+    'MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME',
 ])
 def no_deprecated_adapter(adapter):
     """Modify an adapter to disable deprecated symbols.
@@ -293,11 +287,7 @@
     # Temporary, while Mbed TLS does not just rely on the TF-PSA-Crypto
     # build system to build its crypto library. When it does, the
     # condition can just be removed.
-    _path_in_tree = ('include/psa/crypto_config.h'
-                     if not os.path.isdir(os.path.join(os.path.dirname(__file__),
-                                                       os.pardir,
-                                                       'tf-psa-crypto')) else
-                     'tf-psa-crypto/include/psa/crypto_config.h')
+    _path_in_tree = 'include/psa/crypto_config.h'
     default_path = [_path_in_tree,
                     os.path.join(os.path.dirname(__file__),
                                  os.pardir,
@@ -357,8 +347,6 @@
 
         if name in PSA_UNSUPPORTED_FEATURE:
             raise ValueError(f'Feature is unsupported: \'{name}\'')
-        if name in PSA_UNSTABLE_FEATURE:
-            raise ValueError(f'Feature is unstable: \'{name}\'')
 
         if name not in self.settings:
             self._get_configfile().templates.append((name, '', '#define ' + name + ' '))
diff --git a/scripts/data_files/driver_templates/psa_crypto_driver_wrappers.h.jinja b/scripts/data_files/driver_templates/psa_crypto_driver_wrappers.h.jinja
index d3b7d6f..ed5c9a0 100644
--- a/scripts/data_files/driver_templates/psa_crypto_driver_wrappers.h.jinja
+++ b/scripts/data_files/driver_templates/psa_crypto_driver_wrappers.h.jinja
@@ -307,8 +307,7 @@
 #endif /* PSA_CRYPTO_DRIVER_TEST */
 #if defined (MBEDTLS_PSA_P256M_DRIVER_ENABLED)
             if( PSA_KEY_TYPE_IS_ECC( psa_get_key_type(attributes) ) &&
-                PSA_ALG_IS_ECDSA(alg) &&
-                !PSA_ALG_ECDSA_IS_DETERMINISTIC( alg ) &&
+                PSA_ALG_IS_RANDOMIZED_ECDSA(alg) &&
                 PSA_KEY_TYPE_ECC_GET_FAMILY(psa_get_key_type(attributes)) == PSA_ECC_FAMILY_SECP_R1 &&
                 psa_get_key_bits(attributes) == 256 )
             {
@@ -412,7 +411,6 @@
 #if defined (MBEDTLS_PSA_P256M_DRIVER_ENABLED)
             if( PSA_KEY_TYPE_IS_ECC( psa_get_key_type(attributes) ) &&
                 PSA_ALG_IS_ECDSA(alg) &&
-                !PSA_ALG_ECDSA_IS_DETERMINISTIC( alg ) &&
                 PSA_KEY_TYPE_ECC_GET_FAMILY(psa_get_key_type(attributes)) == PSA_ECC_FAMILY_SECP_R1 &&
                 psa_get_key_bits(attributes) == 256 )
             {
diff --git a/scripts/generate_visualc_files.pl b/scripts/generate_visualc_files.pl
index bc1d502..9f28cdd 100755
--- a/scripts/generate_visualc_files.pl
+++ b/scripts/generate_visualc_files.pl
@@ -22,6 +22,7 @@
 my $vsx_sln_file = "$vsx_dir/mbedTLS.sln";
 
 my $programs_dir = 'programs';
+my $framework_programs_dir = 'framework/tests/programs';
 my $mbedtls_header_dir = 'include/mbedtls';
 my $psa_header_dir = 'include/psa';
 my $source_dir = 'library';
@@ -52,6 +53,7 @@
     3rdparty/everest/include/everest/kremlib
     tests/include
     framework/tests/include
+    framework/tests/programs
 );
 my $include_directories = join(';', map {"../../$_"} @include_directories);
 
@@ -112,7 +114,8 @@
         && -d $test_header_dir
         && -d $tls_test_header_dir
         && -d $test_drivers_header_dir
-        && -d $programs_dir;
+        && -d $programs_dir
+        && -d $framework_programs_dir;
 }
 
 sub slurp_file {
@@ -151,7 +154,14 @@
     (my $appname = $path) =~ s/.*\\//;
     my $is_test_app = ($path =~ m/^test\\/);
 
-    my $srcs = "<ClCompile Include=\"..\\..\\programs\\$path.c\" \/>";
+    my $srcs;
+    if( $appname eq "metatest" or $appname eq "query_compile_time_config" or
+        $appname eq "query_included_headers" or $appname eq "zeroize" ) {
+        $srcs = "<ClCompile Include=\"..\\..\\framework\\tests\\programs\\$appname.c\" \/>";
+    } else {
+        $srcs = "<ClCompile Include=\"..\\..\\programs\\$path.c\" \/>";
+    }
+
     if( $appname eq "ssl_client2" or $appname eq "ssl_server2" or
         $appname eq "query_compile_time_config" ) {
         $srcs .= "\n    <ClCompile Include=\"..\\..\\programs\\test\\query_config.c\" \/>";
@@ -267,6 +277,7 @@
                        $tls_test_header_dir,
                        $test_drivers_header_dir,
                        $source_dir,
+                       $framework_programs_dir,
                        @thirdparty_header_dirs,
                       );
     my @headers = (map { <$_/*.h> } @header_dirs);
diff --git a/scripts/make_generated_files.bat b/scripts/make_generated_files.bat
index 29687cb..4977cec 100644
--- a/scripts/make_generated_files.bat
+++ b/scripts/make_generated_files.bat
@@ -15,9 +15,6 @@
 perl scripts\generate_features.pl || exit /b 1

 python framework\scripts\generate_ssl_debug_helpers.py || exit /b 1

 

-@rem @@@@ Build @@@@

-perl scripts\generate_visualc_files.pl || exit /b 1

-

 @rem @@@@ programs\** @@@@

 python scripts\generate_psa_constants.py || exit /b 1

 

@@ -26,6 +23,12 @@
 python framework\scripts\generate_config_tests.py || exit /b 1

 python framework\scripts\generate_ecp_tests.py || exit /b 1

 python framework\scripts\generate_psa_tests.py || exit /b 1

-python framework\scripts\generate_test_keys.py --output framework\tests\src\test_keys.h || exit /b 1

-python framework\scripts\generate_test_cert_macros.py --output tests\src\test_certs.h || exit /b 1

+python framework\scripts\generate_test_keys.py --output tests\include\test\test_keys.h || exit /b 1

+python framework\scripts\generate_test_cert_macros.py --output tests\include\test\test_certs.h || exit /b 1

+python framework\scripts\generate_tls_handshake_tests.py || exit /b 1

 python framework\scripts\generate_tls13_compat_tests.py || exit /b 1

+

+@rem @@@@ Build @@@@

+@rem Call generate_visualc_files.pl last to be sure everything else has been

+@rem generated before.

+perl scripts\generate_visualc_files.pl || exit /b 1

diff --git a/scripts/min_requirements.py b/scripts/min_requirements.py
index b36f906..a67b761 100755
--- a/scripts/min_requirements.py
+++ b/scripts/min_requirements.py
@@ -5,125 +5,12 @@
 # Copyright The Mbed TLS Contributors
 # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
 
-import argparse
 import os
-import re
-import subprocess
-import sys
-import tempfile
-import typing
-
-from typing import List, Optional
-
 import framework_scripts_path # pylint: disable=unused-import
-from mbedtls_framework import typing_util
+from mbedtls_framework import min_requirements
 
-def pylint_doesn_t_notice_that_certain_types_are_used_in_annotations(
-        _list: List[typing.Any],
-) -> None:
-    pass
-
-
-class Requirements:
-    """Collect and massage Python requirements."""
-
-    def __init__(self) -> None:
-        self.requirements = [] #type: List[str]
-
-    def adjust_requirement(self, req: str) -> str:
-        """Adjust a requirement to the minimum specified version."""
-        # allow inheritance #pylint: disable=no-self-use
-        # If a requirement specifies a minimum version, impose that version.
-        split_req = req.split(';', 1)
-        split_req[0] = re.sub(r'>=|~=', r'==', split_req[0])
-        return ';'.join(split_req)
-
-    def add_file(self, filename: str) -> None:
-        """Add requirements from the specified file.
-
-        This method supports a subset of pip's requirement file syntax:
-        * One requirement specifier per line, which is passed to
-          `adjust_requirement`.
-        * Comments (``#`` at the beginning of the line or after whitespace).
-        * ``-r FILENAME`` to include another file.
-        """
-        for line in open(filename):
-            line = line.strip()
-            line = re.sub(r'(\A|\s+)#.*', r'', line)
-            if not line:
-                continue
-            m = re.match(r'-r\s+', line)
-            if m:
-                nested_file = os.path.join(os.path.dirname(filename),
-                                           line[m.end(0):])
-                self.add_file(nested_file)
-                continue
-            self.requirements.append(self.adjust_requirement(line))
-
-    def write(self, out: typing_util.Writable) -> None:
-        """List the gathered requirements."""
-        for req in self.requirements:
-            out.write(req + '\n')
-
-    def install(
-            self,
-            pip_general_options: Optional[List[str]] = None,
-            pip_install_options: Optional[List[str]] = None,
-    ) -> None:
-        """Call pip to install the requirements."""
-        if pip_general_options is None:
-            pip_general_options = []
-        if pip_install_options is None:
-            pip_install_options = []
-        with tempfile.TemporaryDirectory() as temp_dir:
-            # This is more complicated than it needs to be for the sake
-            # of Windows. Use a temporary file rather than the command line
-            # to avoid quoting issues. Use a temporary directory rather
-            # than NamedTemporaryFile because with a NamedTemporaryFile on
-            # Windows, the subprocess can't open the file because this process
-            # has an exclusive lock on it.
-            req_file_name = os.path.join(temp_dir, 'requirements.txt')
-            with open(req_file_name, 'w') as req_file:
-                self.write(req_file)
-            subprocess.check_call([sys.executable, '-m', 'pip'] +
-                                  pip_general_options +
-                                  ['install'] + pip_install_options +
-                                  ['-r', req_file_name])
-
+# The default file is located in the same folder as this script.
 DEFAULT_REQUIREMENTS_FILE = 'ci.requirements.txt'
 
-def main() -> None:
-    """Command line entry point."""
-    parser = argparse.ArgumentParser(description=__doc__)
-    parser.add_argument('--no-act', '-n',
-                        action='store_true',
-                        help="Don't act, just print what will be done")
-    parser.add_argument('--pip-install-option',
-                        action='append', dest='pip_install_options',
-                        help="Pass this option to pip install")
-    parser.add_argument('--pip-option',
-                        action='append', dest='pip_general_options',
-                        help="Pass this general option to pip")
-    parser.add_argument('--user',
-                        action='append_const', dest='pip_install_options',
-                        const='--user',
-                        help="Install to the Python user install directory"
-                             " (short for --pip-install-option --user)")
-    parser.add_argument('files', nargs='*', metavar='FILE',
-                        help="Requirement files"
-                             " (default: {} in the script's directory)" \
-                             .format(DEFAULT_REQUIREMENTS_FILE))
-    options = parser.parse_args()
-    if not options.files:
-        options.files = [os.path.join(os.path.dirname(__file__),
-                                      DEFAULT_REQUIREMENTS_FILE)]
-    reqs = Requirements()
-    for filename in options.files:
-        reqs.add_file(filename)
-    reqs.write(sys.stdout)
-    if not options.no_act:
-        reqs.install(pip_general_options=options.pip_general_options,
-                     pip_install_options=options.pip_install_options)
-
-if __name__ == '__main__':
-    main()
+min_requirements.main(os.path.join(os.path.dirname(__file__),
+                                   DEFAULT_REQUIREMENTS_FILE))
diff --git a/tests/.gitignore b/tests/.gitignore
index 0c58875..d32d367 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -18,12 +18,13 @@
 
 ###START_GENERATED_FILES###
 # Generated source files
+/opt-testcases/handshake-generated.sh
 /opt-testcases/tls13-compat.sh
 /suites/*.generated.data
 /suites/test_suite_config.mbedtls_boolean.data
 /suites/test_suite_config.psa_boolean.data
 /suites/test_suite_psa_crypto_storage_format.v[0-9]*.data
 /suites/test_suite_psa_crypto_storage_format.current.data
-/src/test_keys.h
-/src/test_certs.h
+/include/test/test_keys.h
+/include/test/test_certs.h
 ###END_GENERATED_FILES###
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index c13d643..aacb9ec 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -124,6 +124,24 @@
             # change too often in ways that don't affect the result
             # ((un)commenting some options).
     )
+
+    add_custom_command(
+        OUTPUT
+            ${CMAKE_CURRENT_SOURCE_DIR}/opt-testcases/handshake-generated.sh
+        WORKING_DIRECTORY
+            ${CMAKE_CURRENT_SOURCE_DIR}/..
+        COMMAND
+            "${MBEDTLS_PYTHON_EXECUTABLE}"
+            "${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/generate_tls_handshake_tests.py"
+        DEPENDS
+            ${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/mbedtls_framework/tls_test_case.py
+            ${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/generate_tls_handshake_tests.py
+    )
+    add_custom_target(handshake-generated.sh
+        DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/opt-testcases/handshake-generated.sh)
+    set_target_properties(handshake-generated.sh PROPERTIES EXCLUDE_FROM_ALL NO)
+    add_dependencies(${ssl_opt_target} handshake-generated.sh)
+
     add_custom_command(
         OUTPUT
             ${ecp_generated_data_files}
@@ -156,6 +174,7 @@
             ${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/mbedtls_framework/macro_collector.py
             ${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/mbedtls_framework/psa_information.py
             ${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/mbedtls_framework/psa_storage.py
+            ${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/mbedtls_framework/psa_test_case.py
             ${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/mbedtls_framework/test_case.py
             ${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/mbedtls_framework/test_data_generation.py
             ${CMAKE_CURRENT_SOURCE_DIR}/../include/psa/crypto_config.h
@@ -317,6 +336,7 @@
     # files are automatically included because the library targets declare
     # them as PUBLIC.
     target_include_directories(test_suite_${data_name}
+        PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/include
         PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include
         PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../framework/tests/include
         PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../library)
diff --git a/tests/Makefile b/tests/Makefile
index baeb7f7..103c4fe 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -53,11 +53,18 @@
 GENERATED_DATA_FILES += $(GENERATED_PSA_DATA_FILES)
 
 GENERATED_FILES = $(GENERATED_DATA_FILES)
-GENERATED_FILES += ../framework/tests/src/test_keys.h src/test_certs.h
+GENERATED_FILES += include/test/test_keys.h include/test/test_certs.h
 
 # Generated files needed to (fully) run ssl-opt.sh
 .PHONY: ssl-opt
 
+opt-testcases/handshake-generated.sh: ../framework/scripts/mbedtls_framework/tls_test_case.py
+opt-testcases/handshake-generated.sh: ../framework/scripts/generate_tls_handshake_tests.py
+	echo "  Gen   $@"
+	$(PYTHON) ../framework/scripts/generate_tls_handshake_tests.py -o $@
+GENERATED_FILES += opt-testcases/handshake-generated.sh
+ssl-opt: opt-testcases/handshake-generated.sh
+
 opt-testcases/tls13-compat.sh: ../framework/scripts/generate_tls13_compat_tests.py
 	echo "  Gen   $@"
 	$(PYTHON) ../framework/scripts/generate_tls13_compat_tests.py -o $@
@@ -120,6 +127,7 @@
 generated_psa_test_data: ../framework/scripts/mbedtls_framework/macro_collector.py
 generated_psa_test_data: ../framework/scripts/mbedtls_framework/psa_information.py
 generated_psa_test_data: ../framework/scripts/mbedtls_framework/psa_storage.py
+generated_psa_test_data: ../framework/scripts/mbedtls_framework/psa_test_case.py
 generated_psa_test_data: ../framework/scripts/mbedtls_framework/test_case.py
 generated_psa_test_data: ../framework/scripts/mbedtls_framework/test_data_generation.py
 ## The generated file only depends on the options that are present in
@@ -156,12 +164,12 @@
 
 mbedtls_test: $(MBEDTLS_TEST_OBJS)
 
-src/test_certs.h: ../framework/scripts/generate_test_cert_macros.py \
+include/test/test_certs.h: ../framework/scripts/generate_test_cert_macros.py \
 				  $($(PYTHON) ../framework/scripts/generate_test_cert_macros.py --list-dependencies)
 	echo "  Gen   $@"
 	$(PYTHON) ../framework/scripts/generate_test_cert_macros.py --output $@
 
-../framework/tests/src/test_keys.h: ../framework/scripts/generate_test_keys.py
+include/test/test_keys.h: ../framework/scripts/generate_test_keys.py
 	echo "  Gen   $@"
 	$(PYTHON) ../framework/scripts/generate_test_keys.py --output $@
 
@@ -172,7 +180,7 @@
 # therefore the wildcard enumeration above doesn't include it.
 TEST_OBJS_DEPS += ../framework/tests/include/test/instrument_record_status.h
 endif
-TEST_OBJS_DEPS += src/test_certs.h ../framework/tests/src/test_keys.h
+TEST_OBJS_DEPS += include/test/test_certs.h include/test/test_keys.h
 
 # Rule to compile common test C files in framework
 ../framework/tests/src/%.o : ../framework/tests/src/%.c $(TEST_OBJS_DEPS)
diff --git a/tests/configs/crypto_config_test_driver_extension.h b/tests/configs/crypto_config_test_driver_extension.h
index 66378e7..879e29e 100644
--- a/tests/configs/crypto_config_test_driver_extension.h
+++ b/tests/configs/crypto_config_test_driver_extension.h
@@ -127,14 +127,6 @@
 #endif
 #endif
 
-#if defined(PSA_WANT_ECC_SECP_K1_224)
-#if defined(MBEDTLS_PSA_ACCEL_ECC_SECP_K1_224)
-#undef MBEDTLS_PSA_ACCEL_ECC_SECP_K1_224
-#else
-#define MBEDTLS_PSA_ACCEL_ECC_SECP_K1_224 1
-#endif
-#endif
-
 #if defined(PSA_WANT_ECC_SECP_K1_256)
 #if defined(MBEDTLS_PSA_ACCEL_ECC_SECP_K1_256)
 #undef MBEDTLS_PSA_ACCEL_ECC_SECP_K1_256
@@ -367,14 +359,6 @@
 #endif
 #endif
 
-#if defined(PSA_WANT_ALG_XTS)
-#if defined(MBEDTLS_PSA_ACCEL_ALG_XTS)
-#undef MBEDTLS_PSA_ACCEL_ALG_XTS
-#else
-#define MBEDTLS_PSA_ACCEL_ALG_XTS 1
-#endif
-#endif
-
 #if defined(PSA_WANT_ALG_CHACHA20_POLY1305)
 #if defined(MBEDTLS_PSA_ACCEL_ALG_CHACHA20_POLY1305)
 #undef MBEDTLS_PSA_ACCEL_ALG_CHACHA20_POLY1305
@@ -600,14 +584,6 @@
 #endif
 #endif
 
-#if defined(PSA_WANT_ALG_CBC_MAC)
-#if defined(MBEDTLS_PSA_ACCEL_ALG_CBC_MAC)
-#undef MBEDTLS_PSA_ACCEL_ALG_CBC_MAC
-#else
-#define MBEDTLS_PSA_ACCEL_ALG_CBC_MAC 1
-#endif
-#endif
-
 #if defined(PSA_WANT_ALG_HMAC)
 #if defined(MBEDTLS_PSA_ACCEL_ALG_HMAC)
 #undef MBEDTLS_PSA_ACCEL_ALG_HMAC
diff --git a/tests/include/test/ssl_helpers.h b/tests/include/test/ssl_helpers.h
index 77f85c4..33ed2a8 100644
--- a/tests/include/test/ssl_helpers.h
+++ b/tests/include/test/ssl_helpers.h
@@ -471,6 +471,18 @@
  * /p second_ssl is used as second endpoint and their sockets have to be
  * connected before calling this function.
  *
+ * For example, to perform a full handshake:
+ * ```
+ * mbedtls_test_move_handshake_to_state(
+ *                       &server.ssl, &client.ssl,
+ *                       MBEDTLS_SSL_HANDSHAKE_OVER);
+ * mbedtls_test_move_handshake_to_state(
+ *                       &client.ssl, &server.ssl,
+ *                       MBEDTLS_SSL_HANDSHAKE_OVER);
+ * ```
+ * Note that you need both calls to reach the handshake-over state on
+ * both sides.
+ *
  * \retval  0 on success, otherwise error code.
  */
 int mbedtls_test_move_handshake_to_state(mbedtls_ssl_context *ssl,
@@ -585,6 +597,14 @@
     int msg_len_2, const int expected_fragments_2);
 
 #if defined(MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED)
+int mbedtls_test_ssl_do_handshake_with_endpoints(
+    mbedtls_test_ssl_endpoint *server_ep,
+    mbedtls_test_ssl_endpoint *client_ep,
+    mbedtls_test_handshake_test_options *options,
+    mbedtls_ssl_protocol_version proto);
+#endif /* defined(MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED) */
+
+#if defined(MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED)
 void mbedtls_test_ssl_perform_handshake(
     mbedtls_test_handshake_test_options *options);
 #endif /* MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED */
diff --git a/tests/scripts/analyze_outcomes.py b/tests/scripts/analyze_outcomes.py
index 18c8bde..301bfc4 100755
--- a/tests/scripts/analyze_outcomes.py
+++ b/tests/scripts/analyze_outcomes.py
@@ -33,41 +33,6 @@
                           r'.*\b(?:' + r'|'.join(words) + r')\b.*',
                           re.DOTALL)
 
-    # generate_psa_tests.py generates test cases involving cryptographic
-    # mechanisms (key types, families, algorithms) that are declared but
-    # not implemented. Until we improve the Python scripts, ignore those
-    # test cases in the analysis.
-    # https://github.com/Mbed-TLS/mbedtls/issues/9572
-    _PSA_MECHANISMS_NOT_IMPLEMENTED = [
-        r'CBC_MAC',
-        r'DETERMINISTIC_DSA',
-        r'DET_DSA',
-        r'DSA',
-        r'ECC_KEY_PAIR\(BRAINPOOL_P_R1\) (?:160|192|224|320)-bit',
-        r'ECC_KEY_PAIR\(SECP_K1\) 225-bit',
-        r'ECC_PAIR\(BP_R1\) (?:160|192|224|320)-bit',
-        r'ECC_PAIR\(SECP_K1\) 225-bit',
-        r'ECC_PUBLIC_KEY\(BRAINPOOL_P_R1\) (?:160|192|224|320)-bit',
-        r'ECC_PUBLIC_KEY\(SECP_K1\) 225-bit',
-        r'ECC_PUB\(BP_R1\) (?:160|192|224|320)-bit',
-        r'ECC_PUB\(SECP_K1\) 225-bit',
-        r'ED25519PH',
-        r'ED448PH',
-        r'PEPPER',
-        r'PURE_EDDSA',
-        r'SECP_R2',
-        r'SECT_K1',
-        r'SECT_R1',
-        r'SECT_R2',
-        r'SHAKE256_512',
-        r'SHA_512_224',
-        r'SHA_512_256',
-        r'TWISTED_EDWARDS',
-        r'XTS',
-    ]
-    PSA_MECHANISM_NOT_IMPLEMENTED_SEARCH_RE = \
-        _has_word_re(_PSA_MECHANISMS_NOT_IMPLEMENTED)
-
     IGNORED_TESTS = {
         'ssl-opt': [
             # We don't run ssl-opt.sh with Valgrind on the CI because
@@ -206,15 +171,10 @@
             'PBES2 Encrypt, pad=6 (PKCS7 padding disabled)',
             'PBES2 Encrypt, pad=8 (PKCS7 padding disabled)',
         ],
-        'test_suite_psa_crypto_generate_key.generated': [
-            # Ignore mechanisms that are not implemented, except
-            # for public keys for which we always test that
-            # psa_generate_key() returns PSA_ERROR_INVALID_ARGUMENT
-            # regardless of whether the specific key type is supported.
-            _has_word_re((mech
-                          for mech in _PSA_MECHANISMS_NOT_IMPLEMENTED
-                          if not mech.startswith('ECC_PUB')),
-                         exclude=r'ECC_PUB'),
+        'test_suite_psa_crypto': [
+            # We don't test this unusual, but sensible configuration.
+            # https://github.com/Mbed-TLS/mbedtls/issues/9592
+            re.compile(r'.*ECDSA.*only deterministic supported'),
         ],
         'test_suite_psa_crypto_metadata': [
             # Algorithms declared but not supported.
@@ -229,10 +189,6 @@
             'MAC: CBC_MAC-AES-256',
         ],
         'test_suite_psa_crypto_not_supported.generated': [
-            # It is a bug that not-supported test cases aren't getting
-            # run for never-implemented key types.
-            # https://github.com/Mbed-TLS/mbedtls/issues/7915
-            PSA_MECHANISM_NOT_IMPLEMENTED_SEARCH_RE,
             # We never test with DH key support disabled but support
             # for a DH group enabled. The dependencies of these test
             # cases don't really make sense.
@@ -246,18 +202,9 @@
             'PSA import DH_PUBLIC_KEY(RFC7919) 2048-bit group not supported',
         ],
         'test_suite_psa_crypto_op_fail.generated': [
-            # Ignore mechanisms that are not implemented, except
-            # for test cases that assume the mechanism is not supported.
-            _has_word_re(_PSA_MECHANISMS_NOT_IMPLEMENTED,
-                         exclude=(r'.*: !(?:' +
-                                  r'|'.join(_PSA_MECHANISMS_NOT_IMPLEMENTED) +
-                                  r')\b')),
-            # Incorrect dependency generation. To be fixed as part of the
-            # resolution of https://github.com/Mbed-TLS/mbedtls/issues/9167
-            # by forward-porting the commit
-            # "PSA test case generation: dependency inference class: operation fail"
-            # from https://github.com/Mbed-TLS/mbedtls/pull/9025 .
-            re.compile(r'.* with (?:DH|ECC)_(?:KEY_PAIR|PUBLIC_KEY)\(.*'),
+            # We don't test this unusual, but sensible configuration.
+            # https://github.com/Mbed-TLS/mbedtls/issues/9592
+            re.compile(r'.*: !ECDSA but DETERMINISTIC_ECDSA with ECC_.*'),
             # PBKDF2_HMAC is not in the default configuration, so we don't
             # enable it in depends.py where we remove hashes.
             # https://github.com/Mbed-TLS/mbedtls/issues/9576
@@ -271,12 +218,17 @@
             # differing from PSA_WANT_ALG_RSA_PSS.
             # https://github.com/Mbed-TLS/mbedtls/issues/9578
             re.compile(r'PSA sign RSA_PSS_ANY_SALT.*!(?:MD|RIPEMD|SHA).*'),
+            # We don't test with ECDH disabled but the key type enabled.
+            # https://github.com/Mbed-TLS/TF-PSA-Crypto/issues/161
+            re.compile(r'PSA key_agreement.* !ECDH with ECC_KEY_PAIR\(.*'),
+            # We don't test with FFDH disabled but the key type enabled.
+            # https://github.com/Mbed-TLS/TF-PSA-Crypto/issues/160
+            re.compile(r'PSA key_agreement.* !FFDH with DH_KEY_PAIR\(.*'),
         ],
-        'test_suite_psa_crypto_storage_format.current': [
-            PSA_MECHANISM_NOT_IMPLEMENTED_SEARCH_RE,
-        ],
-        'test_suite_psa_crypto_storage_format.v0': [
-            PSA_MECHANISM_NOT_IMPLEMENTED_SEARCH_RE,
+        'test_suite_psa_crypto_op_fail.misc': [
+            # We don't test this unusual, but sensible configuration.
+            # https://github.com/Mbed-TLS/mbedtls/issues/9592
+            'PSA sign DETERMINISTIC_ECDSA(SHA_256): !ECDSA but DETERMINISTIC_ECDSA with ECC_KEY_PAIR(SECP_R1)', #pylint: disable=line-too-long
         ],
         'tls13-misc': [
             # Disabled due to OpenSSL bug.
@@ -460,7 +412,7 @@
         ],
         'test_suite_ssl': [
             # This deprecated function is only present when ECP_C is On.
-            'Test configuration of groups for DHE through mbedtls_ssl_conf_curves()',
+            'Test configuration of EC groups through mbedtls_ssl_conf_curves()',
         ],
     }
 
@@ -500,7 +452,7 @@
         ],
         # See ecp_light_only
         'test_suite_ssl': [
-            'Test configuration of groups for DHE through mbedtls_ssl_conf_curves()',
+            'Test configuration of EC groups through mbedtls_ssl_conf_curves()',
         ],
     }
 
@@ -547,7 +499,7 @@
         ],
         # See ecp_light_only
         'test_suite_ssl': [
-            'Test configuration of groups for DHE through mbedtls_ssl_conf_curves()',
+            'Test configuration of EC groups through mbedtls_ssl_conf_curves()',
         ],
     }
 
@@ -602,7 +554,7 @@
         ],
         # See ecp_light_only
         'test_suite_ssl': [
-            'Test configuration of groups for DHE through mbedtls_ssl_conf_curves()',
+            'Test configuration of EC groups through mbedtls_ssl_conf_curves()',
         ],
     }
 
diff --git a/tests/scripts/check-doxy-blocks.pl b/tests/scripts/check-doxy-blocks.pl
deleted file mode 100755
index 3199c2a..0000000
--- a/tests/scripts/check-doxy-blocks.pl
+++ /dev/null
@@ -1,67 +0,0 @@
-#!/usr/bin/env perl
-
-# Detect comment blocks that are likely meant to be doxygen blocks but aren't.
-#
-# More precisely, look for normal comment block containing '\'.
-# Of course one could use doxygen warnings, eg with:
-#   sed -e '/EXTRACT/s/YES/NO/' doxygen/mbedtls.doxyfile | doxygen -
-# but that would warn about any undocumented item, while our goal is to find
-# items that are documented, but not marked as such by mistake.
-#
-# Copyright The Mbed TLS Contributors
-# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
-
-use warnings;
-use strict;
-use File::Basename;
-
-# C/header files in the following directories will be checked
-my @directories = qw(include/mbedtls library doxygen/input);
-
-# very naive pattern to find directives:
-# everything with a backslach except '\0' and backslash at EOL
-my $doxy_re = qr/\\(?!0|\n)/;
-
-# Return an error code to the environment if a potential error in the
-# source code is found.
-my $exit_code = 0;
-
-sub check_file {
-    my ($fname) = @_;
-    open my $fh, '<', $fname or die "Failed to open '$fname': $!\n";
-
-    # first line of the last normal comment block,
-    # or 0 if not in a normal comment block
-    my $block_start = 0;
-    while (my $line = <$fh>) {
-        $block_start = $.   if $line =~ m/\/\*(?![*!])/;
-        $block_start = 0    if $line =~ m/\*\//;
-        if ($block_start and $line =~ m/$doxy_re/) {
-            print "$fname:$block_start: directive on line $.\n";
-            $block_start = 0; # report only one directive per block
-            $exit_code = 1;
-        }
-    }
-
-    close $fh;
-}
-
-sub check_dir {
-    my ($dirname) = @_;
-    for my $file (<$dirname/*.[ch]>) {
-        check_file($file);
-    }
-}
-
-# Check that the script is being run from the project's root directory.
-for my $dir (@directories) {
-    if (! -d $dir) {
-        die "This script must be run from the Mbed TLS root directory";
-    } else {
-        check_dir($dir)
-    }
-}
-
-exit $exit_code;
-
-__END__
diff --git a/tests/scripts/check-generated-files.sh b/tests/scripts/check-generated-files.sh
index d796cc0..4352480 100755
--- a/tests/scripts/check-generated-files.sh
+++ b/tests/scripts/check-generated-files.sh
@@ -111,7 +111,7 @@
 }
 
 # Note: if the format of calls to the "check" function changes, update
-# scripts/code_style.py accordingly. For generated C source files (*.h or *.c),
+# framework/scripts/code_style.py accordingly. For generated C source files (*.h or *.c),
 # the format must be "check SCRIPT FILENAME...". For other source files,
 # any shell syntax is permitted (including e.g. command substitution).
 
@@ -126,7 +126,7 @@
 check framework/scripts/generate_config_tests.py $(framework/scripts/generate_config_tests.py --list)
 check framework/scripts/generate_ecp_tests.py $(framework/scripts/generate_ecp_tests.py --list)
 check framework/scripts/generate_psa_tests.py $(framework/scripts/generate_psa_tests.py --list)
-check framework/scripts/generate_test_keys.py framework/tests/src/test_keys.h
+check framework/scripts/generate_test_keys.py tests/include/test/test_keys.h
 check scripts/generate_driver_wrappers.py $library_dir/psa_crypto_driver_wrappers.h $library_dir/psa_crypto_driver_wrappers_no_static.c
 
 # Additional checks for Mbed TLS only
@@ -135,8 +135,9 @@
     check scripts/generate_query_config.pl programs/test/query_config.c
     check scripts/generate_features.pl library/version_features.c
     check framework/scripts/generate_ssl_debug_helpers.py library/ssl_debug_helpers_generated.c
+    check framework/scripts/generate_tls_handshake_tests.py tests/opt-testcases/handshake-generated.sh
     check framework/scripts/generate_tls13_compat_tests.py tests/opt-testcases/tls13-compat.sh
-    check framework/scripts/generate_test_cert_macros.py tests/src/test_certs.h
+    check framework/scripts/generate_test_cert_macros.py tests/include/test/test_certs.h
     # generate_visualc_files enumerates source files (library/*.c). It doesn't
     # care about their content, but the files must exist. So it must run after
     # the step that creates or updates these files.
diff --git a/tests/scripts/check-python-files.sh b/tests/scripts/check-python-files.sh
deleted file mode 100755
index 77102ba..0000000
--- a/tests/scripts/check-python-files.sh
+++ /dev/null
@@ -1,68 +0,0 @@
-#! /usr/bin/env sh
-
-# Copyright The Mbed TLS Contributors
-# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
-
-# Purpose: check Python files for potential programming errors or maintenance
-# hurdles. Run pylint to detect some potential mistakes and enforce PEP8
-# coding standards. Run mypy to perform static type checking.
-
-# We'll keep going on errors and report the status at the end.
-ret=0
-
-if type python3 >/dev/null 2>/dev/null; then
-    PYTHON=python3
-else
-    PYTHON=python
-fi
-
-check_version () {
-    $PYTHON - "$2" <<EOF
-import packaging.version
-import sys
-import $1 as package
-actual = package.__version__
-wanted = sys.argv[1]
-if packaging.version.parse(actual) < packaging.version.parse(wanted):
-    sys.stderr.write("$1: version %s is too old (want %s)\n" % (actual, wanted))
-    exit(1)
-EOF
-}
-
-can_pylint () {
-    # Pylint 1.5.2 from Ubuntu 16.04 is too old:
-    #     E: 34, 0: Unable to import 'mbedtls_framework' (import-error)
-    # Pylint 1.8.3 from Ubuntu 18.04 passed on the first commit containing this line.
-    check_version pylint 1.8.3
-}
-
-can_mypy () {
-    # mypy 0.770 is too old:
-    #     tests/scripts/test_psa_constant_names.py:34: error: Cannot find implementation or library stub for module named 'mbedtls_framework'
-    # mypy 0.780 from pip passed on the first commit containing this line.
-    check_version mypy.version 0.780
-}
-
-# With just a --can-xxx option, check whether the tool for xxx is available
-# with an acceptable version, and exit without running any checks. The exit
-# status is true if the tool is available and acceptable and false otherwise.
-if [ "$1" = "--can-pylint" ]; then
-    can_pylint
-    exit
-elif [ "$1" = "--can-mypy" ]; then
-    can_mypy
-    exit
-fi
-
-echo 'Running pylint ...'
-$PYTHON -m pylint framework/scripts/*.py framework/scripts/mbedtls_framework/*.py scripts/*.py tests/scripts/*.py || {
-    echo >&2 "pylint reported errors"
-    ret=1
-}
-
-echo
-echo 'Running mypy ...'
-$PYTHON -m mypy framework/scripts/*.py framework/scripts/mbedtls_framework/*.py scripts/*.py tests/scripts/*.py ||
-  ret=1
-
-exit $ret
diff --git a/tests/scripts/check_files.py b/tests/scripts/check_files.py
deleted file mode 100755
index 5a6eda2..0000000
--- a/tests/scripts/check_files.py
+++ /dev/null
@@ -1,546 +0,0 @@
-#!/usr/bin/env python3
-
-# Copyright The Mbed TLS Contributors
-# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
-
-"""
-This script checks the current state of the source code for minor issues,
-including incorrect file permissions, presence of tabs, non-Unix line endings,
-trailing whitespace, and presence of UTF-8 BOM.
-Note: requires python 3, must be run from Mbed TLS root.
-"""
-
-import argparse
-import codecs
-import inspect
-import logging
-import os
-import re
-import subprocess
-import sys
-try:
-    from typing import FrozenSet, Optional, Pattern # pylint: disable=unused-import
-except ImportError:
-    pass
-
-import scripts_path # pylint: disable=unused-import
-from mbedtls_framework import build_tree
-
-
-class FileIssueTracker:
-    """Base class for file-wide issue tracking.
-
-    To implement a checker that processes a file as a whole, inherit from
-    this class and implement `check_file_for_issue` and define ``heading``.
-
-    ``suffix_exemptions``: files whose name ends with a string in this set
-     will not be checked.
-
-    ``path_exemptions``: files whose path (relative to the root of the source
-    tree) matches this regular expression will not be checked. This can be
-    ``None`` to match no path. Paths are normalized and converted to ``/``
-    separators before matching.
-
-    ``heading``: human-readable description of the issue
-    """
-
-    suffix_exemptions = frozenset() #type: FrozenSet[str]
-    path_exemptions = None #type: Optional[Pattern[str]]
-    # heading must be defined in derived classes.
-    # pylint: disable=no-member
-
-    def __init__(self):
-        self.files_with_issues = {}
-
-    @staticmethod
-    def normalize_path(filepath):
-        """Normalize ``filepath`` with / as the directory separator."""
-        filepath = os.path.normpath(filepath)
-        # On Windows, we may have backslashes to separate directories.
-        # We need slashes to match exemption lists.
-        seps = os.path.sep
-        if os.path.altsep is not None:
-            seps += os.path.altsep
-        return '/'.join(filepath.split(seps))
-
-    def should_check_file(self, filepath):
-        """Whether the given file name should be checked.
-
-        Files whose name ends with a string listed in ``self.suffix_exemptions``
-        or whose path matches ``self.path_exemptions`` will not be checked.
-        """
-        for files_exemption in self.suffix_exemptions:
-            if filepath.endswith(files_exemption):
-                return False
-        if self.path_exemptions and \
-           re.match(self.path_exemptions, self.normalize_path(filepath)):
-            return False
-        return True
-
-    def check_file_for_issue(self, filepath):
-        """Check the specified file for the issue that this class is for.
-
-        Subclasses must implement this method.
-        """
-        raise NotImplementedError
-
-    def record_issue(self, filepath, line_number):
-        """Record that an issue was found at the specified location."""
-        if filepath not in self.files_with_issues.keys():
-            self.files_with_issues[filepath] = []
-        self.files_with_issues[filepath].append(line_number)
-
-    def output_file_issues(self, logger):
-        """Log all the locations where the issue was found."""
-        if self.files_with_issues.values():
-            logger.info(self.heading)
-            for filename, lines in sorted(self.files_with_issues.items()):
-                if lines:
-                    logger.info("{}: {}".format(
-                        filename, ", ".join(str(x) for x in lines)
-                    ))
-                else:
-                    logger.info(filename)
-            logger.info("")
-
-BINARY_FILE_PATH_RE_LIST = [
-    r'docs/.*\.pdf\Z',
-    r'docs/.*\.png\Z',
-    r'programs/fuzz/corpuses/[^.]+\Z',
-    r'framework/data_files/[^.]+\Z',
-    r'framework/data_files/.*\.(crt|csr|db|der|key|pubkey)\Z',
-    r'framework/data_files/.*\.req\.[^/]+\Z',
-    r'framework/data_files/.*malformed[^/]+\Z',
-    r'framework/data_files/format_pkcs12\.fmt\Z',
-    r'framework/data_files/.*\.bin\Z',
-]
-BINARY_FILE_PATH_RE = re.compile('|'.join(BINARY_FILE_PATH_RE_LIST))
-
-class LineIssueTracker(FileIssueTracker):
-    """Base class for line-by-line issue tracking.
-
-    To implement a checker that processes files line by line, inherit from
-    this class and implement `line_with_issue`.
-    """
-
-    # Exclude binary files.
-    path_exemptions = BINARY_FILE_PATH_RE
-
-    def issue_with_line(self, line, filepath, line_number):
-        """Check the specified line for the issue that this class is for.
-
-        Subclasses must implement this method.
-        """
-        raise NotImplementedError
-
-    def check_file_line(self, filepath, line, line_number):
-        if self.issue_with_line(line, filepath, line_number):
-            self.record_issue(filepath, line_number)
-
-    def check_file_for_issue(self, filepath):
-        """Check the lines of the specified file.
-
-        Subclasses must implement the ``issue_with_line`` method.
-        """
-        with open(filepath, "rb") as f:
-            for i, line in enumerate(iter(f.readline, b"")):
-                self.check_file_line(filepath, line, i + 1)
-
-
-def is_windows_file(filepath):
-    _root, ext = os.path.splitext(filepath)
-    return ext in ('.bat', '.dsp', '.dsw', '.sln', '.vcxproj')
-
-
-class ShebangIssueTracker(FileIssueTracker):
-    """Track files with a bad, missing or extraneous shebang line.
-
-    Executable scripts must start with a valid shebang (#!) line.
-    """
-
-    heading = "Invalid shebang line:"
-
-    # Allow either /bin/sh, /bin/bash, or /usr/bin/env.
-    # Allow at most one argument (this is a Linux limitation).
-    # For sh and bash, the argument if present must be options.
-    # For env, the argument must be the base name of the interpreter.
-    _shebang_re = re.compile(rb'^#! ?(?:/bin/(bash|sh)(?: -[^\n ]*)?'
-                             rb'|/usr/bin/env ([^\n /]+))$')
-    _extensions = {
-        b'bash': 'sh',
-        b'perl': 'pl',
-        b'python3': 'py',
-        b'sh': 'sh',
-    }
-
-    path_exemptions = re.compile(r'framework/scripts/quiet/.*')
-
-    def is_valid_shebang(self, first_line, filepath):
-        m = re.match(self._shebang_re, first_line)
-        if not m:
-            return False
-        interpreter = m.group(1) or m.group(2)
-        if interpreter not in self._extensions:
-            return False
-        if not filepath.endswith('.' + self._extensions[interpreter]):
-            return False
-        return True
-
-    def check_file_for_issue(self, filepath):
-        is_executable = os.access(filepath, os.X_OK)
-        with open(filepath, "rb") as f:
-            first_line = f.readline()
-        if first_line.startswith(b'#!'):
-            if not is_executable:
-                # Shebang on a non-executable file
-                self.files_with_issues[filepath] = None
-            elif not self.is_valid_shebang(first_line, filepath):
-                self.files_with_issues[filepath] = [1]
-        elif is_executable:
-            # Executable without a shebang
-            self.files_with_issues[filepath] = None
-
-
-class EndOfFileNewlineIssueTracker(FileIssueTracker):
-    """Track files that end with an incomplete line
-    (no newline character at the end of the last line)."""
-
-    heading = "Missing newline at end of file:"
-
-    path_exemptions = BINARY_FILE_PATH_RE
-
-    def check_file_for_issue(self, filepath):
-        with open(filepath, "rb") as f:
-            try:
-                f.seek(-1, 2)
-            except OSError:
-                # This script only works on regular files. If we can't seek
-                # 1 before the end, it means that this position is before
-                # the beginning of the file, i.e. that the file is empty.
-                return
-            if f.read(1) != b"\n":
-                self.files_with_issues[filepath] = None
-
-
-class Utf8BomIssueTracker(FileIssueTracker):
-    """Track files that start with a UTF-8 BOM.
-    Files should be ASCII or UTF-8. Valid UTF-8 does not start with a BOM."""
-
-    heading = "UTF-8 BOM present:"
-
-    suffix_exemptions = frozenset([".vcxproj", ".sln"])
-    path_exemptions = BINARY_FILE_PATH_RE
-
-    def check_file_for_issue(self, filepath):
-        with open(filepath, "rb") as f:
-            if f.read().startswith(codecs.BOM_UTF8):
-                self.files_with_issues[filepath] = None
-
-
-class UnicodeIssueTracker(LineIssueTracker):
-    """Track lines with invalid characters or invalid text encoding."""
-
-    heading = "Invalid UTF-8 or forbidden character:"
-
-    # Only allow valid UTF-8, and only other explicitly allowed characters.
-    # We deliberately exclude all characters that aren't a simple non-blank,
-    # non-zero-width glyph, apart from a very small set (tab, ordinary space,
-    # line breaks, "basic" no-break space and soft hyphen). In particular,
-    # non-ASCII control characters, combinig characters, and Unicode state
-    # changes (e.g. right-to-left text) are forbidden.
-    # Note that we do allow some characters with a risk of visual confusion,
-    # for example '-' (U+002D HYPHEN-MINUS) vs '­' (U+00AD SOFT HYPHEN) vs
-    # '‐' (U+2010 HYPHEN), or 'A' (U+0041 LATIN CAPITAL LETTER A) vs
-    # 'Α' (U+0391 GREEK CAPITAL LETTER ALPHA).
-    GOOD_CHARACTERS = ''.join([
-        '\t\n\r -~', # ASCII (tabs and line endings are checked separately)
-        '\u00A0-\u00FF', # Latin-1 Supplement (for NO-BREAK SPACE and punctuation)
-        '\u2010-\u2027\u2030-\u205E', # General Punctuation (printable)
-        '\u2070\u2071\u2074-\u208E\u2090-\u209C', # Superscripts and Subscripts
-        '\u2190-\u21FF', # Arrows
-        '\u2200-\u22FF', # Mathematical Symbols
-        '\u2500-\u257F' # Box Drawings characters used in markdown trees
-    ])
-    # Allow any of the characters and ranges above, and anything classified
-    # as a word constituent.
-    GOOD_CHARACTERS_RE = re.compile(r'[\w{}]+\Z'.format(GOOD_CHARACTERS))
-
-    def issue_with_line(self, line, _filepath, line_number):
-        try:
-            text = line.decode('utf-8')
-        except UnicodeDecodeError:
-            return True
-        if line_number == 1 and text.startswith('\uFEFF'):
-            # Strip BOM (U+FEFF ZERO WIDTH NO-BREAK SPACE) at the beginning.
-            # Which files are allowed to have a BOM is handled in
-            # Utf8BomIssueTracker.
-            text = text[1:]
-        return not self.GOOD_CHARACTERS_RE.match(text)
-
-class UnixLineEndingIssueTracker(LineIssueTracker):
-    """Track files with non-Unix line endings (i.e. files with CR)."""
-
-    heading = "Non-Unix line endings:"
-
-    def should_check_file(self, filepath):
-        if not super().should_check_file(filepath):
-            return False
-        return not is_windows_file(filepath)
-
-    def issue_with_line(self, line, _filepath, _line_number):
-        return b"\r" in line
-
-
-class WindowsLineEndingIssueTracker(LineIssueTracker):
-    """Track files with non-Windows line endings (i.e. CR or LF not in CRLF)."""
-
-    heading = "Non-Windows line endings:"
-
-    def should_check_file(self, filepath):
-        if not super().should_check_file(filepath):
-            return False
-        return is_windows_file(filepath)
-
-    def issue_with_line(self, line, _filepath, _line_number):
-        return not line.endswith(b"\r\n") or b"\r" in line[:-2]
-
-
-class TrailingWhitespaceIssueTracker(LineIssueTracker):
-    """Track lines with trailing whitespace."""
-
-    heading = "Trailing whitespace:"
-    suffix_exemptions = frozenset([".dsp", ".md"])
-
-    def issue_with_line(self, line, _filepath, _line_number):
-        return line.rstrip(b"\r\n") != line.rstrip()
-
-
-class TabIssueTracker(LineIssueTracker):
-    """Track lines with tabs."""
-
-    heading = "Tabs present:"
-    suffix_exemptions = frozenset([
-        ".make",
-        ".pem", # some openssl dumps have tabs
-        ".sln",
-        "/.gitmodules",
-        "/Makefile",
-        "/Makefile.inc",
-        "/generate_visualc_files.pl",
-    ])
-
-    def issue_with_line(self, line, _filepath, _line_number):
-        return b"\t" in line
-
-
-class MergeArtifactIssueTracker(LineIssueTracker):
-    """Track lines with merge artifacts.
-    These are leftovers from a ``git merge`` that wasn't fully edited."""
-
-    heading = "Merge artifact:"
-
-    def issue_with_line(self, line, _filepath, _line_number):
-        # Detect leftover git conflict markers.
-        if line.startswith(b'<<<<<<< ') or line.startswith(b'>>>>>>> '):
-            return True
-        if line.startswith(b'||||||| '): # from merge.conflictStyle=diff3
-            return True
-        if line.rstrip(b'\r\n') == b'=======' and \
-           not _filepath.endswith('.md'):
-            return True
-        return False
-
-
-def this_location():
-    frame = inspect.currentframe()
-    assert frame is not None
-    info = inspect.getframeinfo(frame)
-    return os.path.basename(info.filename), info.lineno
-THIS_FILE_BASE_NAME, LINE_NUMBER_BEFORE_LICENSE_ISSUE_TRACKER = this_location()
-
-class LicenseIssueTracker(LineIssueTracker):
-    """Check copyright statements and license indications.
-
-    This class only checks that statements are correct if present. It does
-    not enforce the presence of statements in each file.
-    """
-
-    heading = "License issue:"
-
-    LICENSE_EXEMPTION_RE_LIST = [
-        # Third-party code, other than whitelisted third-party modules,
-        # may be under a different license.
-        r'3rdparty/(?!(p256-m)/.*)',
-        # Documentation explaining the license may have accidental
-        # false positives.
-        r'(ChangeLog|LICENSE|framework\/LICENSE|[-0-9A-Z_a-z]+\.md)\Z',
-        # Files imported from TF-M, and not used except in test builds,
-        # may be under a different license.
-        r'configs/ext/crypto_config_profile_medium\.h\Z',
-        r'configs/ext/tfm_mbedcrypto_config_profile_medium\.h\Z',
-        r'configs/ext/README\.md\Z',
-        # Third-party file.
-        r'dco\.txt\Z',
-        r'framework\/dco\.txt\Z',
-    ]
-    path_exemptions = re.compile('|'.join(BINARY_FILE_PATH_RE_LIST +
-                                          LICENSE_EXEMPTION_RE_LIST))
-
-    COPYRIGHT_HOLDER = rb'The Mbed TLS Contributors'
-    # Catch "Copyright foo", "Copyright (C) foo", "Copyright © foo", etc.
-    COPYRIGHT_RE = re.compile(rb'.*\bcopyright\s+((?:\w|\s|[()]|[^ -~])*\w)', re.I)
-
-    SPDX_HEADER_KEY = b'SPDX-License-Identifier'
-    LICENSE_IDENTIFIER = b'Apache-2.0 OR GPL-2.0-or-later'
-    SPDX_RE = re.compile(br'.*?(' +
-                         re.escape(SPDX_HEADER_KEY) +
-                         br')(:\s*(.*?)\W*\Z|.*)', re.I)
-
-    LICENSE_MENTION_RE = re.compile(rb'.*(?:' + rb'|'.join([
-        rb'Apache License',
-        rb'General Public License',
-    ]) + rb')', re.I)
-
-    def __init__(self):
-        super().__init__()
-        # Record what problem was caused. We can't easily report it due to
-        # the structure of the script. To be fixed after
-        # https://github.com/Mbed-TLS/mbedtls/pull/2506
-        self.problem = None
-
-    def issue_with_line(self, line, filepath, line_number):
-        #pylint: disable=too-many-return-statements
-
-        # Use endswith() rather than the more correct os.path.basename()
-        # because experimentally, it makes a significant difference to
-        # the running time.
-        if filepath.endswith(THIS_FILE_BASE_NAME) and \
-           line_number > LINE_NUMBER_BEFORE_LICENSE_ISSUE_TRACKER:
-            # Avoid false positives from the code in this class.
-            # Also skip the rest of this file, which is highly unlikely to
-            # contain any problematic statements since we put those near the
-            # top of files.
-            return False
-
-        m = self.COPYRIGHT_RE.match(line)
-        if m and m.group(1) != self.COPYRIGHT_HOLDER:
-            self.problem = 'Invalid copyright line'
-            return True
-
-        m = self.SPDX_RE.match(line)
-        if m:
-            if m.group(1) != self.SPDX_HEADER_KEY:
-                self.problem = 'Misspelled ' + self.SPDX_HEADER_KEY.decode()
-                return True
-            if not m.group(3):
-                self.problem = 'Improperly formatted SPDX license identifier'
-                return True
-            if m.group(3) != self.LICENSE_IDENTIFIER:
-                self.problem = 'Wrong SPDX license identifier'
-                return True
-
-        m = self.LICENSE_MENTION_RE.match(line)
-        if m:
-            self.problem = 'Suspicious license mention'
-            return True
-
-        return False
-
-
-class IntegrityChecker:
-    """Sanity-check files under the current directory."""
-
-    def __init__(self, log_file):
-        """Instantiate the sanity checker.
-        Check files under the current directory.
-        Write a report of issues to log_file."""
-        build_tree.check_repo_path()
-        self.logger = None
-        self.setup_logger(log_file)
-        self.issues_to_check = [
-            ShebangIssueTracker(),
-            EndOfFileNewlineIssueTracker(),
-            Utf8BomIssueTracker(),
-            UnicodeIssueTracker(),
-            UnixLineEndingIssueTracker(),
-            WindowsLineEndingIssueTracker(),
-            TrailingWhitespaceIssueTracker(),
-            TabIssueTracker(),
-            MergeArtifactIssueTracker(),
-            LicenseIssueTracker(),
-        ]
-
-    def setup_logger(self, log_file, level=logging.INFO):
-        """Log to log_file if provided, or to stderr if None."""
-        self.logger = logging.getLogger()
-        self.logger.setLevel(level)
-        if log_file:
-            handler = logging.FileHandler(log_file)
-            self.logger.addHandler(handler)
-        else:
-            console = logging.StreamHandler()
-            self.logger.addHandler(console)
-
-    @staticmethod
-    def collect_files():
-        """Return the list of files to check.
-
-        These are the regular files commited into Git.
-        """
-        bytes_output = subprocess.check_output(['git', '-C', 'framework',
-                                                'ls-files', '-z'])
-        bytes_framework_filepaths = bytes_output.split(b'\0')[:-1]
-        bytes_framework_filepaths = ["framework/".encode() + filepath
-                                     for filepath in bytes_framework_filepaths]
-
-        bytes_output = subprocess.check_output(['git', 'ls-files', '-z'])
-        bytes_filepaths = bytes_output.split(b'\0')[:-1] + \
-                          bytes_framework_filepaths
-        ascii_filepaths = map(lambda fp: fp.decode('ascii'), bytes_filepaths)
-
-        # Filter out directories. Normally Git doesn't list directories
-        # (it only knows about the files inside them), but there is
-        # at least one case where 'git ls-files' includes a directory:
-        # submodules. Just skip submodules (and any other directories).
-        ascii_filepaths = [fp for fp in ascii_filepaths
-                           if os.path.isfile(fp)]
-        # Prepend './' to files in the top-level directory so that
-        # something like `'/Makefile' in fp` matches in the top-level
-        # directory as well as in subdirectories.
-        return [fp if os.path.dirname(fp) else os.path.join(os.curdir, fp)
-                for fp in ascii_filepaths]
-
-    def check_files(self):
-        """Check all files for all issues."""
-        for issue_to_check in self.issues_to_check:
-            for filepath in self.collect_files():
-                if issue_to_check.should_check_file(filepath):
-                    issue_to_check.check_file_for_issue(filepath)
-
-    def output_issues(self):
-        """Log the issues found and their locations.
-
-        Return 1 if there were issues, 0 otherwise.
-        """
-        integrity_return_code = 0
-        for issue_to_check in self.issues_to_check:
-            if issue_to_check.files_with_issues:
-                integrity_return_code = 1
-            issue_to_check.output_file_issues(self.logger)
-        return integrity_return_code
-
-
-def run_main():
-    parser = argparse.ArgumentParser(description=__doc__)
-    parser.add_argument(
-        "-l", "--log_file", type=str, help="path to optional output log",
-    )
-    check_args = parser.parse_args()
-    integrity_check = IntegrityChecker(check_args.log_file)
-    integrity_check.check_files()
-    return_code = integrity_check.output_issues()
-    sys.exit(return_code)
-
-
-if __name__ == "__main__":
-    run_main()
diff --git a/tests/scripts/check_names.py b/tests/scripts/check_names.py
deleted file mode 100755
index 3310cc6..0000000
--- a/tests/scripts/check_names.py
+++ /dev/null
@@ -1,965 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright The Mbed TLS Contributors
-# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
-
-"""
-This script confirms that the naming of all symbols and identifiers in Mbed TLS
-are consistent with the house style and are also self-consistent. It only runs
-on Linux and macOS since it depends on nm.
-
-It contains two major Python classes, CodeParser and NameChecker. They both have
-a comprehensive "run-all" function (comprehensive_parse() and perform_checks())
-but the individual functions can also be used for specific needs.
-
-CodeParser makes heavy use of regular expressions to parse the code, and is
-dependent on the current code formatting. Many Python C parser libraries require
-preprocessed C code, which means no macro parsing. Compiler tools are also not
-very helpful when we want the exact location in the original source (which
-becomes impossible when e.g. comments are stripped).
-
-NameChecker performs the following checks:
-
-- All exported and available symbols in the library object files, are explicitly
-  declared in the header files. This uses the nm command.
-- All macros, constants, and identifiers (function names, struct names, etc)
-  follow the required regex pattern.
-- Typo checking: All words that begin with MBED|PSA exist as macros or constants.
-
-The script returns 0 on success, 1 on test failure, and 2 if there is a script
-error. It must be run from Mbed TLS root.
-"""
-
-import abc
-import argparse
-import fnmatch
-import glob
-import textwrap
-import os
-import sys
-import traceback
-import re
-import enum
-import shutil
-import subprocess
-import logging
-
-import scripts_path # pylint: disable=unused-import
-from mbedtls_framework import build_tree
-
-
-# Naming patterns to check against. These are defined outside the NameCheck
-# class for ease of modification.
-PUBLIC_MACRO_PATTERN = r"^(MBEDTLS|PSA)_[0-9A-Z_]*[0-9A-Z]$"
-INTERNAL_MACRO_PATTERN = r"^[0-9A-Za-z_]*[0-9A-Z]$"
-CONSTANTS_PATTERN = PUBLIC_MACRO_PATTERN
-IDENTIFIER_PATTERN = r"^(mbedtls|psa)_[0-9a-z_]*[0-9a-z]$"
-
-class Match(): # pylint: disable=too-few-public-methods
-    """
-    A class representing a match, together with its found position.
-
-    Fields:
-    * filename: the file that the match was in.
-    * line: the full line containing the match.
-    * line_no: the line number.
-    * pos: a tuple of (start, end) positions on the line where the match is.
-    * name: the match itself.
-    """
-    def __init__(self, filename, line, line_no, pos, name):
-        # pylint: disable=too-many-arguments
-        self.filename = filename
-        self.line = line
-        self.line_no = line_no
-        self.pos = pos
-        self.name = name
-
-    def __str__(self):
-        """
-        Return a formatted code listing representation of the erroneous line.
-        """
-        gutter = format(self.line_no, "4d")
-        underline = self.pos[0] * " " + (self.pos[1] - self.pos[0]) * "^"
-
-        return (
-            " {0} |\n".format(" " * len(gutter)) +
-            " {0} | {1}".format(gutter, self.line) +
-            " {0} | {1}\n".format(" " * len(gutter), underline)
-        )
-
-class Problem(abc.ABC): # pylint: disable=too-few-public-methods
-    """
-    An abstract parent class representing a form of static analysis error.
-    It extends an Abstract Base Class, which means it is not instantiable, and
-    it also mandates certain abstract methods to be implemented in subclasses.
-    """
-    # Class variable to control the quietness of all problems
-    quiet = False
-    def __init__(self):
-        self.textwrapper = textwrap.TextWrapper()
-        self.textwrapper.width = 80
-        self.textwrapper.initial_indent = "    > "
-        self.textwrapper.subsequent_indent = "      "
-
-    def __str__(self):
-        """
-        Unified string representation method for all Problems.
-        """
-        if self.__class__.quiet:
-            return self.quiet_output()
-        return self.verbose_output()
-
-    @abc.abstractmethod
-    def quiet_output(self):
-        """
-        The output when --quiet is enabled.
-        """
-        pass
-
-    @abc.abstractmethod
-    def verbose_output(self):
-        """
-        The default output with explanation and code snippet if appropriate.
-        """
-        pass
-
-class SymbolNotInHeader(Problem): # pylint: disable=too-few-public-methods
-    """
-    A problem that occurs when an exported/available symbol in the object file
-    is not explicitly declared in header files. Created with
-    NameCheck.check_symbols_declared_in_header()
-
-    Fields:
-    * symbol_name: the name of the symbol.
-    """
-    def __init__(self, symbol_name):
-        self.symbol_name = symbol_name
-        Problem.__init__(self)
-
-    def quiet_output(self):
-        return "{0}".format(self.symbol_name)
-
-    def verbose_output(self):
-        return self.textwrapper.fill(
-            "'{0}' was found as an available symbol in the output of nm, "
-            "however it was not declared in any header files."
-            .format(self.symbol_name))
-
-class PatternMismatch(Problem): # pylint: disable=too-few-public-methods
-    """
-    A problem that occurs when something doesn't match the expected pattern.
-    Created with NameCheck.check_match_pattern()
-
-    Fields:
-    * pattern: the expected regex pattern
-    * match: the Match object in question
-    """
-    def __init__(self, pattern, match):
-        self.pattern = pattern
-        self.match = match
-        Problem.__init__(self)
-
-
-    def quiet_output(self):
-        return (
-            "{0}:{1}:{2}"
-            .format(self.match.filename, self.match.line_no, self.match.name)
-        )
-
-    def verbose_output(self):
-        return self.textwrapper.fill(
-            "{0}:{1}: '{2}' does not match the required pattern '{3}'."
-            .format(
-                self.match.filename,
-                self.match.line_no,
-                self.match.name,
-                self.pattern
-            )
-        ) + "\n" + str(self.match)
-
-class Typo(Problem): # pylint: disable=too-few-public-methods
-    """
-    A problem that occurs when a word using MBED or PSA doesn't
-    appear to be defined as constants nor enum values. Created with
-    NameCheck.check_for_typos()
-
-    Fields:
-    * match: the Match object of the MBED|PSA name in question.
-    """
-    def __init__(self, match):
-        self.match = match
-        Problem.__init__(self)
-
-    def quiet_output(self):
-        return (
-            "{0}:{1}:{2}"
-            .format(self.match.filename, self.match.line_no, self.match.name)
-        )
-
-    def verbose_output(self):
-        return self.textwrapper.fill(
-            "{0}:{1}: '{2}' looks like a typo. It was not found in any "
-            "macros or any enums. If this is not a typo, put "
-            "//no-check-names after it."
-            .format(self.match.filename, self.match.line_no, self.match.name)
-        ) + "\n" + str(self.match)
-
-class CodeParser():
-    """
-    Class for retrieving files and parsing the code. This can be used
-    independently of the checks that NameChecker performs, for example for
-    list_internal_identifiers.py.
-    """
-    def __init__(self, log):
-        self.log = log
-        build_tree.check_repo_path()
-
-        # Memo for storing "glob expression": set(filepaths)
-        self.files = {}
-
-        # Globally excluded filenames.
-        # Note that "*" can match directory separators in exclude lists.
-        self.excluded_files = ["*/bn_mul", "*/compat-2.x.h"]
-
-    def comprehensive_parse(self):
-        """
-        Comprehensive ("default") function to call each parsing function and
-        retrieve various elements of the code, together with the source location.
-
-        Returns a dict of parsed item key to the corresponding List of Matches.
-        """
-        self.log.info("Parsing source code...")
-        self.log.debug(
-            "The following files are excluded from the search: {}"
-            .format(str(self.excluded_files))
-        )
-
-        all_macros = {"public": [], "internal": [], "private":[]}
-        all_macros["public"] = self.parse_macros([
-            "include/mbedtls/*.h",
-            "include/psa/*.h",
-            "3rdparty/everest/include/everest/everest.h",
-            "3rdparty/everest/include/everest/x25519.h"
-        ])
-        all_macros["internal"] = self.parse_macros([
-            "library/*.h",
-            "framework/tests/include/test/drivers/*.h",
-        ])
-        all_macros["private"] = self.parse_macros([
-            "library/*.c",
-        ])
-        enum_consts = self.parse_enum_consts([
-            "include/mbedtls/*.h",
-            "include/psa/*.h",
-            "library/*.h",
-            "library/*.c",
-            "3rdparty/everest/include/everest/everest.h",
-            "3rdparty/everest/include/everest/x25519.h"
-        ])
-        identifiers, excluded_identifiers = self.parse_identifiers([
-            "include/mbedtls/*.h",
-            "include/psa/*.h",
-            "library/*.h",
-            "3rdparty/everest/include/everest/everest.h",
-            "3rdparty/everest/include/everest/x25519.h"
-        ], ["3rdparty/p256-m/p256-m/p256-m.h"])
-        mbed_psa_words = self.parse_mbed_psa_words([
-            "include/mbedtls/*.h",
-            "include/psa/*.h",
-            "library/*.h",
-            "3rdparty/everest/include/everest/everest.h",
-            "3rdparty/everest/include/everest/x25519.h",
-            "library/*.c",
-            "3rdparty/everest/library/everest.c",
-            "3rdparty/everest/library/x25519.c"
-        ], ["library/psa_crypto_driver_wrappers.h"])
-        symbols = self.parse_symbols()
-
-        # Remove identifier macros like mbedtls_printf or mbedtls_calloc
-        identifiers_justname = [x.name for x in identifiers]
-        actual_macros = {"public": [], "internal": []}
-        for scope in actual_macros:
-            for macro in all_macros[scope]:
-                if macro.name not in identifiers_justname:
-                    actual_macros[scope].append(macro)
-
-        self.log.debug("Found:")
-        # Aligns the counts on the assumption that none exceeds 4 digits
-        for scope in actual_macros:
-            self.log.debug("  {:4} Total {} Macros"
-                           .format(len(all_macros[scope]), scope))
-            self.log.debug("  {:4} {} Non-identifier Macros"
-                           .format(len(actual_macros[scope]), scope))
-        self.log.debug("  {:4} Enum Constants".format(len(enum_consts)))
-        self.log.debug("  {:4} Identifiers".format(len(identifiers)))
-        self.log.debug("  {:4} Exported Symbols".format(len(symbols)))
-        return {
-            "public_macros": actual_macros["public"],
-            "internal_macros": actual_macros["internal"],
-            "private_macros": all_macros["private"],
-            "enum_consts": enum_consts,
-            "identifiers": identifiers,
-            "excluded_identifiers": excluded_identifiers,
-            "symbols": symbols,
-            "mbed_psa_words": mbed_psa_words
-        }
-
-    def is_file_excluded(self, path, exclude_wildcards):
-        """Whether the given file path is excluded."""
-        # exclude_wildcards may be None. Also, consider the global exclusions.
-        exclude_wildcards = (exclude_wildcards or []) + self.excluded_files
-        for pattern in exclude_wildcards:
-            if fnmatch.fnmatch(path, pattern):
-                return True
-        return False
-
-    def get_all_files(self, include_wildcards, exclude_wildcards):
-        """
-        Get all files that match any of the included UNIX-style wildcards
-        and filter them into included and excluded lists.
-        While the check_names script is designed only for use on UNIX/macOS
-        (due to nm), this function alone will work fine on Windows even with
-        forward slashes in the wildcard.
-
-        Args:
-        * include_wildcards: a List of shell-style wildcards to match filepaths.
-        * exclude_wildcards: a List of shell-style wildcards to exclude.
-
-        Returns:
-        * inc_files: A List of relative filepaths for included files.
-        * exc_files: A List of relative filepaths for excluded files.
-        """
-        accumulator = set()
-        all_wildcards = include_wildcards + (exclude_wildcards or [])
-        for wildcard in all_wildcards:
-            accumulator = accumulator.union(glob.iglob(wildcard))
-
-        inc_files = []
-        exc_files = []
-        for path in accumulator:
-            if self.is_file_excluded(path, exclude_wildcards):
-                exc_files.append(path)
-            else:
-                inc_files.append(path)
-        return (inc_files, exc_files)
-
-    def get_included_files(self, include_wildcards, exclude_wildcards):
-        """
-        Get all files that match any of the included UNIX-style wildcards.
-        While the check_names script is designed only for use on UNIX/macOS
-        (due to nm), this function alone will work fine on Windows even with
-        forward slashes in the wildcard.
-
-        Args:
-        * include_wildcards: a List of shell-style wildcards to match filepaths.
-        * exclude_wildcards: a List of shell-style wildcards to exclude.
-
-        Returns a List of relative filepaths.
-        """
-        accumulator = set()
-
-        for include_wildcard in include_wildcards:
-            accumulator = accumulator.union(glob.iglob(include_wildcard))
-
-        return list(path for path in accumulator
-                    if not self.is_file_excluded(path, exclude_wildcards))
-
-    def parse_macros(self, include, exclude=None):
-        """
-        Parse all macros defined by #define preprocessor directives.
-
-        Args:
-        * include: A List of glob expressions to look for files through.
-        * exclude: A List of glob expressions for excluding files.
-
-        Returns a List of Match objects for the found macros.
-        """
-        macro_regex = re.compile(r"# *define +(?P<macro>\w+)")
-        exclusions = (
-            "asm", "inline", "EMIT", "_CRT_SECURE_NO_DEPRECATE", "MULADDC_"
-        )
-
-        files = self.get_included_files(include, exclude)
-        self.log.debug("Looking for macros in {} files".format(len(files)))
-
-        macros = []
-        for header_file in files:
-            with open(header_file, "r", encoding="utf-8") as header:
-                for line_no, line in enumerate(header):
-                    for macro in macro_regex.finditer(line):
-                        if macro.group("macro").startswith(exclusions):
-                            continue
-
-                        macros.append(Match(
-                            header_file,
-                            line,
-                            line_no,
-                            macro.span("macro"),
-                            macro.group("macro")))
-
-        return macros
-
-    def parse_mbed_psa_words(self, include, exclude=None):
-        """
-        Parse all words in the file that begin with MBED|PSA, in and out of
-        macros, comments, anything.
-
-        Args:
-        * include: A List of glob expressions to look for files through.
-        * exclude: A List of glob expressions for excluding files.
-
-        Returns a List of Match objects for words beginning with MBED|PSA.
-        """
-        # Typos of TLS are common, hence the broader check below than MBEDTLS.
-        mbed_regex = re.compile(r"\b(MBED.+?|PSA)_[A-Z0-9_]*")
-        exclusions = re.compile(r"// *no-check-names|#error")
-
-        files = self.get_included_files(include, exclude)
-        self.log.debug(
-            "Looking for MBED|PSA words in {} files"
-            .format(len(files))
-        )
-
-        mbed_psa_words = []
-        for filename in files:
-            with open(filename, "r", encoding="utf-8") as fp:
-                for line_no, line in enumerate(fp):
-                    if exclusions.search(line):
-                        continue
-
-                    for name in mbed_regex.finditer(line):
-                        mbed_psa_words.append(Match(
-                            filename,
-                            line,
-                            line_no,
-                            name.span(0),
-                            name.group(0)))
-
-        return mbed_psa_words
-
-    def parse_enum_consts(self, include, exclude=None):
-        """
-        Parse all enum value constants that are declared.
-
-        Args:
-        * include: A List of glob expressions to look for files through.
-        * exclude: A List of glob expressions for excluding files.
-
-        Returns a List of Match objects for the findings.
-        """
-        files = self.get_included_files(include, exclude)
-        self.log.debug("Looking for enum consts in {} files".format(len(files)))
-
-        # Emulate a finite state machine to parse enum declarations.
-        # OUTSIDE_KEYWORD = outside the enum keyword
-        # IN_BRACES = inside enum opening braces
-        # IN_BETWEEN = between enum keyword and opening braces
-        states = enum.Enum("FSM", ["OUTSIDE_KEYWORD", "IN_BRACES", "IN_BETWEEN"])
-        enum_consts = []
-        for header_file in files:
-            state = states.OUTSIDE_KEYWORD
-            with open(header_file, "r", encoding="utf-8") as header:
-                for line_no, line in enumerate(header):
-                    # Match typedefs and brackets only when they are at the
-                    # beginning of the line -- if they are indented, they might
-                    # be sub-structures within structs, etc.
-                    optional_c_identifier = r"([_a-zA-Z][_a-zA-Z0-9]*)?"
-                    if (state == states.OUTSIDE_KEYWORD and
-                            re.search(r"^(typedef +)?enum " + \
-                                    optional_c_identifier + \
-                                    r" *{", line)):
-                        state = states.IN_BRACES
-                    elif (state == states.OUTSIDE_KEYWORD and
-                          re.search(r"^(typedef +)?enum", line)):
-                        state = states.IN_BETWEEN
-                    elif (state == states.IN_BETWEEN and
-                          re.search(r"^{", line)):
-                        state = states.IN_BRACES
-                    elif (state == states.IN_BRACES and
-                          re.search(r"^}", line)):
-                        state = states.OUTSIDE_KEYWORD
-                    elif (state == states.IN_BRACES and
-                          not re.search(r"^ *#", line)):
-                        enum_const = re.search(r"^ *(?P<enum_const>\w+)", line)
-                        if not enum_const:
-                            continue
-
-                        enum_consts.append(Match(
-                            header_file,
-                            line,
-                            line_no,
-                            enum_const.span("enum_const"),
-                            enum_const.group("enum_const")))
-
-        return enum_consts
-
-    IGNORED_CHUNK_REGEX = re.compile('|'.join([
-        r'/\*.*?\*/', # block comment entirely on one line
-        r'//.*', # line comment
-        r'(?P<string>")(?:[^\\\"]|\\.)*"', # string literal
-    ]))
-
-    def strip_comments_and_literals(self, line, in_block_comment):
-        """Strip comments and string literals from line.
-
-        Continuation lines are not supported.
-
-        If in_block_comment is true, assume that the line starts inside a
-        block comment.
-
-        Return updated values of (line, in_block_comment) where:
-        * Comments in line have been replaced by a space (or nothing at the
-          start or end of the line).
-        * String contents have been removed.
-        * in_block_comment indicates whether the line ends inside a block
-          comment that continues on the next line.
-        """
-
-        # Terminate current multiline comment?
-        if in_block_comment:
-            m = re.search(r"\*/", line)
-            if m:
-                in_block_comment = False
-                line = line[m.end(0):]
-            else:
-                return '', True
-
-        # Remove full comments and string literals.
-        # Do it all together to handle cases like "/*" correctly.
-        # Note that continuation lines are not supported.
-        line = re.sub(self.IGNORED_CHUNK_REGEX,
-                      lambda s: '""' if s.group('string') else ' ',
-                      line)
-
-        # Start an unfinished comment?
-        # (If `/*` was part of a complete comment, it's already been removed.)
-        m = re.search(r"/\*", line)
-        if m:
-            in_block_comment = True
-            line = line[:m.start(0)]
-
-        return line, in_block_comment
-
-    IDENTIFIER_REGEX = re.compile('|'.join([
-        # Match " something(a" or " *something(a". Functions.
-        # Assumptions:
-        # - function definition from return type to one of its arguments is
-        #   all on one line
-        # - function definition line only contains alphanumeric, asterisk,
-        #   underscore, and open bracket
-        r".* \**(\w+) *\( *\w",
-        # Match "(*something)(".
-        r".*\( *\* *(\w+) *\) *\(",
-        # Match names of named data structures.
-        r"(?:typedef +)?(?:struct|union|enum) +(\w+)(?: *{)?$",
-        # Match names of typedef instances, after closing bracket.
-        r"}? *(\w+)[;[].*",
-    ]))
-    # The regex below is indented for clarity.
-    EXCLUSION_LINES = re.compile("|".join([
-        r"extern +\"C\"",
-        r"(typedef +)?(struct|union|enum)( *{)?$",
-        r"} *;?$",
-        r"$",
-        r"//",
-        r"#",
-    ]))
-
-    def parse_identifiers_in_file(self, header_file, identifiers):
-        """
-        Parse all lines of a header where a function/enum/struct/union/typedef
-        identifier is declared, based on some regex and heuristics. Highly
-        dependent on formatting style.
-
-        Append found matches to the list ``identifiers``.
-        """
-
-        with open(header_file, "r", encoding="utf-8") as header:
-            in_block_comment = False
-            # The previous line variable is used for concatenating lines
-            # when identifiers are formatted and spread across multiple
-            # lines.
-            previous_line = ""
-
-            for line_no, line in enumerate(header):
-                line, in_block_comment = \
-                    self.strip_comments_and_literals(line, in_block_comment)
-
-                if self.EXCLUSION_LINES.match(line):
-                    previous_line = ""
-                    continue
-
-                # If the line contains only space-separated alphanumeric
-                # characters (or underscore, asterisk, or open parenthesis),
-                # and nothing else, high chance it's a declaration that
-                # continues on the next line
-                if re.search(r"^([\w\*\(]+\s+)+$", line):
-                    previous_line += line
-                    continue
-
-                # If previous line seemed to start an unfinished declaration
-                # (as above), concat and treat them as one.
-                if previous_line:
-                    line = previous_line.strip() + " " + line.strip() + "\n"
-                    previous_line = ""
-
-                # Skip parsing if line has a space in front = heuristic to
-                # skip function argument lines (highly subject to formatting
-                # changes)
-                if line[0] == " ":
-                    continue
-
-                identifier = self.IDENTIFIER_REGEX.search(line)
-
-                if not identifier:
-                    continue
-
-                # Find the group that matched, and append it
-                for group in identifier.groups():
-                    if not group:
-                        continue
-
-                    identifiers.append(Match(
-                        header_file,
-                        line,
-                        line_no,
-                        identifier.span(),
-                        group))
-
-    def parse_identifiers(self, include, exclude=None):
-        """
-        Parse all lines of a header where a function/enum/struct/union/typedef
-        identifier is declared, based on some regex and heuristics. Highly
-        dependent on formatting style. Identifiers in excluded files are still
-        parsed
-
-        Args:
-        * include: A List of glob expressions to look for files through.
-        * exclude: A List of glob expressions for excluding files.
-
-        Returns: a Tuple of two Lists of Match objects with identifiers.
-        * included_identifiers: A List of Match objects with identifiers from
-          included files.
-        * excluded_identifiers: A List of Match objects with identifiers from
-          excluded files.
-        """
-
-        included_files, excluded_files = \
-            self.get_all_files(include, exclude)
-
-        self.log.debug("Looking for included identifiers in {} files".format \
-            (len(included_files)))
-
-        included_identifiers = []
-        excluded_identifiers = []
-        for header_file in included_files:
-            self.parse_identifiers_in_file(header_file, included_identifiers)
-        for header_file in excluded_files:
-            self.parse_identifiers_in_file(header_file, excluded_identifiers)
-
-        return (included_identifiers, excluded_identifiers)
-
-    def parse_symbols(self):
-        """
-        Compile the Mbed TLS libraries, and parse the TLS, Crypto, and x509
-        object files using nm to retrieve the list of referenced symbols.
-        Exceptions thrown here are rethrown because they would be critical
-        errors that void several tests, and thus needs to halt the program. This
-        is explicitly done for clarity.
-
-        Returns a List of unique symbols defined and used in the libraries.
-        """
-        self.log.info("Compiling...")
-        symbols = []
-
-        # Back up the config and atomically compile with the full configuration.
-        shutil.copy(
-            "include/mbedtls/mbedtls_config.h",
-            "include/mbedtls/mbedtls_config.h.bak"
-        )
-        try:
-            # Use check=True in all subprocess calls so that failures are raised
-            # as exceptions and logged.
-            subprocess.run(
-                ["python3", "scripts/config.py", "full"],
-                universal_newlines=True,
-                check=True
-            )
-            my_environment = os.environ.copy()
-            my_environment["CFLAGS"] = "-fno-asynchronous-unwind-tables"
-            # Run make clean separately to lib to prevent unwanted behavior when
-            # make is invoked with parallelism.
-            subprocess.run(
-                ["make", "clean"],
-                universal_newlines=True,
-                check=True
-            )
-            subprocess.run(
-                ["make", "lib"],
-                env=my_environment,
-                universal_newlines=True,
-                stdout=subprocess.PIPE,
-                stderr=subprocess.STDOUT,
-                check=True
-            )
-
-            # Perform object file analysis using nm
-            symbols = self.parse_symbols_from_nm([
-                "library/libmbedcrypto.a",
-                "library/libmbedtls.a",
-                "library/libmbedx509.a"
-            ])
-
-            subprocess.run(
-                ["make", "clean"],
-                universal_newlines=True,
-                check=True
-            )
-        except subprocess.CalledProcessError as error:
-            self.log.debug(error.output)
-            raise error
-        finally:
-            # Put back the original config regardless of there being errors.
-            # Works also for keyboard interrupts.
-            shutil.move(
-                "include/mbedtls/mbedtls_config.h.bak",
-                "include/mbedtls/mbedtls_config.h"
-            )
-
-        return symbols
-
-    def parse_symbols_from_nm(self, object_files):
-        """
-        Run nm to retrieve the list of referenced symbols in each object file.
-        Does not return the position data since it is of no use.
-
-        Args:
-        * object_files: a List of compiled object filepaths to search through.
-
-        Returns a List of unique symbols defined and used in any of the object
-        files.
-        """
-        nm_undefined_regex = re.compile(r"^\S+: +U |^$|^\S+:$")
-        nm_valid_regex = re.compile(r"^\S+( [0-9A-Fa-f]+)* . _*(?P<symbol>\w+)")
-        exclusions = ("FStar", "Hacl")
-
-        symbols = []
-
-        # Gather all outputs of nm
-        nm_output = ""
-        for lib in object_files:
-            nm_output += subprocess.run(
-                ["nm", "-og", lib],
-                universal_newlines=True,
-                stdout=subprocess.PIPE,
-                stderr=subprocess.STDOUT,
-                check=True
-            ).stdout
-
-        for line in nm_output.splitlines():
-            if not nm_undefined_regex.search(line):
-                symbol = nm_valid_regex.search(line)
-                if (symbol and not symbol.group("symbol").startswith(exclusions)):
-                    symbols.append(symbol.group("symbol"))
-                else:
-                    self.log.error(line)
-
-        return symbols
-
-class NameChecker():
-    """
-    Representation of the core name checking operation performed by this script.
-    """
-    def __init__(self, parse_result, log):
-        self.parse_result = parse_result
-        self.log = log
-
-    def perform_checks(self, quiet=False):
-        """
-        A comprehensive checker that performs each check in order, and outputs
-        a final verdict.
-
-        Args:
-        * quiet: whether to hide detailed problem explanation.
-        """
-        self.log.info("=============")
-        Problem.quiet = quiet
-        problems = 0
-        problems += self.check_symbols_declared_in_header()
-
-        pattern_checks = [
-            ("public_macros", PUBLIC_MACRO_PATTERN),
-            ("internal_macros", INTERNAL_MACRO_PATTERN),
-            ("enum_consts", CONSTANTS_PATTERN),
-            ("identifiers", IDENTIFIER_PATTERN)
-        ]
-        for group, check_pattern in pattern_checks:
-            problems += self.check_match_pattern(group, check_pattern)
-
-        problems += self.check_for_typos()
-
-        self.log.info("=============")
-        if problems > 0:
-            self.log.info("FAIL: {0} problem(s) to fix".format(str(problems)))
-            if quiet:
-                self.log.info("Remove --quiet to see explanations.")
-            else:
-                self.log.info("Use --quiet for minimal output.")
-            return 1
-        else:
-            self.log.info("PASS")
-            return 0
-
-    def check_symbols_declared_in_header(self):
-        """
-        Perform a check that all detected symbols in the library object files
-        are properly declared in headers.
-        Assumes parse_names_in_source() was called before this.
-
-        Returns the number of problems that need fixing.
-        """
-        problems = []
-        all_identifiers = self.parse_result["identifiers"] +  \
-            self.parse_result["excluded_identifiers"]
-
-        for symbol in self.parse_result["symbols"]:
-            found_symbol_declared = False
-            for identifier_match in all_identifiers:
-                if symbol == identifier_match.name:
-                    found_symbol_declared = True
-                    break
-
-            if not found_symbol_declared:
-                problems.append(SymbolNotInHeader(symbol))
-
-        self.output_check_result("All symbols in header", problems)
-        return len(problems)
-
-    def check_match_pattern(self, group_to_check, check_pattern):
-        """
-        Perform a check that all items of a group conform to a regex pattern.
-        Assumes parse_names_in_source() was called before this.
-
-        Args:
-        * group_to_check: string key to index into self.parse_result.
-        * check_pattern: the regex to check against.
-
-        Returns the number of problems that need fixing.
-        """
-        problems = []
-
-        for item_match in self.parse_result[group_to_check]:
-            if not re.search(check_pattern, item_match.name):
-                problems.append(PatternMismatch(check_pattern, item_match))
-            # Double underscore should not be used for names
-            if re.search(r".*__.*", item_match.name):
-                problems.append(
-                    PatternMismatch("no double underscore allowed", item_match))
-
-        self.output_check_result(
-            "Naming patterns of {}".format(group_to_check),
-            problems)
-        return len(problems)
-
-    def check_for_typos(self):
-        """
-        Perform a check that all words in the source code beginning with MBED are
-        either defined as macros, or as enum constants.
-        Assumes parse_names_in_source() was called before this.
-
-        Returns the number of problems that need fixing.
-        """
-        problems = []
-
-        # Set comprehension, equivalent to a list comprehension wrapped by set()
-        all_caps_names = {
-            match.name
-            for match
-            in self.parse_result["public_macros"] +
-            self.parse_result["internal_macros"] +
-            self.parse_result["private_macros"] +
-            self.parse_result["enum_consts"]
-            }
-        typo_exclusion = re.compile(r"XXX|__|_$|^MBEDTLS_.*CONFIG_FILE$|"
-                                    r"MBEDTLS_TEST_LIBTESTDRIVER*|"
-                                    r"PSA_CRYPTO_DRIVER_TEST")
-
-        for name_match in self.parse_result["mbed_psa_words"]:
-            found = name_match.name in all_caps_names
-
-            # Since MBEDTLS_PSA_ACCEL_XXX defines are defined by the
-            # PSA driver, they will not exist as macros. However, they
-            # should still be checked for typos using the equivalent
-            # BUILTINs that exist.
-            if "MBEDTLS_PSA_ACCEL_" in name_match.name:
-                found = name_match.name.replace(
-                    "MBEDTLS_PSA_ACCEL_",
-                    "MBEDTLS_PSA_BUILTIN_") in all_caps_names
-
-            if not found and not typo_exclusion.search(name_match.name):
-                problems.append(Typo(name_match))
-
-        self.output_check_result("Likely typos", problems)
-        return len(problems)
-
-    def output_check_result(self, name, problems):
-        """
-        Write out the PASS/FAIL status of a performed check depending on whether
-        there were problems.
-
-        Args:
-        * name: the name of the test
-        * problems: a List of encountered Problems
-        """
-        if problems:
-            self.log.info("{}: FAIL\n".format(name))
-            for problem in problems:
-                self.log.warning(str(problem))
-        else:
-            self.log.info("{}: PASS".format(name))
-
-def main():
-    """
-    Perform argument parsing, and create an instance of CodeParser and
-    NameChecker to begin the core operation.
-    """
-    parser = argparse.ArgumentParser(
-        formatter_class=argparse.RawDescriptionHelpFormatter,
-        description=(
-            "This script confirms that the naming of all symbols and identifiers "
-            "in Mbed TLS are consistent with the house style and are also "
-            "self-consistent.\n\n"
-            "Expected to be run from the Mbed TLS root directory.")
-    )
-    parser.add_argument(
-        "-v", "--verbose",
-        action="store_true",
-        help="show parse results"
-    )
-    parser.add_argument(
-        "-q", "--quiet",
-        action="store_true",
-        help="hide unnecessary text, explanations, and highlights"
-    )
-
-    args = parser.parse_args()
-
-    # Configure the global logger, which is then passed to the classes below
-    log = logging.getLogger()
-    log.setLevel(logging.DEBUG if args.verbose else logging.INFO)
-    log.addHandler(logging.StreamHandler())
-
-    try:
-        code_parser = CodeParser(log)
-        parse_result = code_parser.comprehensive_parse()
-    except Exception: # pylint: disable=broad-except
-        traceback.print_exc()
-        sys.exit(2)
-
-    name_checker = NameChecker(parse_result, log)
-    return_code = name_checker.perform_checks(quiet=args.quiet)
-
-    sys.exit(return_code)
-
-if __name__ == "__main__":
-    main()
diff --git a/tests/scripts/components-basic-checks.sh b/tests/scripts/components-basic-checks.sh
index 7b60b49..f34ec41 100644
--- a/tests/scripts/components-basic-checks.sh
+++ b/tests/scripts/components-basic-checks.sh
@@ -11,7 +11,7 @@
 
 component_check_recursion () {
     msg "Check: recursion.pl" # < 1s
-    tests/scripts/recursion.pl library/*.c
+    ./framework/scripts/recursion.pl library/*.c
 }
 
 component_check_generated_files () {
@@ -36,18 +36,18 @@
 
 component_check_doxy_blocks () {
     msg "Check: doxygen markup outside doxygen blocks" # < 1s
-    tests/scripts/check-doxy-blocks.pl
+    ./framework/scripts/check-doxy-blocks.pl
 }
 
 component_check_files () {
     msg "Check: file sanity checks (permissions, encodings)" # < 1s
-    tests/scripts/check_files.py
+    framework/scripts/check_files.py
 }
 
 component_check_changelog () {
     msg "Check: changelog entries" # < 1s
     rm -f ChangeLog.new
-    scripts/assemble_changelog.py -o ChangeLog.new
+    ./framework/scripts/assemble_changelog.py -o ChangeLog.new
     if [ -e ChangeLog.new ]; then
         # Show the diff for information. It isn't an error if the diff is
         # non-empty.
@@ -58,7 +58,7 @@
 
 component_check_names () {
     msg "Check: declared and exported names (builds the library)" # < 3s
-    tests/scripts/check_names.py -v
+    framework/scripts/check_names.py -v
 }
 
 component_check_test_cases () {
@@ -132,12 +132,12 @@
 
 component_check_doxygen_warnings () {
     msg "Check: doxygen warnings (builds the documentation)" # ~ 3s
-    tests/scripts/doxygen.sh
+    ./framework/scripts/doxygen.sh
 }
 
 component_check_code_style () {
     msg "Check C code style"
-    ./scripts/code_style.py
+    ./framework/scripts/code_style.py
 }
 
 support_check_code_style () {
@@ -149,7 +149,7 @@
 
 component_check_python_files () {
     msg "Lint: Python scripts"
-    tests/scripts/check-python-files.sh
+    ./framework/scripts/check-python-files.sh
 }
 
 component_check_test_helpers () {
diff --git a/tests/scripts/components-build-system.sh b/tests/scripts/components-build-system.sh
index 0db7885..5dc296a 100644
--- a/tests/scripts/components-build-system.sh
+++ b/tests/scripts/components-build-system.sh
@@ -13,7 +13,7 @@
     msg "build/test: make shared" # ~ 40s
     make SHARED=1 TEST_CPP=1 all check
     ldd programs/util/strerror | grep libmbedcrypto
-    programs/test/dlopen_demo.sh
+    $FRAMEWORK/tests/programs/dlopen_demo.sh
 }
 
 component_test_cmake_shared () {
@@ -22,7 +22,7 @@
     make
     ldd programs/util/strerror | grep libmbedcrypto
     make test
-    programs/test/dlopen_demo.sh
+    $FRAMEWORK/tests/programs/dlopen_demo.sh
 }
 
 support_test_cmake_out_of_source () {
@@ -115,7 +115,11 @@
     make
     ./cmake_package
     if [[ "$OSTYPE" == linux* ]]; then
-        PKG_CONFIG_PATH="${build_variant_dir}/mbedtls/pkgconfig" ${root_dir}/tests/scripts/pkgconfig.sh
+        PKG_CONFIG_PATH="${build_variant_dir}/mbedtls/pkgconfig" \
+        ${root_dir}/framework/scripts/pkgconfig.sh \
+        mbedtls mbedx509 mbedcrypto
+        # These are the EXPECTED package names. Renaming these could break
+        # consumers of pkg-config, consider carefully.
     fi
 }
 
@@ -212,4 +216,3 @@
 support_build_cmake_programs_no_testing () {
     support_test_cmake_out_of_source
 }
-
diff --git a/tests/scripts/components-compiler.sh b/tests/scripts/components-compiler.sh
index a4b2323..c0edd20 100644
--- a/tests/scripts/components-compiler.sh
+++ b/tests/scripts/components-compiler.sh
@@ -135,7 +135,7 @@
         for compiler in clang gcc; do
             msg "test: $compiler $optimization_flag, mbedtls_platform_zeroize()"
             make programs CC="$compiler" DEBUG=1 CFLAGS="$optimization_flag"
-            gdb -ex "$gdb_disable_aslr" -x tests/scripts/test_zeroize.gdb -nw -batch -nx 2>&1 | tee test_zeroize.log
+            gdb -ex "$gdb_disable_aslr" -x $FRAMEWORK/tests/programs/test_zeroize.gdb -nw -batch -nx 2>&1 | tee test_zeroize.log
             grep "The buffer was correctly zeroized" test_zeroize.log
             not grep -i "error" test_zeroize.log
             rm -f test_zeroize.log
diff --git a/tests/scripts/components-compliance.sh b/tests/scripts/components-compliance.sh
index 38bcd01..ec61b10 100644
--- a/tests/scripts/components-compliance.sh
+++ b/tests/scripts/components-compliance.sh
@@ -15,7 +15,7 @@
     CC=gcc make -C library libmbedcrypto.a
 
     msg "unit test: test_psa_compliance.py"
-    CC=gcc ./tests/scripts/test_psa_compliance.py
+    CC=gcc $FRAMEWORK/scripts/test_psa_compliance.py
 }
 
 support_test_psa_compliance () {
diff --git a/tests/scripts/components-configuration.sh b/tests/scripts/components-configuration.sh
index feedbcf..72e7a86 100644
--- a/tests/scripts/components-configuration.sh
+++ b/tests/scripts/components-configuration.sh
@@ -148,7 +148,7 @@
     tests/scripts/run_demos.py
 
     msg "test: psa_constant_names (full config, clang)" # ~ 1s
-    tests/scripts/test_psa_constant_names.py
+    $FRAMEWORK/scripts/test_psa_constant_names.py
 
     msg "test: ssl-opt.sh default, ECJPAKE, SSL async (full config)" # ~ 1s
     tests/ssl-opt.sh -f 'Default\|ECJPAKE\|SSL async private'
@@ -175,6 +175,9 @@
 
     msg "test: ensure that X509 has no direct dependency on BIGNUM_C"
     not grep mbedtls_mpi library/libmbedx509.a
+
+    msg "test: ssl-opt.sh authentication, full_no_deprecated config" # ~ 10s
+    tests/ssl-opt.sh -f 'Default\|Authentication'
 }
 
 component_test_full_no_deprecated_deprecated_warning () {
diff --git a/tests/scripts/doxygen.sh b/tests/scripts/doxygen.sh
deleted file mode 100755
index b6a1d45..0000000
--- a/tests/scripts/doxygen.sh
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/bin/sh
-
-# Make sure the doxygen documentation builds without warnings
-#
-# Copyright The Mbed TLS Contributors
-# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
-
-# Abort on errors (and uninitialised variables)
-set -eu
-
-if [ -d library -a -d include -a -d tests ]; then :; else
-    echo "Must be run from Mbed TLS root" >&2
-    exit 1
-fi
-
-if scripts/apidoc_full.sh > doc.out 2>doc.err; then :; else
-    cat doc.err
-    echo "FAIL" >&2
-    exit 1;
-fi
-
-cat doc.out doc.err | \
-    grep -v "warning: ignoring unsupported tag" \
-    > doc.filtered
-
-if grep -E "(warning|error):" doc.filtered; then
-    echo "FAIL" >&2
-    exit 1;
-fi
-
-make apidoc_clean
-rm -f doc.out doc.err doc.filtered
diff --git a/tests/scripts/list-identifiers.sh b/tests/scripts/list-identifiers.sh
index 4ccac23..9032baf 100755
--- a/tests/scripts/list-identifiers.sh
+++ b/tests/scripts/list-identifiers.sh
@@ -49,6 +49,6 @@
 use. It is a thin wrapper around list_internal_identifiers.py.
 
 check-names.sh, which used to depend on this script, has been replaced with
-check_names.py and is now self-complete.
+framework/scripts/check_names.py and is now self-complete.
 EOF
 fi
diff --git a/tests/scripts/list_internal_identifiers.py b/tests/scripts/list_internal_identifiers.py
index b648ce2..9619df7 100755
--- a/tests/scripts/list_internal_identifiers.py
+++ b/tests/scripts/list_internal_identifiers.py
@@ -10,7 +10,8 @@
 exclusion file list for ABI/API checking, since we do not promise compatibility
 for them.
 
-It uses the CodeParser class from check_names.py to perform the parsing.
+It uses the CodeParser class from framework/scripts/check_names.py to perform
+the parsing.
 
 The script returns 0 on success, 1 if there is a script error.
 Must be run from Mbed TLS root.
@@ -18,6 +19,7 @@
 
 import argparse
 import logging
+import scripts_path # pylint: disable=unused-import
 from check_names import CodeParser
 
 def main():
diff --git a/tests/scripts/pkgconfig.sh b/tests/scripts/pkgconfig.sh
deleted file mode 100755
index 2702bfa..0000000
--- a/tests/scripts/pkgconfig.sh
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/bin/sh
-#
-# Copyright The Mbed TLS Contributors
-# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
-#
-# Purpose
-#
-# Test pkgconfig files.
-#
-# For each of the build pkg-config files, .pc files, check that
-# they validate and do some basic sanity testing on the output,
-# i.e. that the strings are non-empty.
-#
-# NOTE: This requires the built pc files to be on the pkg-config
-# search path, this can be controlled with env variable
-# PKG_CONFIG_PATH. See man(1) pkg-config for details.
-#
-
-set -e -u
-
-# These are the EXPECTED package names. Renaming these could break
-# consumers of pkg-config, consider carefully.
-all_pcs="mbedtls mbedx509 mbedcrypto"
-
-for pc in $all_pcs; do
-    printf "testing package config file: ${pc} ... "
-    pkg-config --validate "${pc}"
-    version="$(pkg-config --modversion "${pc}")"
-    test -n "$version"
-    cflags="$(pkg-config --cflags "${pc}")"
-    test -n "$cflags"
-    libs="$(pkg-config --libs "${pc}")"
-    test -n "$libs"
-    printf "passed\n"
-done
-
-exit 0
diff --git a/tests/scripts/recursion.pl b/tests/scripts/recursion.pl
deleted file mode 100755
index 3cdeff7..0000000
--- a/tests/scripts/recursion.pl
+++ /dev/null
@@ -1,47 +0,0 @@
-#!/usr/bin/env perl
-
-# Find functions making recursive calls to themselves.
-# (Multiple recursion where a() calls b() which calls a() not covered.)
-#
-# When the recursion depth might depend on data controlled by the attacker in
-# an unbounded way, those functions should use iteration instead.
-#
-# Typical usage: scripts/recursion.pl library/*.c
-#
-# Copyright The Mbed TLS Contributors
-# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
-
-use warnings;
-use strict;
-
-use utf8;
-use open qw(:std utf8);
-
-# exclude functions that are ok:
-# - mpi_write_hlp: bounded by size of mbedtls_mpi, a compile-time constant
-# - x509_crt_verify_child: bounded by MBEDTLS_X509_MAX_INTERMEDIATE_CA
-my $known_ok = qr/mpi_write_hlp|x509_crt_verify_child/;
-
-my $cur_name;
-my $inside;
-my @funcs;
-
-die "Usage: $0 file.c [...]\n" unless @ARGV;
-
-while (<>)
-{
-    if( /^[^\/#{}\s]/ && ! /\[.*]/ ) {
-        chomp( $cur_name = $_ ) unless $inside;
-    } elsif( /^{/ && $cur_name ) {
-        $inside = 1;
-        $cur_name =~ s/.* ([^ ]*)\(.*/$1/;
-    } elsif( /^}/ && $inside ) {
-        undef $inside;
-        undef $cur_name;
-    } elsif( $inside && /\b\Q$cur_name\E\([^)]/ ) {
-        push @funcs, $cur_name unless /$known_ok/;
-    }
-}
-
-print "$_\n" for @funcs;
-exit @funcs;
diff --git a/tests/scripts/test_psa_compliance.py b/tests/scripts/test_psa_compliance.py
deleted file mode 100755
index f7d1895..0000000
--- a/tests/scripts/test_psa_compliance.py
+++ /dev/null
@@ -1,159 +0,0 @@
-#!/usr/bin/env python3
-"""Run the PSA Crypto API compliance test suite.
-Clone the repo and check out the commit specified by PSA_ARCH_TEST_REPO and PSA_ARCH_TEST_REF,
-then compile and run the test suite. The clone is stored at <repository root>/psa-arch-tests.
-Known defects in either the test suite or mbedtls / TF-PSA-Crypto - identified by their test
-number - are ignored, while unexpected failures AND successes are reported as errors, to help
-keep the list of known defects as up to date as possible.
-"""
-
-# Copyright The Mbed TLS Contributors
-# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
-
-import argparse
-import os
-import re
-import shutil
-import subprocess
-import sys
-from typing import List
-
-#pylint: disable=unused-import
-import scripts_path
-from mbedtls_framework import build_tree
-
-# PSA Compliance tests we expect to fail due to known defects in Mbed TLS /
-# TF-PSA-Crypto (or the test suite).
-# The test numbers correspond to the numbers used by the console output of the test suite.
-# Test number 2xx corresponds to the files in the folder
-# psa-arch-tests/api-tests/dev_apis/crypto/test_c0xx
-EXPECTED_FAILURES = {} # type: dict
-
-PSA_ARCH_TESTS_REPO = 'https://github.com/ARM-software/psa-arch-tests.git'
-PSA_ARCH_TESTS_REF = 'v23.06_API1.5_ADAC_EAC'
-
-#pylint: disable=too-many-branches,too-many-statements,too-many-locals
-def main(library_build_dir: str):
-    root_dir = os.getcwd()
-
-    in_tf_psa_crypto_repo = build_tree.looks_like_tf_psa_crypto_root(root_dir)
-
-    crypto_name = build_tree.crypto_library_filename(root_dir)
-    library_subdir = build_tree.crypto_core_directory(root_dir, relative=True)
-
-    crypto_lib_filename = (library_build_dir + '/' +
-                           library_subdir + '/' +
-                           'lib' + crypto_name + '.a')
-
-    if not os.path.exists(crypto_lib_filename):
-        #pylint: disable=bad-continuation
-        subprocess.check_call([
-            'cmake', '.',
-                     '-GUnix Makefiles',
-                     '-B' + library_build_dir
-        ])
-        subprocess.check_call(['cmake', '--build', library_build_dir,
-                               '--target', crypto_name])
-
-    psa_arch_tests_dir = 'psa-arch-tests'
-    os.makedirs(psa_arch_tests_dir, exist_ok=True)
-    try:
-        os.chdir(psa_arch_tests_dir)
-
-        # Reuse existing local clone
-        subprocess.check_call(['git', 'init'])
-        subprocess.check_call(['git', 'fetch', PSA_ARCH_TESTS_REPO, PSA_ARCH_TESTS_REF])
-        subprocess.check_call(['git', 'checkout', 'FETCH_HEAD'])
-
-        build_dir = 'api-tests/build'
-        try:
-            shutil.rmtree(build_dir)
-        except FileNotFoundError:
-            pass
-        os.mkdir(build_dir)
-        os.chdir(build_dir)
-
-        extra_includes = (';{}/drivers/builtin/include'.format(root_dir)
-                          if in_tf_psa_crypto_repo else '')
-
-        #pylint: disable=bad-continuation
-        subprocess.check_call([
-            'cmake', '..',
-                     '-GUnix Makefiles',
-                     '-DTARGET=tgt_dev_apis_stdc',
-                     '-DTOOLCHAIN=HOST_GCC',
-                     '-DSUITE=CRYPTO',
-                     '-DPSA_CRYPTO_LIB_FILENAME={}/{}'.format(root_dir,
-                                                              crypto_lib_filename),
-                     ('-DPSA_INCLUDE_PATHS={}/include' + extra_includes).format(root_dir)
-        ])
-        subprocess.check_call(['cmake', '--build', '.'])
-
-        proc = subprocess.Popen(['./psa-arch-tests-crypto'],
-                                bufsize=1, stdout=subprocess.PIPE, universal_newlines=True)
-
-        test_re = re.compile(
-            '^TEST: (?P<test_num>[0-9]*)|'
-            '^TEST RESULT: (?P<test_result>FAILED|PASSED)'
-        )
-        test = -1
-        unexpected_successes = set(EXPECTED_FAILURES)
-        expected_failures = [] # type: List[int]
-        unexpected_failures = [] # type: List[int]
-        if proc.stdout is None:
-            return 1
-
-        for line in proc.stdout:
-            print(line, end='')
-            match = test_re.match(line)
-            if match is not None:
-                groupdict = match.groupdict()
-                test_num = groupdict['test_num']
-                if test_num is not None:
-                    test = int(test_num)
-                elif groupdict['test_result'] == 'FAILED':
-                    try:
-                        unexpected_successes.remove(test)
-                        expected_failures.append(test)
-                        print('Expected failure, ignoring')
-                    except KeyError:
-                        unexpected_failures.append(test)
-                        print('ERROR: Unexpected failure')
-                elif test in unexpected_successes:
-                    print('ERROR: Unexpected success')
-        proc.wait()
-
-        print()
-        print('***** test_psa_compliance.py report ******')
-        print()
-        print('Expected failures:', ', '.join(str(i) for i in expected_failures))
-        print('Unexpected failures:', ', '.join(str(i) for i in unexpected_failures))
-        print('Unexpected successes:', ', '.join(str(i) for i in sorted(unexpected_successes)))
-        print()
-        if unexpected_successes or unexpected_failures:
-            if unexpected_successes:
-                print('Unexpected successes encountered.')
-                print('Please remove the corresponding tests from '
-                      'EXPECTED_FAILURES in tests/scripts/compliance_test.py')
-                print()
-            print('FAILED')
-            return 1
-        else:
-            print('SUCCESS')
-            return 0
-    finally:
-        os.chdir(root_dir)
-
-if __name__ == '__main__':
-    BUILD_DIR = 'out_of_source_build'
-
-    # pylint: disable=invalid-name
-    parser = argparse.ArgumentParser()
-    parser.add_argument('--build-dir', nargs=1,
-                        help='path to Mbed TLS / TF-PSA-Crypto build directory')
-    args = parser.parse_args()
-
-    if args.build_dir is not None:
-        BUILD_DIR = args.build_dir[0]
-
-    sys.exit(main(BUILD_DIR))
diff --git a/tests/scripts/test_psa_constant_names.py b/tests/scripts/test_psa_constant_names.py
deleted file mode 100755
index 86d9e6f..0000000
--- a/tests/scripts/test_psa_constant_names.py
+++ /dev/null
@@ -1,191 +0,0 @@
-#!/usr/bin/env python3
-"""Test the program psa_constant_names.
-Gather constant names from header files and test cases. Compile a C program
-to print out their numerical values, feed these numerical values to
-psa_constant_names, and check that the output is the original name.
-Return 0 if all test cases pass, 1 if the output was not always as expected,
-or 1 (with a Python backtrace) if there was an operational error.
-"""
-
-# Copyright The Mbed TLS Contributors
-# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
-
-import argparse
-from collections import namedtuple
-import os
-import re
-import subprocess
-import sys
-from typing import Iterable, List, Optional, Tuple
-
-import scripts_path # pylint: disable=unused-import
-from mbedtls_framework import c_build_helper
-from mbedtls_framework.macro_collector import InputsForTest, PSAMacroEnumerator
-from mbedtls_framework import typing_util
-
-def gather_inputs(headers: Iterable[str],
-                  test_suites: Iterable[str],
-                  inputs_class=InputsForTest) -> PSAMacroEnumerator:
-    """Read the list of inputs to test psa_constant_names with."""
-    inputs = inputs_class()
-    for header in headers:
-        inputs.parse_header(header)
-    for test_cases in test_suites:
-        inputs.parse_test_cases(test_cases)
-    inputs.add_numerical_values()
-    inputs.gather_arguments()
-    return inputs
-
-def run_c(type_word: str,
-          expressions: Iterable[str],
-          include_path: Optional[str] = None,
-          keep_c: bool = False) -> List[str]:
-    """Generate and run a program to print out numerical values of C expressions."""
-    if type_word == 'status':
-        cast_to = 'long'
-        printf_format = '%ld'
-    else:
-        cast_to = 'unsigned long'
-        printf_format = '0x%08lx'
-    return c_build_helper.get_c_expression_values(
-        cast_to, printf_format,
-        expressions,
-        caller='test_psa_constant_names.py for {} values'.format(type_word),
-        file_label=type_word,
-        header='#include <psa/crypto.h>',
-        include_path=include_path,
-        keep_c=keep_c
-    )
-
-NORMALIZE_STRIP_RE = re.compile(r'\s+')
-def normalize(expr: str) -> str:
-    """Normalize the C expression so as not to care about trivial differences.
-
-    Currently "trivial differences" means whitespace.
-    """
-    return re.sub(NORMALIZE_STRIP_RE, '', expr)
-
-ALG_TRUNCATED_TO_SELF_RE = \
-    re.compile(r'PSA_ALG_AEAD_WITH_SHORTENED_TAG\('
-               r'PSA_ALG_(?:CCM|CHACHA20_POLY1305|GCM)'
-               r', *16\)\Z')
-
-def is_simplifiable(expr: str) -> bool:
-    """Determine whether an expression is simplifiable.
-
-    Simplifiable expressions can't be output in their input form, since
-    the output will be the simple form. Therefore they must be excluded
-    from testing.
-    """
-    if ALG_TRUNCATED_TO_SELF_RE.match(expr):
-        return True
-    return False
-
-def collect_values(inputs: InputsForTest,
-                   type_word: str,
-                   include_path: Optional[str] = None,
-                   keep_c: bool = False) -> Tuple[List[str], List[str]]:
-    """Generate expressions using known macro names and calculate their values.
-
-    Return a list of pairs of (expr, value) where expr is an expression and
-    value is a string representation of its integer value.
-    """
-    names = inputs.get_names(type_word)
-    expressions = sorted(expr
-                         for expr in inputs.generate_expressions(names)
-                         if not is_simplifiable(expr))
-    values = run_c(type_word, expressions,
-                   include_path=include_path, keep_c=keep_c)
-    return expressions, values
-
-class Tests:
-    """An object representing tests and their results."""
-
-    Error = namedtuple('Error',
-                       ['type', 'expression', 'value', 'output'])
-
-    def __init__(self, options) -> None:
-        self.options = options
-        self.count = 0
-        self.errors = [] #type: List[Tests.Error]
-
-    def run_one(self, inputs: InputsForTest, type_word: str) -> None:
-        """Test psa_constant_names for the specified type.
-
-        Run the program on the names for this type.
-        Use the inputs to figure out what arguments to pass to macros that
-        take arguments.
-        """
-        expressions, values = collect_values(inputs, type_word,
-                                             include_path=self.options.include,
-                                             keep_c=self.options.keep_c)
-        output_bytes = subprocess.check_output([self.options.program,
-                                                type_word] + values)
-        output = output_bytes.decode('ascii')
-        outputs = output.strip().split('\n')
-        self.count += len(expressions)
-        for expr, value, output in zip(expressions, values, outputs):
-            if self.options.show:
-                sys.stdout.write('{} {}\t{}\n'.format(type_word, value, output))
-            if normalize(expr) != normalize(output):
-                self.errors.append(self.Error(type=type_word,
-                                              expression=expr,
-                                              value=value,
-                                              output=output))
-
-    def run_all(self, inputs: InputsForTest) -> None:
-        """Run psa_constant_names on all the gathered inputs."""
-        for type_word in ['status', 'algorithm', 'ecc_curve', 'dh_group',
-                          'key_type', 'key_usage']:
-            self.run_one(inputs, type_word)
-
-    def report(self, out: typing_util.Writable) -> None:
-        """Describe each case where the output is not as expected.
-
-        Write the errors to ``out``.
-        Also write a total.
-        """
-        for error in self.errors:
-            out.write('For {} "{}", got "{}" (value: {})\n'
-                      .format(error.type, error.expression,
-                              error.output, error.value))
-        out.write('{} test cases'.format(self.count))
-        if self.errors:
-            out.write(', {} FAIL\n'.format(len(self.errors)))
-        else:
-            out.write(' PASS\n')
-
-HEADERS = ['psa/crypto.h', 'psa/crypto_extra.h', 'psa/crypto_values.h']
-TEST_SUITES = ['tests/suites/test_suite_psa_crypto_metadata.data']
-
-def main():
-    parser = argparse.ArgumentParser(description=globals()['__doc__'])
-    parser.add_argument('--include', '-I',
-                        action='append', default=['include'],
-                        help='Directory for header files')
-    parser.add_argument('--keep-c',
-                        action='store_true', dest='keep_c', default=False,
-                        help='Keep the intermediate C file')
-    parser.add_argument('--no-keep-c',
-                        action='store_false', dest='keep_c',
-                        help='Don\'t keep the intermediate C file (default)')
-    parser.add_argument('--program',
-                        default='programs/psa/psa_constant_names',
-                        help='Program to test')
-    parser.add_argument('--show',
-                        action='store_true',
-                        help='Show tested values on stdout')
-    parser.add_argument('--no-show',
-                        action='store_false', dest='show',
-                        help='Don\'t show tested values (default)')
-    options = parser.parse_args()
-    headers = [os.path.join(options.include[0], h) for h in HEADERS]
-    inputs = gather_inputs(headers, TEST_SUITES)
-    tests = Tests(options)
-    tests.run_all(inputs)
-    tests.report(sys.stdout)
-    if tests.errors:
-        sys.exit(1)
-
-if __name__ == '__main__':
-    main()
diff --git a/tests/scripts/test_zeroize.gdb b/tests/scripts/test_zeroize.gdb
deleted file mode 100644
index 57f771f..0000000
--- a/tests/scripts/test_zeroize.gdb
+++ /dev/null
@@ -1,64 +0,0 @@
-# test_zeroize.gdb
-#
-# Copyright The Mbed TLS Contributors
-# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
-#
-# Purpose
-#
-# Run a test using the debugger to check that the mbedtls_platform_zeroize()
-# function in platform_util.h is not being optimized out by the compiler. To do
-# so, the script loads the test program at programs/test/zeroize.c and sets a
-# breakpoint at the last return statement in main(). When the breakpoint is
-# hit, the debugger manually checks the contents to be zeroized and checks that
-# it is actually cleared.
-#
-# The mbedtls_platform_zeroize() test is debugger driven because there does not
-# seem to be a mechanism to reliably check whether the zeroize calls are being
-# eliminated by compiler optimizations from within the compiled program. The
-# problem is that a compiler would typically remove what it considers to be
-# "unnecessary" assignments as part of redundant code elimination. To identify
-# such code, the compilar will create some form dependency graph between
-# reads and writes to variables (among other situations). It will then use this
-# data structure to remove redundant code that does not have an impact on the
-# program's observable behavior. In the case of mbedtls_platform_zeroize(), an
-# intelligent compiler could determine that this function clears a block of
-# memory that is not accessed later in the program, so removing the call to
-# mbedtls_platform_zeroize() does not have an observable behavior. However,
-# inserting a test after a call to mbedtls_platform_zeroize() to check whether
-# the block of memory was correctly zeroed would force the compiler to not
-# eliminate the mbedtls_platform_zeroize() call. If this does not occur, then
-# the compiler potentially has a bug.
-#
-# Note: This test requires that the test program is compiled with -g3.
-
-set confirm off
-
-file ./programs/test/zeroize
-
-search GDB_BREAK_HERE
-break $_
-
-set args ./programs/test/zeroize.c
-run
-
-set $i = 0
-set $len = sizeof(buf)
-set $buf = buf
-
-while $i < $len
-    if $buf[$i++] != 0
-        echo The buffer at was not zeroized\n
-        quit 1
-    end
-end
-
-echo The buffer was correctly zeroized\n
-
-continue
-
-if $_exitcode != 0
-    echo The program did not terminate correctly\n
-    quit 1
-end
-
-quit 0
diff --git a/tests/src/certs.c b/tests/src/certs.c
index 879f088..6a01893 100644
--- a/tests/src/certs.c
+++ b/tests/src/certs.c
@@ -13,7 +13,7 @@
 
 #include "mbedtls/pk.h"
 
-#include "test_certs.h"
+#include "test/test_certs.h"
 
 /*
  *
diff --git a/tests/src/test_helpers/ssl_helpers.c b/tests/src/test_helpers/ssl_helpers.c
index 3cb6175..7fb23ab 100644
--- a/tests/src/test_helpers/ssl_helpers.c
+++ b/tests/src/test_helpers/ssl_helpers.c
@@ -28,9 +28,22 @@
 {
     mbedtls_test_ssl_log_pattern *p = (mbedtls_test_ssl_log_pattern *) ctx;
 
+/* Change 0 to 1 for debugging of test cases that use this function. */
+#if 0
+    const char *q, *basename;
+    /* Extract basename from file */
+    for (q = basename = file; *q != '\0'; q++) {
+        if (*q == '/' || *q == '\\') {
+            basename = q + 1;
+        }
+    }
+    printf("%s:%04d: |%d| %s",
+           basename, line, level, str);
+#else
     (void) level;
     (void) line;
     (void) file;
+#endif
 
     if (NULL != p &&
         NULL != p->pattern &&
@@ -598,6 +611,7 @@
 {
     int i = 0;
     int ret = -1;
+    int ok = 0;
     mbedtls_test_ssl_endpoint_certificate *cert = NULL;
 #if defined(MBEDTLS_USE_PSA_CRYPTO)
     mbedtls_svc_key_id_t key_slot = MBEDTLS_SVC_KEY_ID_INIT;
@@ -724,7 +738,13 @@
                                     cert->pkey);
     TEST_ASSERT(ret == 0);
 
+    ok = 1;
+
 exit:
+    if (ret == 0 && !ok) {
+        /* Exiting due to a test assertion that isn't ret == 0 */
+        ret = -1;
+    }
     if (ret != 0) {
         test_ssl_endpoint_certificate_free(ep);
     }
@@ -855,6 +875,11 @@
     ret = mbedtls_ssl_setup(&(ep->ssl), &(ep->conf));
     TEST_ASSERT(ret == 0);
 
+    if (MBEDTLS_SSL_IS_CLIENT == endpoint_type) {
+        ret = mbedtls_ssl_set_hostname(&(ep->ssl), "localhost");
+        TEST_EQUAL(ret, 0);
+    }
+
 #if defined(MBEDTLS_SSL_PROTO_DTLS) && defined(MBEDTLS_SSL_SRV_C)
     if (endpoint_type == MBEDTLS_SSL_IS_SERVER && dtls_context != NULL) {
         mbedtls_ssl_conf_dtls_cookies(&(ep->conf), NULL, NULL, NULL);
@@ -889,7 +914,13 @@
     TEST_EQUAL(mbedtls_ssl_get_user_data_n(&ep->ssl), user_data_n);
     mbedtls_ssl_set_user_data_p(&ep->ssl, ep);
 
+    return 0;
+
 exit:
+    if (ret == 0) {
+        /* Exiting due to a test assertion that isn't ret == 0 */
+        ret = -1;
+    }
     return ret;
 }
 
@@ -2021,6 +2052,63 @@
 #endif /* MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED */
 
 #if defined(MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED)
+int mbedtls_test_ssl_do_handshake_with_endpoints(
+    mbedtls_test_ssl_endpoint *server_ep,
+    mbedtls_test_ssl_endpoint *client_ep,
+    mbedtls_test_handshake_test_options *options,
+    mbedtls_ssl_protocol_version proto)
+{
+    enum { BUFFSIZE = 1024 };
+
+    int ret = -1;
+
+    mbedtls_platform_zeroize(server_ep, sizeof(mbedtls_test_ssl_endpoint));
+    mbedtls_platform_zeroize(client_ep, sizeof(mbedtls_test_ssl_endpoint));
+
+    mbedtls_test_init_handshake_options(options);
+    options->server_min_version = proto;
+    options->client_min_version = proto;
+    options->server_max_version = proto;
+    options->client_max_version = proto;
+
+    ret = mbedtls_test_ssl_endpoint_init(client_ep, MBEDTLS_SSL_IS_CLIENT, options,
+                                         NULL, NULL, NULL);
+    if (ret != 0) {
+        return ret;
+    }
+    ret = mbedtls_test_ssl_endpoint_init(server_ep, MBEDTLS_SSL_IS_SERVER, options,
+                                         NULL, NULL, NULL);
+    if (ret != 0) {
+        return ret;
+    }
+
+    ret = mbedtls_test_mock_socket_connect(&client_ep->socket, &server_ep->socket, BUFFSIZE);
+    if (ret != 0) {
+        return ret;
+    }
+
+    ret = mbedtls_test_move_handshake_to_state(&server_ep->ssl,
+                                               &client_ep->ssl,
+                                               MBEDTLS_SSL_HANDSHAKE_OVER);
+    if (ret != 0 && ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
+        return ret;
+    }
+    ret = mbedtls_test_move_handshake_to_state(&client_ep->ssl,
+                                               &server_ep->ssl,
+                                               MBEDTLS_SSL_HANDSHAKE_OVER);
+    if (ret != 0 && ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
+        return ret;
+    }
+    if (!mbedtls_ssl_is_handshake_over(&client_ep->ssl) ||
+        !mbedtls_ssl_is_handshake_over(&server_ep->ssl)) {
+        return -1;
+    }
+
+    return 0;
+}
+#endif /* defined(MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED) */
+
+#if defined(MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED)
 void mbedtls_test_ssl_perform_handshake(
     mbedtls_test_handshake_test_options *options)
 {
@@ -2529,6 +2617,7 @@
     mbedtls_ssl_session *session)
 {
     int ret = -1;
+    int ok = 0;
     unsigned char buf[64];
     mbedtls_test_ssl_endpoint client_ep, server_ep;
 
@@ -2568,10 +2657,16 @@
     ret = mbedtls_ssl_get_session(&(client_ep.ssl), session);
     TEST_EQUAL(ret, 0);
 
+    ok = 1;
+
 exit:
     mbedtls_test_ssl_endpoint_free(&client_ep, NULL);
     mbedtls_test_ssl_endpoint_free(&server_ep, NULL);
 
+    if (ret == 0 && !ok) {
+        /* Exiting due to a test assertion that isn't ret == 0 */
+        ret = -1;
+    }
     return ret;
 }
 #endif /* MBEDTLS_SSL_CLI_C && MBEDTLS_SSL_SRV_C &&
diff --git a/tests/ssl-opt.sh b/tests/ssl-opt.sh
index 0376018..16e2675 100755
--- a/tests/ssl-opt.sh
+++ b/tests/ssl-opt.sh
@@ -103,12 +103,14 @@
     O_NEXT_SRV_NO_CERT="$OPENSSL_NEXT s_server -www "
     O_NEXT_CLI="echo 'GET / HTTP/1.0' | $OPENSSL_NEXT s_client -CAfile $DATA_FILES_PATH/test-ca_cat12.crt"
     O_NEXT_CLI_NO_CERT="echo 'GET / HTTP/1.0' | $OPENSSL_NEXT s_client"
+    O_NEXT_CLI_RENEGOTIATE="echo 'R' | $OPENSSL_NEXT s_client -cert $DATA_FILES_PATH/server5.crt -key $DATA_FILES_PATH/server5.key"
 else
     O_NEXT_SRV=false
     O_NEXT_SRV_NO_CERT=false
     O_NEXT_SRV_EARLY_DATA=false
     O_NEXT_CLI_NO_CERT=false
     O_NEXT_CLI=false
+    O_NEXT_CLI_RENEGOTIATE=false
 fi
 
 if [ -n "${GNUTLS_NEXT_SERV:-}" ]; then
@@ -491,6 +493,11 @@
     esac
 
     case " $CMD_LINE " in
+        *\ ca_callback=1\ *)
+            requires_config_enabled MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK;;
+    esac
+
+    case " $CMD_LINE " in
         *"programs/ssl/dtls_client "*|\
         *"programs/ssl/ssl_client1 "*)
             requires_config_enabled MBEDTLS_CTR_DRBG_C
@@ -1223,6 +1230,26 @@
     fi
 }
 
+# Extract the exported key from the output.
+get_exported_key() {
+    OUTPUT="$1"
+    EXPORTED_KEY1=$(sed -n '/Exporting key of length 20 with label ".*": /s/.*: //p' $OUTPUT)
+}
+
+# Check that the exported key from the output matches the one obtained in get_exported_key().
+check_exported_key() {
+    OUTPUT="$1"
+    EXPORTED_KEY2=$(sed -n '/Exporting key of length 20 with label ".*": /s/.*: //p' $OUTPUT)
+    test "$EXPORTED_KEY1" = "$EXPORTED_KEY2"
+}
+
+# Check that the exported key from the output matches the one obtained in get_exported_key().
+check_exported_key_openssl() {
+    OUTPUT="$1"
+    EXPORTED_KEY2=0x$(sed -n '/Keying material: /s/.*: //p' $OUTPUT)
+    test "$EXPORTED_KEY1" = "$EXPORTED_KEY2"
+}
+
 # Get handshake memory usage from server or client output and put it into the variable specified by the first argument
 handshake_memory_get() {
     OUTPUT_VARIABLE="$1"
@@ -1970,6 +1997,46 @@
     run_test_memory_after_handshake_with_mfl 512 "$MEMORY_USAGE_MFL_16K"
 }
 
+run_test_export_keying_material() {
+    unset EXPORTED_KEY1
+    unset EXPORTED_KEY2
+    TLS_VERSION="$1"
+
+    case $TLS_VERSION in
+        tls12) TLS_VERSION_PRINT="TLS 1.2";;
+        tls13) TLS_VERSION_PRINT="TLS 1.3";;
+    esac
+
+    run_test    "$TLS_VERSION_PRINT: Export keying material" \
+                "$P_SRV debug_level=4 force_version=$TLS_VERSION exp_label=test-label" \
+                "$P_CLI debug_level=4 force_version=$TLS_VERSION exp_label=test-label" \
+                0 \
+                -s "Exporting key of length 20 with label \".*\": 0x" \
+                -c "Exporting key of length 20 with label \".*\": 0x" \
+                -f get_exported_key \
+                -F check_exported_key
+}
+
+run_test_export_keying_material_openssl_compat() {
+    unset EXPORTED_KEY1
+    unset EXPORTED_KEY2
+    TLS_VERSION="$1"
+
+    case $TLS_VERSION in
+        tls12) TLS_VERSION_PRINT="TLS 1.2"; OPENSSL_CLIENT="$O_CLI";;
+        tls13) TLS_VERSION_PRINT="TLS 1.3"; OPENSSL_CLIENT="$O_NEXT_CLI";;
+    esac
+
+    run_test    "$TLS_VERSION_PRINT: Export keying material (OpenSSL compatibility)" \
+                "$P_SRV debug_level=4 force_version=$TLS_VERSION exp_label=test-label" \
+                "$OPENSSL_CLIENT -keymatexport test-label" \
+                0 \
+                -s "Exporting key of length 20 with label \".*\": 0x" \
+                -c "Keying material exporter:" \
+                -F get_exported_key \
+                -f check_exported_key_openssl
+}
+
 cleanup() {
     rm -f $CLI_OUT $SRV_OUT $PXY_OUT $SESSION
     rm -f context_srv.txt
@@ -2254,7 +2321,6 @@
             "$P_CLI" \
             0
 
-requires_config_enabled MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK
 run_test    "CA callback on client" \
             "$P_SRV debug_level=3" \
             "$P_CLI ca_callback=1 debug_level=3 " \
@@ -2263,7 +2329,6 @@
             -S "error" \
             -C "error"
 
-requires_config_enabled MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK
 requires_config_enabled MBEDTLS_X509_CRT_PARSE_C
 requires_hash_alg SHA_256
 run_test    "CA callback on server" \
@@ -3133,6 +3198,24 @@
             0 \
             -s "Save serialized context to a file... ok" \
             -c "Save serialized context to a file... ok"
+
+requires_config_enabled MBEDTLS_SSL_KEYING_MATERIAL_EXPORT
+requires_protocol_version tls12
+run_test_export_keying_material tls12
+
+requires_config_enabled MBEDTLS_SSL_KEYING_MATERIAL_EXPORT
+requires_protocol_version tls12
+run_test_export_keying_material_openssl_compat tls12
+
+requires_config_enabled MBEDTLS_SSL_KEYING_MATERIAL_EXPORT
+requires_protocol_version tls13
+run_test_export_keying_material tls13
+
+requires_config_enabled MBEDTLS_SSL_KEYING_MATERIAL_EXPORT
+requires_config_enabled MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED
+requires_openssl_tls1_3_with_compatible_ephemeral
+run_test_export_keying_material_openssl_compat tls13
+
 rm -f context_srv.txt
 rm -f context_cli.txt
 
@@ -6072,6 +6155,271 @@
             -C "X509 - Certificate verification failed" \
             -C "SSL - No CA Chain is set, but required to operate"
 
+# The next few tests check what happens if the server has a valid certificate
+# that does not match its name (impersonation).
+
+run_test "Authentication: hostname match, client required" \
+         "$P_SRV" \
+         "$P_CLI auth_mode=required server_name=localhost debug_level=2" \
+         0 \
+         -C "does not match with the expected CN" \
+         -C "Certificate verification without having set hostname" \
+         -C "Certificate verification without CN verification" \
+         -C "x509_verify_cert() returned -" \
+         -C "! mbedtls_ssl_handshake returned" \
+         -C "X509 - Certificate verification failed"
+
+run_test "Authentication: hostname match, client required, CA callback" \
+         "$P_SRV" \
+         "$P_CLI auth_mode=required server_name=localhost debug_level=3 ca_callback=1" \
+         0 \
+         -C "does not match with the expected CN" \
+         -C "Certificate verification without having set hostname" \
+         -C "Certificate verification without CN verification" \
+         -c "use CA callback for X.509 CRT verification" \
+         -C "x509_verify_cert() returned -" \
+         -C "! mbedtls_ssl_handshake returned" \
+         -C "X509 - Certificate verification failed"
+
+run_test "Authentication: hostname mismatch (wrong), client required" \
+         "$P_SRV" \
+         "$P_CLI auth_mode=required server_name=wrong-name debug_level=1" \
+         1 \
+         -c "does not match with the expected CN" \
+         -c "x509_verify_cert() returned -" \
+         -c "! mbedtls_ssl_handshake returned" \
+         -c "X509 - Certificate verification failed"
+
+run_test "Authentication: hostname mismatch (empty), client required" \
+         "$P_SRV" \
+         "$P_CLI auth_mode=required server_name= debug_level=1" \
+         1 \
+         -c "does not match with the expected CN" \
+         -c "x509_verify_cert() returned -" \
+         -c "! mbedtls_ssl_handshake returned" \
+         -c "X509 - Certificate verification failed"
+
+run_test "Authentication: hostname mismatch (truncated), client required" \
+         "$P_SRV" \
+         "$P_CLI auth_mode=required server_name=localhos debug_level=1" \
+         1 \
+         -c "does not match with the expected CN" \
+         -c "x509_verify_cert() returned -" \
+         -c "! mbedtls_ssl_handshake returned" \
+         -c "X509 - Certificate verification failed"
+
+run_test "Authentication: hostname mismatch (last char), client required" \
+         "$P_SRV" \
+         "$P_CLI auth_mode=required server_name=localhoss debug_level=1" \
+         1 \
+         -c "does not match with the expected CN" \
+         -c "x509_verify_cert() returned -" \
+         -c "! mbedtls_ssl_handshake returned" \
+         -c "X509 - Certificate verification failed"
+
+run_test "Authentication: hostname mismatch (trailing), client required" \
+         "$P_SRV" \
+         "$P_CLI auth_mode=required server_name=localhostt debug_level=1" \
+         1 \
+         -c "does not match with the expected CN" \
+         -c "x509_verify_cert() returned -" \
+         -c "! mbedtls_ssl_handshake returned" \
+         -c "X509 - Certificate verification failed"
+
+run_test "Authentication: hostname mismatch, client optional" \
+         "$P_SRV" \
+         "$P_CLI auth_mode=optional server_name=wrong-name debug_level=2" \
+         0 \
+         -c "does not match with the expected CN" \
+         -c "x509_verify_cert() returned -" \
+         -C "X509 - Certificate verification failed"
+
+run_test "Authentication: hostname mismatch, client none" \
+         "$P_SRV" \
+         "$P_CLI auth_mode=none server_name=wrong-name debug_level=2" \
+         0 \
+         -C "does not match with the expected CN" \
+         -C "Certificate verification without having set hostname" \
+         -C "Certificate verification without CN verification" \
+         -C "x509_verify_cert() returned -" \
+         -C "X509 - Certificate verification failed"
+
+run_test "Authentication: hostname null, client required" \
+         "$P_SRV" \
+         "$P_CLI auth_mode=required set_hostname=NULL debug_level=2" \
+         0 \
+         -C "does not match with the expected CN" \
+         -C "Certificate verification without having set hostname" \
+         -c "Certificate verification without CN verification" \
+         -C "x509_verify_cert() returned -" \
+         -C "! mbedtls_ssl_handshake returned" \
+         -C "X509 - Certificate verification failed"
+
+run_test "Authentication: hostname null, client optional" \
+         "$P_SRV" \
+         "$P_CLI auth_mode=optional set_hostname=NULL debug_level=2" \
+         0 \
+         -C "does not match with the expected CN" \
+         -C "Certificate verification without having set hostname" \
+         -c "Certificate verification without CN verification" \
+         -C "x509_verify_cert() returned -" \
+         -C "X509 - Certificate verification failed"
+
+run_test "Authentication: hostname null, client none" \
+         "$P_SRV" \
+         "$P_CLI auth_mode=none set_hostname=NULL debug_level=2" \
+         0 \
+         -C "does not match with the expected CN" \
+         -C "Certificate verification without having set hostname" \
+         -C "Certificate verification without CN verification" \
+         -C "x509_verify_cert() returned -" \
+         -C "X509 - Certificate verification failed"
+
+requires_config_disabled MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME
+run_test "Authentication: hostname unset, client required, secure config" \
+         "$P_SRV" \
+         "$P_CLI auth_mode=required set_hostname=no debug_level=2" \
+         1 \
+         -C "does not match with the expected CN" \
+         -c "Certificate verification without having set hostname" \
+         -C "Certificate verification without CN verification" \
+         -c "get_hostname_for_verification() returned -" \
+         -C "x509_verify_cert() returned -" \
+         -c "! mbedtls_ssl_handshake returned" \
+         -C "X509 - Certificate verification failed"
+
+requires_config_enabled MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME
+run_test "Authentication: hostname unset, client required, historical config" \
+         "$P_SRV" \
+         "$P_CLI auth_mode=required set_hostname=no debug_level=2" \
+         0 \
+         -C "does not match with the expected CN" \
+         -c "Certificate verification without having set hostname" \
+         -c "Certificate verification without CN verification" \
+         -C "get_hostname_for_verification() returned -" \
+         -C "x509_verify_cert() returned -" \
+         -C "! mbedtls_ssl_handshake returned" \
+         -C "X509 - Certificate verification failed"
+
+requires_config_disabled MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME
+run_test "Authentication: hostname unset, client required, secure config, CA callback" \
+         "$P_SRV" \
+         "$P_CLI auth_mode=required set_hostname=no debug_level=3 ca_callback=1" \
+         1 \
+         -C "does not match with the expected CN" \
+         -c "Certificate verification without having set hostname" \
+         -C "Certificate verification without CN verification" \
+         -c "get_hostname_for_verification() returned -" \
+         -C "use CA callback for X.509 CRT verification" \
+         -C "x509_verify_cert() returned -" \
+         -c "! mbedtls_ssl_handshake returned" \
+         -C "X509 - Certificate verification failed"
+
+requires_config_enabled MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME
+run_test "Authentication: hostname unset, client required, historical config, CA callback" \
+         "$P_SRV" \
+         "$P_CLI auth_mode=required set_hostname=no debug_level=3 ca_callback=1" \
+         0 \
+         -C "does not match with the expected CN" \
+         -c "Certificate verification without having set hostname" \
+         -c "Certificate verification without CN verification" \
+         -C "get_hostname_for_verification() returned -" \
+         -c "use CA callback for X.509 CRT verification" \
+         -C "x509_verify_cert() returned -" \
+         -C "! mbedtls_ssl_handshake returned" \
+         -C "X509 - Certificate verification failed"
+
+run_test "Authentication: hostname unset, client optional" \
+         "$P_SRV" \
+         "$P_CLI auth_mode=optional set_hostname=no debug_level=2" \
+         0 \
+         -C "does not match with the expected CN" \
+         -c "Certificate verification without having set hostname" \
+         -c "Certificate verification without CN verification" \
+         -C "x509_verify_cert() returned -" \
+         -C "X509 - Certificate verification failed"
+
+run_test "Authentication: hostname unset, client none" \
+         "$P_SRV" \
+         "$P_CLI auth_mode=none set_hostname=no debug_level=2" \
+         0 \
+         -C "does not match with the expected CN" \
+         -C "Certificate verification without having set hostname" \
+         -C "Certificate verification without CN verification" \
+         -C "x509_verify_cert() returned -" \
+         -C "X509 - Certificate verification failed"
+
+requires_config_disabled MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME
+run_test "Authentication: hostname unset, client default, secure config, server picks cert, 1.2" \
+         "$P_SRV force_version=tls12 force_ciphersuite=TLS-ECDHE-ECDSA-WITH-AES-128-CCM-8" \
+         "$P_CLI psk=73776f726466697368 psk_identity=foo set_hostname=no debug_level=2" \
+         1 \
+         -C "does not match with the expected CN" \
+         -c "Certificate verification without having set hostname" \
+         -C "Certificate verification without CN verification" \
+         -c "get_hostname_for_verification() returned -" \
+         -C "x509_verify_cert() returned -" \
+         -C "X509 - Certificate verification failed"
+
+requires_config_disabled MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME
+requires_config_enabled MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED
+run_test "Authentication: hostname unset, client default, secure config, server picks cert, 1.3" \
+         "$P_SRV force_version=tls13 tls13_kex_modes=ephemeral" \
+         "$P_CLI psk=73776f726466697368 psk_identity=foo set_hostname=no debug_level=2" \
+         1 \
+         -C "does not match with the expected CN" \
+         -c "Certificate verification without having set hostname" \
+         -C "Certificate verification without CN verification" \
+         -c "get_hostname_for_verification() returned -" \
+         -C "x509_verify_cert() returned -" \
+         -C "X509 - Certificate verification failed"
+
+requires_config_enabled MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME
+run_test "Authentication: hostname unset, client default, historical config, server picks cert, 1.2" \
+         "$P_SRV force_version=tls12 force_ciphersuite=TLS-ECDHE-ECDSA-WITH-AES-128-CCM-8" \
+         "$P_CLI psk=73776f726466697368 psk_identity=foo set_hostname=no debug_level=2" \
+         0 \
+         -C "does not match with the expected CN" \
+         -c "Certificate verification without having set hostname" \
+         -c "Certificate verification without CN verification" \
+         -C "get_hostname_for_verification() returned -" \
+         -C "x509_verify_cert() returned -" \
+         -C "X509 - Certificate verification failed"
+
+requires_config_enabled MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME
+requires_config_enabled MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED
+run_test "Authentication: hostname unset, client default, historical config, server picks cert, 1.3" \
+         "$P_SRV force_version=tls13 tls13_kex_modes=ephemeral" \
+         "$P_CLI psk=73776f726466697368 psk_identity=foo set_hostname=no debug_level=2" \
+         0 \
+         -C "does not match with the expected CN" \
+         -c "Certificate verification without having set hostname" \
+         -c "Certificate verification without CN verification" \
+         -C "get_hostname_for_verification() returned -" \
+         -C "x509_verify_cert() returned -" \
+         -C "X509 - Certificate verification failed"
+
+run_test "Authentication: hostname unset, client default, server picks PSK, 1.2" \
+         "$P_SRV force_version=tls12 force_ciphersuite=TLS-PSK-WITH-AES-128-CCM-8 psk=73776f726466697368 psk_identity=foo" \
+         "$P_CLI psk=73776f726466697368 psk_identity=foo set_hostname=no debug_level=2" \
+         0 \
+         -C "does not match with the expected CN" \
+         -C "Certificate verification without having set hostname" \
+         -C "Certificate verification without CN verification" \
+         -C "x509_verify_cert() returned -" \
+         -C "X509 - Certificate verification failed"
+
+requires_config_enabled MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_ENABLED
+run_test "Authentication: hostname unset, client default, server picks PSK, 1.3" \
+         "$P_SRV force_version=tls13 tls13_kex_modes=psk psk=73776f726466697368 psk_identity=foo" \
+         "$P_CLI psk=73776f726466697368 psk_identity=foo set_hostname=no debug_level=2" \
+         0 \
+         -C "does not match with the expected CN" \
+         -C "Certificate verification without having set hostname" \
+         -C "Certificate verification without CN verification" \
+         -C "x509_verify_cert() returned -" \
+         -C "X509 - Certificate verification failed"
+
 # The purpose of the next two tests is to test the client's behaviour when receiving a server
 # certificate with an unsupported elliptic curve. This should usually not happen because
 # the client informs the server about the supported curves - it does, though, in the
@@ -6416,7 +6764,6 @@
 # Tests for auth_mode, using CA callback, these are duplicated from the authentication tests
 # When updating these tests, modify the matching authentication tests accordingly
 
-requires_config_enabled MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK
 run_test    "Authentication, CA callback: server badcert, client required" \
             "$P_SRV crt_file=$DATA_FILES_PATH/server5-badsign.crt \
              key_file=$DATA_FILES_PATH/server5.key" \
@@ -6428,7 +6775,6 @@
             -c "! mbedtls_ssl_handshake returned" \
             -c "X509 - Certificate verification failed"
 
-requires_config_enabled MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK
 run_test    "Authentication, CA callback: server badcert, client optional" \
             "$P_SRV crt_file=$DATA_FILES_PATH/server5-badsign.crt \
              key_file=$DATA_FILES_PATH/server5.key" \
@@ -6440,7 +6786,6 @@
             -C "! mbedtls_ssl_handshake returned" \
             -C "X509 - Certificate verification failed"
 
-requires_config_enabled MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK
 run_test    "Authentication, CA callback: server badcert, client none" \
             "$P_SRV crt_file=$DATA_FILES_PATH/server5-badsign.crt \
              key_file=$DATA_FILES_PATH/server5.key" \
@@ -6459,7 +6804,6 @@
 # occasion (to be fixed). If that bug's fixed, the test needs to be altered to use a
 # different means to have the server ignoring the client's supported curve list.
 
-requires_config_enabled MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK
 run_test    "Authentication, CA callback: server ECDH p256v1, client required, p256v1 unsupported" \
             "$P_SRV debug_level=1 key_file=$DATA_FILES_PATH/server5.key \
              crt_file=$DATA_FILES_PATH/server5.ku-ka.crt" \
@@ -6470,7 +6814,6 @@
             -c "! Certificate verification flags" \
             -C "bad server certificate (ECDH curve)" # Expect failure at earlier verification stage
 
-requires_config_enabled MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK
 run_test    "Authentication, CA callback: server ECDH p256v1, client optional, p256v1 unsupported" \
             "$P_SRV debug_level=1 key_file=$DATA_FILES_PATH/server5.key \
              crt_file=$DATA_FILES_PATH/server5.ku-ka.crt" \
@@ -6481,7 +6824,6 @@
             -c "! Certificate verification flags"\
             -c "bad server certificate (ECDH curve)" # Expect failure only at ECDH params check
 
-requires_config_enabled MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK
 requires_any_configs_enabled $TLS1_2_KEY_EXCHANGES_WITH_CERT
 run_test    "Authentication, CA callback: client SHA384, server required" \
             "$P_SRV ca_callback=1 debug_level=3 auth_mode=required" \
@@ -6493,7 +6835,6 @@
             -c "Supported Signature Algorithm found: 04 " \
             -c "Supported Signature Algorithm found: 05 "
 
-requires_config_enabled MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK
 requires_any_configs_enabled $TLS1_2_KEY_EXCHANGES_WITH_CERT
 run_test    "Authentication, CA callback: client SHA256, server required" \
             "$P_SRV ca_callback=1 debug_level=3 auth_mode=required" \
@@ -6505,7 +6846,6 @@
             -c "Supported Signature Algorithm found: 04 " \
             -c "Supported Signature Algorithm found: 05 "
 
-requires_config_enabled MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK
 run_test    "Authentication, CA callback: client badcert, server required" \
             "$P_SRV ca_callback=1 debug_level=3 auth_mode=required" \
             "$P_CLI debug_level=3 crt_file=$DATA_FILES_PATH/server5-badsign.crt \
@@ -6527,7 +6867,6 @@
 # detect that its write end of the connection is closed and abort
 # before reading the alert message.
 
-requires_config_enabled MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK
 run_test    "Authentication, CA callback: client cert not trusted, server required" \
             "$P_SRV ca_callback=1 debug_level=3 auth_mode=required" \
             "$P_CLI debug_level=3 crt_file=$DATA_FILES_PATH/server5-selfsigned.crt \
@@ -6545,7 +6884,6 @@
             -s "! mbedtls_ssl_handshake returned" \
             -s "X509 - Certificate verification failed"
 
-requires_config_enabled MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK
 run_test    "Authentication, CA callback: client badcert, server optional" \
             "$P_SRV ca_callback=1 debug_level=3 auth_mode=optional" \
             "$P_CLI debug_level=3 crt_file=$DATA_FILES_PATH/server5-badsign.crt \
@@ -6566,7 +6904,6 @@
 
 requires_config_value_equals "MBEDTLS_X509_MAX_INTERMEDIATE_CA" $MAX_IM_CA
 requires_full_size_output_buffer
-requires_config_enabled MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK
 run_test    "Authentication, CA callback: server max_int chain, client default" \
             "$P_SRV crt_file=$DATA_FILES_PATH/dir-maxpath/c09.pem \
                     key_file=$DATA_FILES_PATH/dir-maxpath/09.key" \
@@ -6577,7 +6914,6 @@
 
 requires_config_value_equals "MBEDTLS_X509_MAX_INTERMEDIATE_CA" $MAX_IM_CA
 requires_full_size_output_buffer
-requires_config_enabled MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK
 run_test    "Authentication, CA callback: server max_int+1 chain, client default" \
             "$P_SRV crt_file=$DATA_FILES_PATH/dir-maxpath/c10.pem \
                     key_file=$DATA_FILES_PATH/dir-maxpath/10.key" \
@@ -6588,7 +6924,6 @@
 
 requires_config_value_equals "MBEDTLS_X509_MAX_INTERMEDIATE_CA" $MAX_IM_CA
 requires_full_size_output_buffer
-requires_config_enabled MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK
 run_test    "Authentication, CA callback: server max_int+1 chain, client optional" \
             "$P_SRV crt_file=$DATA_FILES_PATH/dir-maxpath/c10.pem \
                     key_file=$DATA_FILES_PATH/dir-maxpath/10.key" \
@@ -6600,7 +6935,6 @@
 
 requires_config_value_equals "MBEDTLS_X509_MAX_INTERMEDIATE_CA" $MAX_IM_CA
 requires_full_size_output_buffer
-requires_config_enabled MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK
 run_test    "Authentication, CA callback: client max_int+1 chain, server optional" \
             "$P_SRV ca_callback=1 debug_level=3 ca_file=$DATA_FILES_PATH/dir-maxpath/00.crt auth_mode=optional" \
             "$P_CLI crt_file=$DATA_FILES_PATH/dir-maxpath/c10.pem \
@@ -6611,7 +6945,6 @@
 
 requires_config_value_equals "MBEDTLS_X509_MAX_INTERMEDIATE_CA" $MAX_IM_CA
 requires_full_size_output_buffer
-requires_config_enabled MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK
 run_test    "Authentication, CA callback: client max_int+1 chain, server required" \
             "$P_SRV ca_callback=1 debug_level=3 ca_file=$DATA_FILES_PATH/dir-maxpath/00.crt auth_mode=required" \
             "$P_CLI crt_file=$DATA_FILES_PATH/dir-maxpath/c10.pem \
@@ -6622,7 +6955,6 @@
 
 requires_config_value_equals "MBEDTLS_X509_MAX_INTERMEDIATE_CA" $MAX_IM_CA
 requires_full_size_output_buffer
-requires_config_enabled MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK
 run_test    "Authentication, CA callback: client max_int chain, server required" \
             "$P_SRV ca_callback=1 debug_level=3 ca_file=$DATA_FILES_PATH/dir-maxpath/00.crt auth_mode=required" \
             "$P_CLI crt_file=$DATA_FILES_PATH/dir-maxpath/c09.pem \
@@ -14457,6 +14789,179 @@
             -c "Handshake was completed" \
             -s "dumping .client hello, compression. (2 bytes)"
 
+# Handshake defragmentation testing
+
+# Most test cases are in opt-testcases/handshake-generated.sh
+
+requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2
+requires_certificate_authentication
+run_test    "Handshake defragmentation on server: len=32, TLS 1.2 ClientHello (unsupported)" \
+            "$P_SRV debug_level=4 force_version=tls12 auth_mode=required" \
+            "$O_NEXT_CLI -tls1_2 -split_send_frag 32 -cert $DATA_FILES_PATH/server5.crt -key $DATA_FILES_PATH/server5.key" \
+            1 \
+            -s "The SSL configuration is tls12 only" \
+            -s "bad client hello message" \
+            -s "SSL - A message could not be parsed due to a syntactic error"
+
+# Test server-side buffer resizing with fragmented handshake on TLS1.2
+requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2
+requires_config_enabled MBEDTLS_SSL_MAX_FRAGMENT_LENGTH
+requires_config_enabled MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH
+requires_max_content_len 1025
+run_test    "Handshake defragmentation on server: len=256, buffer resizing with MFL=1024" \
+            "$P_SRV debug_level=4 auth_mode=required" \
+            "$O_NEXT_CLI -tls1_2 -split_send_frag 256 -maxfraglen 1024 -cert $DATA_FILES_PATH/server5.crt -key $DATA_FILES_PATH/server5.key" \
+            0 \
+            -s "Reallocating in_buf" \
+            -s "Reallocating out_buf" \
+            -s "reassembled record" \
+            -s "initial handshake fragment: 256, 0\\.\\.256 of [0-9]\\+" \
+            -s "Prepare: waiting for more handshake fragments 256/" \
+            -s "Consume: waiting for more handshake fragments 256/"
+
+# Test client-initiated renegotiation with fragmented handshake on TLS1.2
+requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2
+requires_config_enabled MBEDTLS_SSL_RENEGOTIATION
+run_test    "Handshake defragmentation on server: len=512, client-initiated renegotiation" \
+            "$P_SRV debug_level=4 exchanges=2 renegotiation=1 auth_mode=required" \
+            "$O_NEXT_CLI_RENEGOTIATE -tls1_2 -split_send_frag 512 -connect 127.0.0.1:+$SRV_PORT" \
+            0 \
+            -s "received TLS_EMPTY_RENEGOTIATION_INFO" \
+            -s "found renegotiation extension" \
+            -s "server hello, secure renegotiation extension" \
+            -s "=> renegotiate" \
+            -S "write hello request" \
+            -s "reassembled record" \
+            -s "initial handshake fragment: 512, 0\\.\\.512 of [0-9]\\+" \
+            -s "Prepare: waiting for more handshake fragments 512/" \
+            -s "Consume: waiting for more handshake fragments 512/" \
+
+requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2
+requires_config_enabled MBEDTLS_SSL_RENEGOTIATION
+run_test    "Handshake defragmentation on server: len=256, client-initiated renegotiation" \
+            "$P_SRV debug_level=4 exchanges=2 renegotiation=1 auth_mode=required" \
+            "$O_NEXT_CLI_RENEGOTIATE -tls1_2 -split_send_frag 256 -connect 127.0.0.1:+$SRV_PORT" \
+            0 \
+            -s "received TLS_EMPTY_RENEGOTIATION_INFO" \
+            -s "found renegotiation extension" \
+            -s "server hello, secure renegotiation extension" \
+            -s "=> renegotiate" \
+            -S "write hello request" \
+            -s "reassembled record" \
+            -s "initial handshake fragment: 256, 0\\.\\.256 of [0-9]\\+" \
+            -s "Prepare: waiting for more handshake fragments 256/" \
+            -s "Consume: waiting for more handshake fragments 256/" \
+
+requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2
+requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_3
+requires_config_enabled MBEDTLS_SSL_RENEGOTIATION
+run_test    "Handshake defragmentation on server: len=128, client-initiated renegotiation" \
+            "$P_SRV debug_level=4 exchanges=2 renegotiation=1 auth_mode=required" \
+            "$O_NEXT_CLI_RENEGOTIATE -tls1_2 -split_send_frag 128 -connect 127.0.0.1:+$SRV_PORT" \
+            0 \
+            -s "received TLS_EMPTY_RENEGOTIATION_INFO" \
+            -s "found renegotiation extension" \
+            -s "server hello, secure renegotiation extension" \
+            -s "=> renegotiate" \
+            -S "write hello request" \
+            -s "reassembled record" \
+            -s "initial handshake fragment: 128, 0\\.\\.128 of [0-9]\\+" \
+            -s "Prepare: waiting for more handshake fragments 128/" \
+            -s "Consume: waiting for more handshake fragments 128/" \
+
+requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2
+requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_3
+requires_config_enabled MBEDTLS_SSL_RENEGOTIATION
+run_test    "Handshake defragmentation on server: len=4, client-initiated renegotiation" \
+            "$P_SRV debug_level=4 exchanges=2 renegotiation=1 auth_mode=required" \
+            "$O_NEXT_CLI_RENEGOTIATE -tls1_2 -split_send_frag 4 -connect 127.0.0.1:+$SRV_PORT" \
+            0 \
+            -s "received TLS_EMPTY_RENEGOTIATION_INFO" \
+            -s "found renegotiation extension" \
+            -s "server hello, secure renegotiation extension" \
+            -s "=> renegotiate" \
+            -S "write hello request" \
+            -s "reassembled record" \
+            -s "initial handshake fragment: 4, 0\\.\\.4 of [0-9]\\+" \
+            -s "Prepare: waiting for more handshake fragments 4/" \
+            -s "Consume: waiting for more handshake fragments 4/" \
+
+requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2
+requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_3
+requires_config_enabled MBEDTLS_SSL_RENEGOTIATION
+run_test    "Handshake defragmentation on server: len=4, client-initiated server-rejected renegotiation" \
+            "$P_SRV debug_level=4 exchanges=2 renegotiation=0 auth_mode=required" \
+            "$O_NEXT_CLI_RENEGOTIATE -tls1_2 -split_send_frag 4 -connect 127.0.0.1:+$SRV_PORT" \
+            1 \
+            -s "received TLS_EMPTY_RENEGOTIATION_INFO" \
+            -s "refusing renegotiation, sending alert" \
+            -s "server hello, secure renegotiation extension" \
+            -s "initial handshake fragment: 4, 0\\.\\.4 of [0-9]\\+" \
+            -s "Prepare: waiting for more handshake fragments 4/" \
+            -s "Consume: waiting for more handshake fragments 4/" \
+
+# Test server-initiated renegotiation with fragmented handshake on TLS1.2
+
+# Note: The /reneg endpoint serves as a directive for OpenSSL's s_server
+# to initiate a handshake renegotiation.
+# Note: Adjusting the renegotiation delay beyond the library's default
+# value of 16 is necessary. This parameter defines the maximum
+# number of records received before renegotiation is completed.
+# By fragmenting records and thereby increasing their quantity,
+# the default threshold can be reached more quickly.
+# Setting it to -1 disables that policy's enforment.
+requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2
+requires_config_enabled MBEDTLS_SSL_RENEGOTIATION
+run_test    "Handshake defragmentation on client: len=512, server-initiated renegotiation" \
+            "$O_NEXT_SRV -tls1_2 -split_send_frag 512 -cert $DATA_FILES_PATH/server5.crt -key $DATA_FILES_PATH/server5.key" \
+            "$P_CLI debug_level=3 renegotiation=1 request_page=/reneg" \
+            0 \
+            -c "initial handshake fragment: 512, 0\\.\\.512 of [0-9]\\+" \
+            -c "Prepare: waiting for more handshake fragments 512/" \
+            -c "Consume: waiting for more handshake fragments 512/" \
+            -c "client hello, adding renegotiation extension" \
+            -c "found renegotiation extension" \
+            -c "=> renegotiate"
+
+requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2
+requires_config_enabled MBEDTLS_SSL_RENEGOTIATION
+run_test    "Handshake defragmentation on client: len=256, server-initiated renegotiation" \
+            "$O_NEXT_SRV -tls1_2 -split_send_frag 256 -cert $DATA_FILES_PATH/server5.crt -key $DATA_FILES_PATH/server5.key" \
+            "$P_CLI debug_level=3 renegotiation=1 renego_delay=-1 request_page=/reneg" \
+            0 \
+            -c "initial handshake fragment: 256, 0\\.\\.256 of [0-9]\\+" \
+            -c "Prepare: waiting for more handshake fragments 256/" \
+            -c "Consume: waiting for more handshake fragments 256/" \
+            -c "client hello, adding renegotiation extension" \
+            -c "found renegotiation extension" \
+            -c "=> renegotiate"
+
+requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2
+requires_config_enabled MBEDTLS_SSL_RENEGOTIATION
+run_test    "Handshake defragmentation on client: len=128, server-initiated renegotiation" \
+            "$O_NEXT_SRV -tls1_2 -split_send_frag 128 -cert $DATA_FILES_PATH/server5.crt -key $DATA_FILES_PATH/server5.key" \
+            "$P_CLI debug_level=3 renegotiation=1 renego_delay=-1 request_page=/reneg" \
+            0 \
+            -c "initial handshake fragment: 128, 0\\.\\.128 of [0-9]\\+" \
+            -c "Prepare: waiting for more handshake fragments 128/" \
+            -c "Consume: waiting for more handshake fragments 128/" \
+            -c "client hello, adding renegotiation extension" \
+            -c "found renegotiation extension" \
+            -c "=> renegotiate"
+
+requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2
+requires_config_enabled MBEDTLS_SSL_RENEGOTIATION
+run_test    "Handshake defragmentation on client: len=4, server-initiated renegotiation" \
+            "$O_NEXT_SRV -tls1_2 -split_send_frag 4 -cert $DATA_FILES_PATH/server5.crt -key $DATA_FILES_PATH/server5.key" \
+            "$P_CLI debug_level=3 renegotiation=1 renego_delay=-1 request_page=/reneg" \
+            0 \
+            -c "initial handshake fragment: 4, 0\\.\\.4 of [0-9]\\+" \
+            -c "Prepare: waiting for more handshake fragments 4/" \
+            -c "Consume: waiting for more handshake fragments 4/" \
+            -c "client hello, adding renegotiation extension" \
+            -c "found renegotiation extension" \
+            -c "=> renegotiate"
+
 # Test heap memory usage after handshake
 requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2
 requires_config_enabled MBEDTLS_MEMORY_DEBUG
diff --git a/tests/suites/test_suite_debug.data b/tests/suites/test_suite_debug.data
index 8b17eb8..46b6be4 100644
--- a/tests/suites/test_suite_debug.data
+++ b/tests/suites/test_suite_debug.data
@@ -1,3 +1,12 @@
+printf "%" MBEDTLS_PRINTF_SIZET, 0
+printf_int_expr:PRINTF_SIZET:sizeof(size_t):0:"0"
+
+printf "%" MBEDTLS_PRINTF_LONGLONG, 0
+printf_int_expr:PRINTF_LONGLONG:sizeof(long long):0:"0"
+
+printf "%" MBEDTLS_PRINTF_MS_TIME, 0
+printf_int_expr:PRINTF_MS_TIME:sizeof(mbedtls_ms_time_t):0:"0"
+
 Debug print msg (threshold 1, level 0)
 debug_print_msg_threshold:1:0:"MyFile":999:"MyFile(0999)\: Text message, 2 == 2\n"
 
diff --git a/tests/suites/test_suite_debug.function b/tests/suites/test_suite_debug.function
index 878ceed..9e53107 100644
--- a/tests/suites/test_suite_debug.function
+++ b/tests/suites/test_suite_debug.function
@@ -4,11 +4,34 @@
 #include "mbedtls/pk.h"
 #include <test/ssl_helpers.h>
 
+#if defined(_WIN32)
+#   include <stdlib.h>
+#   include <crtdbg.h>
+#endif
+
+// Dummy type for builds without MBEDTLS_HAVE_TIME
+#if !defined(MBEDTLS_HAVE_TIME)
+typedef int64_t mbedtls_ms_time_t;
+#endif
+
+typedef enum {
+    PRINTF_SIZET,
+    PRINTF_LONGLONG,
+    PRINTF_MS_TIME,
+} printf_format_indicator_t;
+
+const char *const printf_formats[] = {
+    [PRINTF_SIZET]    = "%" MBEDTLS_PRINTF_SIZET,
+    [PRINTF_LONGLONG] = "%" MBEDTLS_PRINTF_LONGLONG,
+    [PRINTF_MS_TIME]  = "%" MBEDTLS_PRINTF_MS_TIME,
+};
+
 struct buffer_data {
     char buf[2000];
     char *ptr;
 };
 
+#if defined(MBEDTLS_SSL_TLS_C)
 static void string_debug(void *data, int level, const char *file, int line, const char *str)
 {
     struct buffer_data *buffer = (struct buffer_data *) data;
@@ -44,14 +67,77 @@
 
     buffer->ptr = p;
 }
+#endif /* MBEDTLS_SSL_TLS_C */
+
+#if defined(_WIN32)
+static void noop_invalid_parameter_handler(
+    const wchar_t *expression,
+    const wchar_t *function,
+    const wchar_t *file,
+    unsigned int line,
+    uintptr_t pReserved)
+{
+    (void) expression;
+    (void) function;
+    (void) file;
+    (void) line;
+    (void) pReserved;
+}
+#endif /* _WIN32 */
+
 /* END_HEADER */
 
 /* BEGIN_DEPENDENCIES
- * depends_on:MBEDTLS_DEBUG_C:MBEDTLS_SSL_TLS_C
+ * depends_on:MBEDTLS_DEBUG_C
  * END_DEPENDENCIES
  */
 
 /* BEGIN_CASE */
+void printf_int_expr(int format_indicator, intmax_t sizeof_x, intmax_t x, char *result)
+{
+#if defined(_WIN32)
+    /* Windows treats any invalid format specifiers passsed to the CRT as fatal assertion failures.
+       Disable this behaviour temporarily, so the rest of the test cases can complete. */
+    _invalid_parameter_handler saved_handler =
+        _set_invalid_parameter_handler(noop_invalid_parameter_handler);
+
+    // Disable assertion pop-up window in Debug builds
+    int saved_report_mode = _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_REPORT_MODE);
+    _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG);
+#endif
+
+    const char *format = printf_formats[format_indicator];
+    char *output = NULL;
+    const size_t n = strlen(result);
+
+    /* Nominal case: buffer just large enough */
+    TEST_CALLOC(output, n + 1);
+    if ((size_t) sizeof_x <= sizeof(int)) { // Any smaller integers would be promoted to an int due to calling a vararg function
+        TEST_EQUAL(n, mbedtls_snprintf(output, n + 1, format, (int) x));
+    } else if (sizeof_x == sizeof(long)) {
+        TEST_EQUAL(n, mbedtls_snprintf(output, n + 1, format, (long) x));
+    } else if (sizeof_x == sizeof(long long)) {
+        TEST_EQUAL(n, mbedtls_snprintf(output, n + 1, format, (long long) x));
+    } else {
+        TEST_FAIL(
+            "sizeof_x <= sizeof(int) || sizeof_x == sizeof(long) || sizeof_x == sizeof(long long)");
+    }
+    TEST_MEMORY_COMPARE(result, n + 1, output, n + 1);
+
+exit:
+    mbedtls_free(output);
+    output = NULL;
+
+#if defined(_WIN32)
+    // Restore default Windows behaviour
+    _set_invalid_parameter_handler(saved_handler);
+    _CrtSetReportMode(_CRT_ASSERT, saved_report_mode);
+    (void) saved_report_mode;
+#endif
+}
+/* END_CASE */
+
+/* BEGIN_CASE depends_on:MBEDTLS_SSL_TLS_C */
 void debug_print_msg_threshold(int threshold, int level, char *file,
                                int line, char *result_str)
 {
@@ -90,7 +176,7 @@
 }
 /* END_CASE */
 
-/* BEGIN_CASE */
+/* BEGIN_CASE depends_on:MBEDTLS_SSL_TLS_C */
 void mbedtls_debug_print_ret(char *file, int line, char *text, int value,
                              char *result_str)
 {
@@ -126,7 +212,7 @@
 }
 /* END_CASE */
 
-/* BEGIN_CASE */
+/* BEGIN_CASE depends_on:MBEDTLS_SSL_TLS_C */
 void mbedtls_debug_print_buf(char *file, int line, char *text,
                              data_t *data, char *result_str)
 {
@@ -162,7 +248,7 @@
 }
 /* END_CASE */
 
-/* BEGIN_CASE depends_on:MBEDTLS_FS_IO:MBEDTLS_X509_CRT_PARSE_C:!MBEDTLS_X509_REMOVE_INFO */
+/* BEGIN_CASE depends_on:MBEDTLS_SSL_TLS_C:MBEDTLS_FS_IO:MBEDTLS_X509_CRT_PARSE_C:!MBEDTLS_X509_REMOVE_INFO */
 void mbedtls_debug_print_crt(char *crt_file, char *file, int line,
                              char *prefix, char *result_str)
 {
@@ -202,7 +288,7 @@
 }
 /* END_CASE */
 
-/* BEGIN_CASE depends_on:MBEDTLS_BIGNUM_C */
+/* BEGIN_CASE depends_on:MBEDTLS_SSL_TLS_C:MBEDTLS_BIGNUM_C */
 void mbedtls_debug_print_mpi(char *value, char *file, int line,
                              char *prefix, char *result_str)
 {
diff --git a/tests/suites/test_suite_md.function b/tests/suites/test_suite_md.function
index 4e62154..4497d04 100644
--- a/tests/suites/test_suite_md.function
+++ b/tests/suites/test_suite_md.function
@@ -420,7 +420,7 @@
 }
 /* END_CASE */
 
-/* BEGIN_CASE */
+/* BEGIN_CASE depends_on:MBEDTLS_PSA_CRYPTO_C */
 void md_psa_dynamic_dispatch(int md_type, int pre_psa_ret, int post_psa_engine)
 {
     const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(md_type);
diff --git a/tests/suites/test_suite_pk.function b/tests/suites/test_suite_pk.function
index f197d04..a8cf711 100644
--- a/tests/suites/test_suite_pk.function
+++ b/tests/suites/test_suite_pk.function
@@ -181,7 +181,7 @@
 #define MBEDTLS_MD_ALG_FOR_TEST         MBEDTLS_MD_SHA512
 #endif
 
-#include <../src/test_keys.h>
+#include <test/test_keys.h>
 
 /* Define an RSA key size we know it's present in predefined_key[] array. */
 #define RSA_KEY_SIZE   1024
@@ -243,7 +243,7 @@
 
 /** Setup the provided PK context.
  *
- * Predefined keys used for the setup are taken from "test/src/test_keys.h"
+ * Predefined keys used for the setup are taken from <test/test_keys.h>
  * which is automatically generated using "framework/scripts/generate_test_keys.py".
  *
  * \param pk               The PK object to fill. It must  have been initialized
diff --git a/tests/suites/test_suite_psa_crypto.data b/tests/suites/test_suite_psa_crypto.data
index bbf7575..038d912 100644
--- a/tests/suites/test_suite_psa_crypto.data
+++ b/tests/suites/test_suite_psa_crypto.data
@@ -156,10 +156,6 @@
 depends_on:PSA_WANT_ALG_RSA_PKCS1V15_SIGN:PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_BASIC:PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_IMPORT:PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_EXPORT:PSA_CRYPTO_DRIVER_TEST
 import_export:"3082025e02010002818100af057d396ee84fb75fdbb5c2b13c7fe5a654aa8aa2470b541ee1feb0b12d25c79711531249e1129628042dbbb6c120d1443524ef4c0e6e1d8956eeb2077af12349ddeee54483bc06c2c61948cd02b202e796aebd94d3a7cbf859c2c1819c324cb82b9cd34ede263a2abffe4733f077869e8660f7d6834da53d690ef7985f6bc3020301000102818100874bf0ffc2f2a71d14671ddd0171c954d7fdbf50281e4f6d99ea0e1ebcf82faa58e7b595ffb293d1abe17f110b37c48cc0f36c37e84d876621d327f64bbe08457d3ec4098ba2fa0a319fba411c2841ed7be83196a8cdf9daa5d00694bc335fc4c32217fe0488bce9cb7202e59468b1ead119000477db2ca797fac19eda3f58c1024100e2ab760841bb9d30a81d222de1eb7381d82214407f1b975cbbfe4e1a9467fd98adbd78f607836ca5be1928b9d160d97fd45c12d6b52e2c9871a174c66b488113024100c5ab27602159ae7d6f20c3c2ee851e46dc112e689e28d5fcbbf990a99ef8a90b8bb44fd36467e7fc1789ceb663abda338652c3c73f111774902e840565927091024100b6cdbd354f7df579a63b48b3643e353b84898777b48b15f94e0bfc0567a6ae5911d57ad6409cf7647bf96264e9bd87eb95e263b7110b9a1f9f94acced0fafa4d024071195eec37e8d257decfc672b07ae639f10cbb9b0c739d0c809968d644a94e3fd6ed9287077a14583f379058f76a8aecd43c62dc8c0f41766650d725275ac4a1024100bb32d133edc2e048d463388b7be9cb4be29f4b6250be603e70e3647501c97ddde20a4e71be95fd5e71784e25aca4baf25be5738aae59bbfe1c997781447a2b24":PSA_KEY_TYPE_RSA_KEY_PAIR:PSA_KEY_USAGE_EXPORT:PSA_ALG_RSA_PKCS1V15_SIGN_RAW:PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION( PSA_KEY_PERSISTENCE_VOLATILE, TEST_DRIVER_LOCATION ):1024:-1:PSA_ERROR_BUFFER_TOO_SMALL:1
 
-PSA import/export RSA keypair: trailing garbage rejected, opaque
-depends_on:PSA_WANT_ALG_RSA_PKCS1V15_SIGN:PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_BASIC:PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_IMPORT:PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_EXPORT:PSA_CRYPTO_DRIVER_TEST
-import_with_data:"3082025e02010002818100af057d396ee84fb75fdbb5c2b13c7fe5a654aa8aa2470b541ee1feb0b12d25c79711531249e1129628042dbbb6c120d1443524ef4c0e6e1d8956eeb2077af12349ddeee54483bc06c2c61948cd02b202e796aebd94d3a7cbf859c2c1819c324cb82b9cd34ede263a2abffe4733f077869e8660f7d6834da53d690ef7985f6bc3020301000102818100874bf0ffc2f2a71d14671ddd0171c954d7fdbf50281e4f6d99ea0e1ebcf82faa58e7b595ffb293d1abe17f110b37c48cc0f36c37e84d876621d327f64bbe08457d3ec4098ba2fa0a319fba411c2841ed7be83196a8cdf9daa5d00694bc335fc4c32217fe0488bce9cb7202e59468b1ead119000477db2ca797fac19eda3f58c1024100e2ab760841bb9d30a81d222de1eb7381d82214407f1b975cbbfe4e1a9467fd98adbd78f607836ca5be1928b9d160d97fd45c12d6b52e2c9871a174c66b488113024100c5ab27602159ae7d6f20c3c2ee851e46dc112e689e28d5fcbbf990a99ef8a90b8bb44fd36467e7fc1789ceb663abda338652c3c73f111774902e840565927091024100b6cdbd354f7df579a63b48b3643e353b84898777b48b15f94e0bfc0567a6ae5911d57ad6409cf7647bf96264e9bd87eb95e263b7110b9a1f9f94acced0fafa4d024071195eec37e8d257decfc672b07ae639f10cbb9b0c739d0c809968d644a94e3fd6ed9287077a14583f379058f76a8aecd43c62dc8c0f41766650d725275ac4a1024100bb32d133edc2e048d463388b7be9cb4be29f4b6250be603e70e3647501c97ddde20a4e71be95fd5e71784e25aca4baf25be5738aae59bbfe1c997781447a2b2400":PSA_KEY_TYPE_RSA_KEY_PAIR:1024:PSA_ERROR_INVALID_ARGUMENT
-
 PSA import RSA keypair: truncated
 depends_on:PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_BASIC:PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_IMPORT
 import_with_data:"3082025e02010002818100af057d396ee84fb75fdbb5c2b13c7fe5a654aa8aa2470b541ee1feb0b12d25c79711531249e1129628042dbbb6c120d1443524ef4c0e6e1d8956eeb2077af12349ddeee54483bc06c2c61948cd02b202e796aebd94d3a7cbf859c2c1819c324cb82b9cd34ede263a2abffe4733f077869e8660f7d6834da53d690ef7985f6bc3020301000102818100874bf0ffc2f2a71d14671ddd0171c954d7fdbf50281e4f6d99ea0e1ebcf82faa58e7b595ffb293d1abe17f110b37c48cc0f36c37e84d876621d327f64bbe08457d3ec4098ba2fa0a319fba411c2841ed7be83196a8cdf9daa5d00694bc335fc4c32217fe0488bce9cb7202e59468b1ead119000477db2ca797fac19eda3f58c1024100e2ab760841bb9d30a81d222de1eb7381d82214407f1b975cbbfe4e1a9467fd98adbd78f607836ca5be1928b9d160d97fd45c12d6b52e2c9871a174c66b488113024100c5ab27602159ae7d6f20c3c2ee851e46dc112e689e28d5fcbbf990a99ef8a90b8bb44fd36467e7fc1789ceb663abda338652c3c73f111774902e840565927091024100b6cdbd354f7df579a63b48b3643e353b84898777b48b15f94e0bfc0567a6ae5911d57ad6409cf7647bf96264e9bd87eb95e263b7110b9a1f9f94acced0fafa4d024071195eec37e8d257decfc672b07ae639f10cbb9b0c739d0c809968d644a94e3fd6ed9287077a14583f379058f76a8aecd43c62dc8c0f41766650d725275ac4a1024100bb32d133edc2e048d463388b7be9cb4be29f4b6250be603e70e3647501c97ddde20a4e71be95fd5e71784e25aca4baf25be5738aae59bbfe1c997781447a2b":PSA_KEY_TYPE_RSA_KEY_PAIR:0:PSA_ERROR_INVALID_ARGUMENT
@@ -422,7 +418,7 @@
 
 PSA import/export-public EC brainpool512r1: good, opaque
 depends_on:PSA_WANT_ALG_ECDSA:PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_BASIC:PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_IMPORT:PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_EXPORT:PSA_WANT_ECC_BRAINPOOL_P_R1_512:PSA_CRYPTO_DRIVER_TEST
-import_export_public_key:"372c9778f69f726cbca3f4a268f16b4d617d10280d79a6a029cd51879fe1012934dfe5395455337df6906dc7d6d2eea4dbb2065c0228f73b3ed716480e7d71d2":PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_BRAINPOOL_P_R1):PSA_ALG_ECDSA_ANY:0:0:PSA_SUCCESS:"0438b7ec92b61c5c6c7fbc28a4ec759d48fcd4e2e374defd5c4968a54dbef7510e517886fbfc38ea39aa529359d70a7156c35d3cbac7ce776bdb251dd64bce71234424ee7049eed072f0dbc4d79996e175d557e263763ae97095c081e73e7db2e38adc3d4c9a0487b1ede876dc1fca61c902e9a1d8722b8612928f18a24845591a"
+import_export_public_key:"372c9778f69f726cbca3f4a268f16b4d617d10280d79a6a029cd51879fe1012934dfe5395455337df6906dc7d6d2eea4dbb2065c0228f73b3ed716480e7d71d2":PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_BRAINPOOL_P_R1):PSA_ALG_ECDSA_ANY:PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION( PSA_KEY_PERSISTENCE_VOLATILE, TEST_DRIVER_LOCATION ):0:PSA_SUCCESS:"0438b7ec92b61c5c6c7fbc28a4ec759d48fcd4e2e374defd5c4968a54dbef7510e517886fbfc38ea39aa529359d70a7156c35d3cbac7ce776bdb251dd64bce71234424ee7049eed072f0dbc4d79996e175d557e263763ae97095c081e73e7db2e38adc3d4c9a0487b1ede876dc1fca61c902e9a1d8722b8612928f18a24845591a"
 
 PSA import/export EC curve25519 key pair: good (already properly masked), opaque
 depends_on:PSA_WANT_ALG_ECDH:PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_BASIC:PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_IMPORT:PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_EXPORT:PSA_WANT_ECC_MONTGOMERY_255:PSA_CRYPTO_DRIVER_TEST
@@ -4489,11 +4485,11 @@
 
 PSA sign hash int (ops=inf): det ECDSA not supported
 depends_on:!PSA_WANT_ALG_DETERMINISTIC_ECDSA:PSA_WANT_ALG_ECDSA:PSA_WANT_ALG_SHA_256:PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_BASIC:PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_IMPORT:PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_EXPORT:PSA_WANT_ECC_SECP_R1_384
-sign_hash_fail_interruptible:PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1):"3f5d8d9be280b5696cc5cc9f94cf8af7e6b61dd6592b2ab2b3a4c607450417ec327dcdcaed7c10053d719a0574f0a76a":PSA_ALG_DETERMINISTIC_ECDSA( PSA_ALG_SHA_256 ):"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824":96:PSA_SUCCESS:PSA_ERROR_NOT_SUPPORTED:PSA_INTERRUPTIBLE_MAX_OPS_UNLIMITED
+sign_hash_fail_interruptible:PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1):"3f5d8d9be280b5696cc5cc9f94cf8af7e6b61dd6592b2ab2b3a4c607450417ec327dcdcaed7c10053d719a0574f0a76a":PSA_ALG_DETERMINISTIC_ECDSA( PSA_ALG_SHA_256 ):"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824":96:PSA_ERROR_NOT_SUPPORTED:PSA_ERROR_BAD_STATE:PSA_INTERRUPTIBLE_MAX_OPS_UNLIMITED
 
 PSA sign hash int (ops=min): det ECDSA not supported
 depends_on:!PSA_WANT_ALG_DETERMINISTIC_ECDSA:PSA_WANT_ALG_ECDSA:PSA_WANT_ALG_SHA_256:PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_BASIC:PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_IMPORT:PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_EXPORT:PSA_WANT_ECC_SECP_R1_384
-sign_hash_fail_interruptible:PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1):"3f5d8d9be280b5696cc5cc9f94cf8af7e6b61dd6592b2ab2b3a4c607450417ec327dcdcaed7c10053d719a0574f0a76a":PSA_ALG_DETERMINISTIC_ECDSA( PSA_ALG_SHA_256 ):"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824":96:PSA_SUCCESS:PSA_ERROR_NOT_SUPPORTED:0
+sign_hash_fail_interruptible:PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1):"3f5d8d9be280b5696cc5cc9f94cf8af7e6b61dd6592b2ab2b3a4c607450417ec327dcdcaed7c10053d719a0574f0a76a":PSA_ALG_DETERMINISTIC_ECDSA( PSA_ALG_SHA_256 ):"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824":96:PSA_ERROR_NOT_SUPPORTED:PSA_ERROR_BAD_STATE:0
 
 PSA sign/verify hash: RSA PKCS#1 v1.5, raw
 depends_on:PSA_WANT_ALG_RSA_PKCS1V15_SIGN:PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_BASIC:PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_IMPORT
@@ -4735,6 +4731,29 @@
 depends_on:PSA_WANT_ALG_DETERMINISTIC_ECDSA:PSA_WANT_ALG_SHA_256:PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_BASIC:PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_IMPORT:PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_EXPORT:PSA_WANT_ECC_SECP_R1_384
 verify_hash_interruptible:PSA_KEY_TYPE_ECC_PUBLIC_KEY(PSA_ECC_FAMILY_SECP_R1):"04d9c662b50ba29ca47990450e043aeaf4f0c69b15676d112f622a71c93059af999691c5680d2b44d111579db12f4a413a2ed5c45fcfb67b5b63e00b91ebe59d09a6b1ac2c0c4282aa12317ed5914f999bc488bb132e8342cc36f2ca5e3379c747":PSA_ALG_DETERMINISTIC_ECDSA(PSA_ALG_SHA_256):"9ac4335b469bbd791439248504dd0d49c71349a295fee5a1c68507f45a9e1c7b":"bed412df472eef873fb0839f91a6867d1c6824d4c5781d4b851faa43c7df904d99dbdd28c0d2fd3a4a006e89d34993a120aff166deb4974e96449a7ffe93c66726ad9443b14b87330c86bdde3faff5fd1cbfdc9afe46f8090376f9664cb116b4":PSA_INTERRUPTIBLE_MAX_OPS_UNLIMITED
 
+# The next 4 test cases check what happens if only one of the two ECDSA
+# variants is supported. The ECDSA variants (deterministic and randomized)
+# are different signature algorithms that can be enabled independently,
+# but they have the same verification. Mbed TLS accepts either variant
+# as the algorithm requested for verification even if that variant is not
+# supported. Test that this works. It would also be acceptable if the
+# library returned NOT_SUPPORTED in this case.
+PSA verify hash: ECDSA SECP256R1, only deterministic supported
+depends_on:!PSA_WANT_ALG_ECDSA:PSA_WANT_ALG_DETERMINISTIC_ECDSA:PSA_WANT_KEY_TYPE_ECC_PUBLIC_KEY:PSA_WANT_ECC_SECP_R1_256
+verify_hash:PSA_KEY_TYPE_ECC_PUBLIC_KEY(PSA_ECC_FAMILY_SECP_R1):"04dea5e45d0ea37fc566232a508f4ad20ea13d47e4bf5fa4d54a57a0ba012042087097496efc583fed8b24a5b9be9a51de063f5a00a8b698a16fd7f29b5485f320":PSA_ALG_ECDSA_ANY:"9ac4335b469bbd791439248504dd0d49c71349a295fee5a1c68507f45a9e1c7b":"6a3399f69421ffe1490377adf2ea1f117d81a63cf5bf22e918d51175eb259151ce95d7c26cc04e25503e2f7a1ec3573e3c2412534bb4a19b3a7811742f49f50f"
+
+PSA verify hash with keypair: ECDSA SECP256R1, only deterministic supported
+depends_on:!PSA_WANT_ALG_ECDSA:PSA_WANT_ALG_DETERMINISTIC_ECDSA:PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_IMPORT:PSA_WANT_ECC_SECP_R1_256
+verify_hash:PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1):"ab45435712649cb30bbddac49197eebf2740ffc7f874d9244c3460f54f322d3a":PSA_ALG_ECDSA_ANY:"9ac4335b469bbd791439248504dd0d49c71349a295fee5a1c68507f45a9e1c7b":"6a3399f69421ffe1490377adf2ea1f117d81a63cf5bf22e918d51175eb259151ce95d7c26cc04e25503e2f7a1ec3573e3c2412534bb4a19b3a7811742f49f50f"
+
+PSA verify hash: deterministic ECDSA SECP256R1, only randomized supported
+depends_on:PSA_WANT_ALG_ECDSA:!PSA_WANT_ALG_DETERMINISTIC_ECDSA:PSA_WANT_KEY_TYPE_ECC_PUBLIC_KEY:PSA_WANT_ECC_SECP_R1_256:PSA_WANT_ALG_SHA_256
+verify_hash:PSA_KEY_TYPE_ECC_PUBLIC_KEY(PSA_ECC_FAMILY_SECP_R1):"04dea5e45d0ea37fc566232a508f4ad20ea13d47e4bf5fa4d54a57a0ba012042087097496efc583fed8b24a5b9be9a51de063f5a00a8b698a16fd7f29b5485f320":PSA_ALG_DETERMINISTIC_ECDSA(PSA_ALG_SHA_256):"9ac4335b469bbd791439248504dd0d49c71349a295fee5a1c68507f45a9e1c7b":"6a3399f69421ffe1490377adf2ea1f117d81a63cf5bf22e918d51175eb259151ce95d7c26cc04e25503e2f7a1ec3573e3c2412534bb4a19b3a7811742f49f50f"
+
+PSA verify hash with keypair: deterministic ECDSA SECP256R1, only randomized supported
+depends_on:PSA_WANT_ALG_ECDSA:!PSA_WANT_ALG_DETERMINISTIC_ECDSA:PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_IMPORT:PSA_WANT_ECC_SECP_R1_256:PSA_WANT_ALG_SHA_256
+verify_hash:PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1):"ab45435712649cb30bbddac49197eebf2740ffc7f874d9244c3460f54f322d3a":PSA_ALG_DETERMINISTIC_ECDSA(PSA_ALG_SHA_256):"9ac4335b469bbd791439248504dd0d49c71349a295fee5a1c68507f45a9e1c7b":"6a3399f69421ffe1490377adf2ea1f117d81a63cf5bf22e918d51175eb259151ce95d7c26cc04e25503e2f7a1ec3573e3c2412534bb4a19b3a7811742f49f50f"
+
 PSA verify hash: ECDSA SECP256R1, wrong signature size (correct but ASN1-encoded)
 depends_on:PSA_WANT_ALG_ECDSA:PSA_WANT_KEY_TYPE_ECC_PUBLIC_KEY:PSA_WANT_ECC_SECP_R1_256
 verify_hash_fail:PSA_KEY_TYPE_ECC_PUBLIC_KEY(PSA_ECC_FAMILY_SECP_R1):"04dea5e45d0ea37fc566232a508f4ad20ea13d47e4bf5fa4d54a57a0ba012042087097496efc583fed8b24a5b9be9a51de063f5a00a8b698a16fd7f29b5485f320":PSA_ALG_ECDSA_ANY:"9ac4335b469bbd791439248504dd0d49c71349a295fee5a1c68507f45a9e1c7b":"304502206a3399f69421ffe1490377adf2ea1f117d81a63cf5bf22e918d51175eb259151022100ce95d7c26cc04e25503e2f7a1ec3573e3c2412534bb4a19b3a7811742f49f50f":PSA_ERROR_INVALID_SIGNATURE
@@ -4844,14 +4863,14 @@
 sign_message_fail:PSA_KEY_TYPE_RSA_KEY_PAIR:"3082025e02010002818100af057d396ee84fb75fdbb5c2b13c7fe5a654aa8aa2470b541ee1feb0b12d25c79711531249e1129628042dbbb6c120d1443524ef4c0e6e1d8956eeb2077af12349ddeee54483bc06c2c61948cd02b202e796aebd94d3a7cbf859c2c1819c324cb82b9cd34ede263a2abffe4733f077869e8660f7d6834da53d690ef7985f6bc3020301000102818100874bf0ffc2f2a71d14671ddd0171c954d7fdbf50281e4f6d99ea0e1ebcf82faa58e7b595ffb293d1abe17f110b37c48cc0f36c37e84d876621d327f64bbe08457d3ec4098ba2fa0a319fba411c2841ed7be83196a8cdf9daa5d00694bc335fc4c32217fe0488bce9cb7202e59468b1ead119000477db2ca797fac19eda3f58c1024100e2ab760841bb9d30a81d222de1eb7381d82214407f1b975cbbfe4e1a9467fd98adbd78f607836ca5be1928b9d160d97fd45c12d6b52e2c9871a174c66b488113024100c5ab27602159ae7d6f20c3c2ee851e46dc112e689e28d5fcbbf990a99ef8a90b8bb44fd36467e7fc1789ceb663abda338652c3c73f111774902e840565927091024100b6cdbd354f7df579a63b48b3643e353b84898777b48b15f94e0bfc0567a6ae5911d57ad6409cf7647bf96264e9bd87eb95e263b7110b9a1f9f94acced0fafa4d024071195eec37e8d257decfc672b07ae639f10cbb9b0c739d0c809968d644a94e3fd6ed9287077a14583f379058f76a8aecd43c62dc8c0f41766650d725275ac4a1024100bb32d133edc2e048d463388b7be9cb4be29f4b6250be603e70e3647501c97ddde20a4e71be95fd5e71784e25aca4baf25be5738aae59bbfe1c997781447a2b24":PSA_ALG_RSA_PKCS1V15_SIGN_RAW:"616263":0:PSA_ERROR_INVALID_ARGUMENT
 
 PSA sign message: RSA PKCS#1 v1.5 SHA-256, invalid key type
-depends_on:PSA_WANT_ALG_RSA_PKCS1V15_SIGN:PSA_WANT_KEY_TYPE_CHACHA20
+depends_on:PSA_WANT_ALG_RSA_PKCS1V15_SIGN:PSA_WANT_ALG_SHA_256:PSA_WANT_KEY_TYPE_CHACHA20
 sign_message_fail:PSA_KEY_TYPE_CHACHA20:"4bddc98c551a95395ef719557f813656b566bc45aac04eca3866324cc75489f2":PSA_ALG_RSA_PKCS1V15_SIGN(PSA_ALG_SHA_256):"616263":128:PSA_ERROR_INVALID_ARGUMENT
 
-PSA sign message: ECDSA SECP256R1 SHA-256, invalid hash (wildcard)
+PSA sign message: ECDSA SECP256R1, invalid hash (wildcard)
 depends_on:PSA_WANT_ALG_ECDSA:PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_BASIC:PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_IMPORT:PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_EXPORT:PSA_WANT_ECC_SECP_R1_256
 sign_message_fail:PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1):"ab45435712649cb30bbddac49197eebf2740ffc7f874d9244c3460f54f322d3a":PSA_ALG_ECDSA(PSA_ALG_ANY_HASH):"616263":64:PSA_ERROR_INVALID_ARGUMENT
 
-PSA sign message: ECDSA SECP256R1 SHA-256, invalid hash algorithm (0)
+PSA sign message: ECDSA SECP256R1, invalid hash algorithm (0)
 depends_on:PSA_WANT_ALG_ECDSA:PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_BASIC:PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_IMPORT:PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_EXPORT:PSA_WANT_ECC_SECP_R1_256
 sign_message_fail:PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1):"ab45435712649cb30bbddac49197eebf2740ffc7f874d9244c3460f54f322d3a":PSA_ALG_ECDSA(0):"616263":64:PSA_ERROR_INVALID_ARGUMENT
 
@@ -5548,11 +5567,11 @@
 
 PSA key derivation: PBKDF2-HMAC-SHA256, salt and password before cost
 depends_on:PSA_WANT_ALG_PBKDF2_HMAC:PSA_WANT_ALG_SHA_256
-derive_input:PSA_ALG_PBKDF2_HMAC(PSA_ALG_SHA_256):PSA_KEY_DERIVATION_INPUT_SALT:PSA_KEY_TYPE_NONE:"73616c74":PSA_ERROR_BAD_STATE:PSA_KEY_DERIVATION_INPUT_PASSWORD:PSA_KEY_TYPE_PASSWORD:"706173737764":PSA_ERROR_BAD_STATE:PSA_KEY_DERIVATION_INPUT_COST:INPUT_INTEGER:"01":PSA_ERROR_INVALID_ARGUMENT:PSA_KEY_TYPE_NONE:PSA_ERROR_BAD_STATE
+derive_input:PSA_ALG_PBKDF2_HMAC(PSA_ALG_SHA_256):PSA_KEY_DERIVATION_INPUT_SALT:PSA_KEY_TYPE_NONE:"73616c74":PSA_ERROR_BAD_STATE:PSA_KEY_DERIVATION_INPUT_PASSWORD:PSA_KEY_TYPE_PASSWORD:"706173737764":PSA_ERROR_BAD_STATE:PSA_KEY_DERIVATION_INPUT_COST:INPUT_INTEGER:"01":PSA_ERROR_BAD_STATE:PSA_KEY_TYPE_NONE:PSA_ERROR_BAD_STATE
 
 PSA key derivation: PBKDF2-HMAC-SHA256, password before cost
 depends_on:PSA_WANT_ALG_PBKDF2_HMAC:PSA_WANT_ALG_SHA_256
-derive_input:PSA_ALG_PBKDF2_HMAC(PSA_ALG_SHA_256):PSA_KEY_DERIVATION_INPUT_PASSWORD:PSA_KEY_TYPE_PASSWORD:"706173737764":PSA_ERROR_BAD_STATE:PSA_KEY_DERIVATION_INPUT_COST:INPUT_INTEGER:"01":PSA_ERROR_INVALID_ARGUMENT:PSA_KEY_DERIVATION_INPUT_SALT:PSA_KEY_TYPE_NONE:"73616c74":PSA_ERROR_BAD_STATE:PSA_KEY_TYPE_NONE:PSA_ERROR_BAD_STATE
+derive_input:PSA_ALG_PBKDF2_HMAC(PSA_ALG_SHA_256):PSA_KEY_DERIVATION_INPUT_PASSWORD:PSA_KEY_TYPE_PASSWORD:"706173737764":PSA_ERROR_BAD_STATE:PSA_KEY_DERIVATION_INPUT_COST:INPUT_INTEGER:"01":PSA_ERROR_BAD_STATE:PSA_KEY_DERIVATION_INPUT_SALT:PSA_KEY_TYPE_NONE:"73616c74":PSA_ERROR_BAD_STATE:PSA_KEY_TYPE_NONE:PSA_ERROR_BAD_STATE
 
 PSA key derivation: PBKDF2-HMAC-SHA256, password bad key type
 depends_on:PSA_WANT_ALG_PBKDF2_HMAC:PSA_WANT_ALG_SHA_256
@@ -5624,11 +5643,11 @@
 
 PSA key derivation: PBKDF2-AES-CMAC-PRF-128, salt and password before cost
 depends_on:PSA_WANT_ALG_PBKDF2_AES_CMAC_PRF_128:PSA_WANT_ALG_CMAC:PSA_WANT_KEY_TYPE_AES
-derive_input:PSA_ALG_PBKDF2_AES_CMAC_PRF_128:PSA_KEY_DERIVATION_INPUT_SALT:PSA_KEY_TYPE_NONE:"73616c74":PSA_ERROR_BAD_STATE:PSA_KEY_DERIVATION_INPUT_PASSWORD:PSA_KEY_TYPE_PASSWORD:"706173737764":PSA_ERROR_BAD_STATE:PSA_KEY_DERIVATION_INPUT_COST:INPUT_INTEGER:"01":PSA_ERROR_INVALID_ARGUMENT:PSA_KEY_TYPE_NONE:PSA_ERROR_BAD_STATE
+derive_input:PSA_ALG_PBKDF2_AES_CMAC_PRF_128:PSA_KEY_DERIVATION_INPUT_SALT:PSA_KEY_TYPE_NONE:"73616c74":PSA_ERROR_BAD_STATE:PSA_KEY_DERIVATION_INPUT_PASSWORD:PSA_KEY_TYPE_PASSWORD:"706173737764":PSA_ERROR_BAD_STATE:PSA_KEY_DERIVATION_INPUT_COST:INPUT_INTEGER:"01":PSA_ERROR_BAD_STATE:PSA_KEY_TYPE_NONE:PSA_ERROR_BAD_STATE
 
 PSA key derivation: PBKDF2-AES-CMAC-PRF-128, password before cost
 depends_on:PSA_WANT_ALG_PBKDF2_AES_CMAC_PRF_128:PSA_WANT_ALG_CMAC:PSA_WANT_KEY_TYPE_AES
-derive_input:PSA_ALG_PBKDF2_AES_CMAC_PRF_128:PSA_KEY_DERIVATION_INPUT_PASSWORD:PSA_KEY_TYPE_PASSWORD:"706173737764":PSA_ERROR_BAD_STATE:PSA_KEY_DERIVATION_INPUT_COST:INPUT_INTEGER:"01":PSA_ERROR_INVALID_ARGUMENT:PSA_KEY_DERIVATION_INPUT_SALT:PSA_KEY_TYPE_NONE:"73616c74":PSA_ERROR_BAD_STATE:PSA_KEY_TYPE_NONE:PSA_ERROR_BAD_STATE
+derive_input:PSA_ALG_PBKDF2_AES_CMAC_PRF_128:PSA_KEY_DERIVATION_INPUT_PASSWORD:PSA_KEY_TYPE_PASSWORD:"706173737764":PSA_ERROR_BAD_STATE:PSA_KEY_DERIVATION_INPUT_COST:INPUT_INTEGER:"01":PSA_ERROR_BAD_STATE:PSA_KEY_DERIVATION_INPUT_SALT:PSA_KEY_TYPE_NONE:"73616c74":PSA_ERROR_BAD_STATE:PSA_KEY_TYPE_NONE:PSA_ERROR_BAD_STATE
 
 PSA key derivation: PBKDF2-AES-CMAC-PRF-128, password bad key type
 depends_on:PSA_WANT_ALG_PBKDF2_AES_CMAC_PRF_128:PSA_WANT_ALG_CMAC:PSA_WANT_KEY_TYPE_AES
@@ -5678,6 +5697,10 @@
 depends_on:PSA_WANT_ALG_PBKDF2_AES_CMAC_PRF_128:PSA_WANT_ALG_CMAC:PSA_WANT_KEY_TYPE_AES
 derive_input_invalid_cost:PSA_ALG_PBKDF2_AES_CMAC_PRF_128:PSA_VENDOR_PBKDF2_MAX_ITERATIONS+1ULL
 
+PSA key derivation: reject calling input functions without calling setup
+depends_on:PSA_WANT_ALG_SHA_256
+derive_input:PSA_ALG_NONE:PSA_KEY_DERIVATION_INPUT_COST:INPUT_INTEGER:"01":PSA_ERROR_BAD_STATE:PSA_KEY_DERIVATION_INPUT_SALT:PSA_KEY_TYPE_NONE:"73616c74":PSA_ERROR_BAD_STATE:PSA_KEY_DERIVATION_INPUT_PASSWORD:PSA_KEY_TYPE_NONE:"706173737764":PSA_ERROR_BAD_STATE:PSA_KEY_TYPE_NONE:PSA_ERROR_BAD_STATE
+
 PSA key derivation over capacity: HKDF
 depends_on:PSA_WANT_ALG_HKDF:PSA_WANT_ALG_SHA_256
 derive_over_capacity:PSA_ALG_HKDF(PSA_ALG_SHA_256)
diff --git a/tests/suites/test_suite_psa_crypto.function b/tests/suites/test_suite_psa_crypto.function
index 94bf28b..da6c1c4 100644
--- a/tests/suites/test_suite_psa_crypto.function
+++ b/tests/suites/test_suite_psa_crypto.function
@@ -8839,7 +8839,9 @@
     psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_DERIVE);
     psa_set_key_algorithm(&attributes, alg);
 
-    PSA_ASSERT(psa_key_derivation_setup(&operation, alg));
+    if (alg != PSA_ALG_NONE) {
+        PSA_ASSERT(psa_key_derivation_setup(&operation, alg));
+    }
 
     for (i = 0; i < ARRAY_LENGTH(steps); i++) {
         mbedtls_test_set_step(i);
diff --git a/tests/suites/test_suite_psa_crypto_not_supported.function b/tests/suites/test_suite_psa_crypto_not_supported.function
index e5e66f4..4f15a3f 100644
--- a/tests/suites/test_suite_psa_crypto_not_supported.function
+++ b/tests/suites/test_suite_psa_crypto_not_supported.function
@@ -20,10 +20,28 @@
 
     PSA_ASSERT(psa_crypto_init());
     psa_set_key_type(&attributes, key_type);
-    TEST_EQUAL(psa_import_key(&attributes,
-                              key_material->x, key_material->len,
-                              &key_id),
-               PSA_ERROR_NOT_SUPPORTED);
+    psa_status_t actual_status =
+        psa_import_key(&attributes, key_material->x, key_material->len, &key_id);
+
+#if defined(PSA_WANT_KEY_TYPE_ECC_PUBLIC_KEY)
+    if (actual_status == PSA_ERROR_INVALID_ARGUMENT) {
+        /* Edge case: when importing an ECC public key with an unspecified
+         * bit-size (as we do here), the implementation of psa_import_key()
+         * infers the bit-size from the input. If the key type specifies an
+         * unknown curve, the validation might reject the data as invalid
+         * before it checks that the curve is supported. If so, that's ok.
+         * In practice, at the time of writing, this happens with Ed25519,
+         * for which a valid but unsupported 32-byte input causes
+         * psa_import_key() to fail because it assumes a Weierstrass curve
+         * which must have an odd-length encoding.
+         *
+         * In other cases, we do not expect an INVALID_ARGUMENT error here. */
+        TEST_ASSERT(PSA_KEY_TYPE_IS_ECC(key_type));
+    } else
+#endif /* defined(PSA_WANT_KEY_TYPE_ECC_PUBLIC_KEY) */
+    {
+        TEST_EQUAL(actual_status, PSA_ERROR_NOT_SUPPORTED);
+    }
     TEST_ASSERT(mbedtls_svc_key_id_equal(key_id, MBEDTLS_SVC_KEY_ID_INIT));
 
 exit:
diff --git a/tests/suites/test_suite_psa_crypto_op_fail.function b/tests/suites/test_suite_psa_crypto_op_fail.function
index 9289869..1d299b9 100644
--- a/tests/suites/test_suite_psa_crypto_op_fail.function
+++ b/tests/suites/test_suite_psa_crypto_op_fail.function
@@ -223,12 +223,9 @@
     size_t length = SIZE_MAX;
     psa_sign_hash_interruptible_operation_t sign_operation =
         psa_sign_hash_interruptible_operation_init();
-
     psa_verify_hash_interruptible_operation_t verify_operation =
         psa_verify_hash_interruptible_operation_init();
 
-
-
     PSA_INIT();
 
     psa_set_key_type(&attributes, key_type);
@@ -252,8 +249,8 @@
     PSA_ASSERT(psa_sign_hash_abort(&sign_operation));
 
     if (!private_only) {
-        /* Determine a plausible signature size to avoid an INVALID_SIGNATURE
-         * error based on this. */
+        /* Construct a signature candidate of a plausible size to avoid an
+         * INVALID_SIGNATURE error based on an early size verification. */
         PSA_ASSERT(psa_get_key_attributes(key_id, &attributes));
         size_t key_bits = psa_get_key_bits(&attributes);
         size_t output_length = sizeof(output);
@@ -277,6 +274,8 @@
     }
 
 exit:
+    psa_sign_hash_abort(&sign_operation);
+    psa_verify_hash_abort(&verify_operation);
     psa_destroy_key(key_id);
     psa_reset_key_attributes(&attributes);
     PSA_DONE();
diff --git a/tests/suites/test_suite_psa_crypto_op_fail.misc.data b/tests/suites/test_suite_psa_crypto_op_fail.misc.data
index 7158f2d..0c69fa8 100644
--- a/tests/suites/test_suite_psa_crypto_op_fail.misc.data
+++ b/tests/suites/test_suite_psa_crypto_op_fail.misc.data
@@ -13,3 +13,24 @@
 PSA sign RSA_PSS(SHA_256): RSA_PSS not enabled, key pair
 depends_on:!PSA_WANT_ALG_RSA_PSS:PSA_WANT_ALG_SHA_256:PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_BASIC:PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_IMPORT
 sign_fail:PSA_KEY_TYPE_RSA_KEY_PAIR:"3082025e02010002818100af057d396ee84fb75fdbb5c2b13c7fe5a654aa8aa2470b541ee1feb0b12d25c79711531249e1129628042dbbb6c120d1443524ef4c0e6e1d8956eeb2077af12349ddeee54483bc06c2c61948cd02b202e796aebd94d3a7cbf859c2c1819c324cb82b9cd34ede263a2abffe4733f077869e8660f7d6834da53d690ef7985f6bc3020301000102818100874bf0ffc2f2a71d14671ddd0171c954d7fdbf50281e4f6d99ea0e1ebcf82faa58e7b595ffb293d1abe17f110b37c48cc0f36c37e84d876621d327f64bbe08457d3ec4098ba2fa0a319fba411c2841ed7be83196a8cdf9daa5d00694bc335fc4c32217fe0488bce9cb7202e59468b1ead119000477db2ca797fac19eda3f58c1024100e2ab760841bb9d30a81d222de1eb7381d82214407f1b975cbbfe4e1a9467fd98adbd78f607836ca5be1928b9d160d97fd45c12d6b52e2c9871a174c66b488113024100c5ab27602159ae7d6f20c3c2ee851e46dc112e689e28d5fcbbf990a99ef8a90b8bb44fd36467e7fc1789ceb663abda338652c3c73f111774902e840565927091024100b6cdbd354f7df579a63b48b3643e353b84898777b48b15f94e0bfc0567a6ae5911d57ad6409cf7647bf96264e9bd87eb95e263b7110b9a1f9f94acced0fafa4d024071195eec37e8d257decfc672b07ae639f10cbb9b0c739d0c809968d644a94e3fd6ed9287077a14583f379058f76a8aecd43c62dc8c0f41766650d725275ac4a1024100bb32d133edc2e048d463388b7be9cb4be29f4b6250be603e70e3647501c97ddde20a4e71be95fd5e71784e25aca4baf25be5738aae59bbfe1c997781447a2b24":PSA_ALG_RSA_PSS(PSA_ALG_SHA_256):0:PSA_ERROR_NOT_SUPPORTED
+
+# There is a special case with ECDSA: deterministic and randomized ECDSA are
+# different signature algorithms that can be enabled independently, but
+# the verification algorithms are the same. Mbed TLS supports verification
+# of either variant when either variant is enabled. (It would also be correct
+# to reject the not-supported algorithm, but it would require a few more lines
+# of code.) In the automatically generated test cases, we avoid this difficulty
+# by making the not-supported test cases require neither variant to be
+# enabled. Here, test the signature operation when one variant is supported
+# but not the other. Testing the positive cases for the verification
+# operation is the job of test_suite_psa_crypto.
+#
+# We only test with one curve and one hash, because we know from a gray-box
+# approach that the curve and hash don't matter here.
+PSA sign DETERMINISTIC_ECDSA(SHA_256): !DETERMINISTIC_ECDSA but ECDSA with ECC_KEY_PAIR(SECP_R1)
+depends_on:!PSA_WANT_ALG_DETERMINISTIC_ECDSA:PSA_WANT_ALG_ECDSA:PSA_WANT_ALG_SHA_256:PSA_WANT_ECC_SECP_R1_192:PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_IMPORT
+sign_fail:PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1):"d83b57a59c51358d9c8bbb898aff507f44dd14cf16917190":PSA_ALG_DETERMINISTIC_ECDSA(PSA_ALG_SHA_256):1:PSA_ERROR_NOT_SUPPORTED
+
+PSA sign DETERMINISTIC_ECDSA(SHA_256): !ECDSA but DETERMINISTIC_ECDSA with ECC_KEY_PAIR(SECP_R1)
+depends_on:PSA_WANT_ALG_DETERMINISTIC_ECDSA:!PSA_WANT_ALG_ECDSA:PSA_WANT_ALG_SHA_256:PSA_WANT_ECC_SECP_R1_192:PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_IMPORT
+sign_fail:PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1):"d83b57a59c51358d9c8bbb898aff507f44dd14cf16917190":PSA_ALG_ECDSA(PSA_ALG_SHA_256):1:PSA_ERROR_NOT_SUPPORTED
diff --git a/tests/suites/test_suite_psa_crypto_se_driver_hal_mocks.function b/tests/suites/test_suite_psa_crypto_se_driver_hal_mocks.function
index efd24e9..b430096 100644
--- a/tests/suites/test_suite_psa_crypto_se_driver_hal_mocks.function
+++ b/tests/suites/test_suite_psa_crypto_se_driver_hal_mocks.function
@@ -196,6 +196,9 @@
     return mock_export_public_data.return_value;
 }
 
+#if defined(PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_IMPORT) && \
+    defined(PSA_WANT_ALG_ECDSA) && \
+    defined(PSA_WANT_ALG_SHA_256)
 static psa_status_t mock_sign(psa_drv_se_context_t *context,
                               psa_key_slot_number_t key_slot,
                               psa_algorithm_t alg,
@@ -218,7 +221,9 @@
 
     return mock_sign_data.return_value;
 }
+#endif
 
+#if defined(PSA_WANT_ALG_ECDSA) && defined(PSA_WANT_ALG_SHA_256)
 static psa_status_t mock_verify(psa_drv_se_context_t *context,
                                 psa_key_slot_number_t key_slot,
                                 psa_algorithm_t alg,
@@ -239,6 +244,7 @@
 
     return mock_verify_data.return_value;
 }
+#endif
 
 static psa_status_t mock_allocate(psa_drv_se_context_t *drv_context,
                                   void *persistent_data,
@@ -550,7 +556,7 @@
 }
 /* END_CASE */
 
-/* BEGIN_CASE */
+/* BEGIN_CASE depends_on:PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_IMPORT:PSA_WANT_ALG_ECDSA:PSA_WANT_ALG_SHA_256 */
 void mock_sign(int mock_sign_return_value, int expected_result)
 {
     psa_drv_se_t driver;
@@ -611,7 +617,7 @@
 }
 /* END_CASE */
 
-/* BEGIN_CASE */
+/* BEGIN_CASE depends_on:PSA_WANT_ALG_ECDSA:PSA_WANT_ALG_SHA_256 */
 void mock_verify(int mock_verify_return_value, int expected_result)
 {
     psa_drv_se_t driver;
diff --git a/tests/suites/test_suite_ssl.data b/tests/suites/test_suite_ssl.data
index ed0fa74..92bda3e 100644
--- a/tests/suites/test_suite_ssl.data
+++ b/tests/suites/test_suite_ssl.data
@@ -376,6 +376,10 @@
 depends_on:MBEDTLS_SSL_HAVE_AES:MBEDTLS_SSL_HAVE_CBC:MBEDTLS_MD_CAN_SHA256:MBEDTLS_RSA_C:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH
 handshake_cipher:"TLS-DHE-RSA-WITH-AES-256-CBC-SHA256":MBEDTLS_PK_RSA:0
 
+Handshake, ECDHE-RSA-WITH-AES-256-CBC-SHA384
+depends_on:MBEDTLS_SSL_HAVE_AES:MBEDTLS_SSL_HAVE_CBC:MBEDTLS_MD_CAN_SHA384:MBEDTLS_RSA_C:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH
+handshake_cipher:"TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384":MBEDTLS_PK_RSA:0
+
 Handshake, ECDHE-ECDSA-WITH-AES-256-CCM
 depends_on:MBEDTLS_SSL_HAVE_AES:MBEDTLS_SSL_HAVE_CCM:MBEDTLS_ECP_HAVE_SECP256R1:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH
 handshake_cipher:"TLS-ECDHE-ECDSA-WITH-AES-256-CCM":MBEDTLS_PK_ECDSA:0
@@ -404,6 +408,10 @@
 depends_on:MBEDTLS_SSL_HAVE_AES:MBEDTLS_SSL_HAVE_CBC:MBEDTLS_MD_CAN_SHA256:MBEDTLS_RSA_C:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_SSL_PROTO_DTLS:MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH
 handshake_cipher:"TLS-DHE-RSA-WITH-AES-256-CBC-SHA256":MBEDTLS_PK_RSA:1
 
+DTLS Handshake, ECDHE-RSA-WITH-AES-256-CBC-SHA384
+depends_on:MBEDTLS_SSL_HAVE_AES:MBEDTLS_SSL_HAVE_CBC:MBEDTLS_MD_CAN_SHA384:MBEDTLS_RSA_C:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_SSL_PROTO_DTLS:MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH
+handshake_cipher:"TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384":MBEDTLS_PK_RSA:1
+
 DTLS Handshake, ECDHE-ECDSA-WITH-AES-256-CCM
 depends_on:MBEDTLS_SSL_HAVE_AES:MBEDTLS_SSL_HAVE_CCM:MBEDTLS_ECP_HAVE_SECP256R1:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_SSL_PROTO_DTLS:MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH
 handshake_cipher:"TLS-ECDHE-ECDSA-WITH-AES-256-CCM":MBEDTLS_PK_ECDSA:1
@@ -420,13 +428,21 @@
 depends_on:MBEDTLS_RSA_C:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_SSL_PROTO_DTLS
 handshake_serialization
 
-DTLS Handshake fragmentation, MFL=512
-depends_on:MBEDTLS_SSL_PROTO_DTLS:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH
-handshake_fragmentation:MBEDTLS_SSL_MAX_FRAG_LEN_512:1:1
+DTLS Handshake fragmentation, MFL=512, DHE-RSA-WITH-AES-256-CBC-SHA256
+depends_on:MBEDTLS_SSL_PROTO_DTLS:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED:MBEDTLS_MD_CAN_SHA256
+handshake_fragmentation:MBEDTLS_SSL_MAX_FRAG_LEN_512:1:1:"TLS-DHE-RSA-WITH-AES-256-CBC-SHA256"
 
-DTLS Handshake fragmentation, MFL=1024
-depends_on:MBEDTLS_SSL_PROTO_DTLS:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH
-handshake_fragmentation:MBEDTLS_SSL_MAX_FRAG_LEN_1024:0:1
+DTLS Handshake fragmentation, MFL=1024, DHE-RSA-WITH-AES-256-CBC-SHA256
+depends_on:MBEDTLS_SSL_PROTO_DTLS:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED:MBEDTLS_MD_CAN_SHA256
+handshake_fragmentation:MBEDTLS_SSL_MAX_FRAG_LEN_1024:0:1:"TLS-DHE-RSA-WITH-AES-256-CBC-SHA256"
+
+DTLS Handshake fragmentation, MFL=512, ECDHE-RSA-WITH-AES-256-CBC-SHA384
+depends_on:MBEDTLS_SSL_PROTO_DTLS:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED:MBEDTLS_MD_CAN_SHA384
+handshake_fragmentation:MBEDTLS_SSL_MAX_FRAG_LEN_512:1:1:"TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384"
+
+DTLS Handshake fragmentation, MFL=1024, ECDHE-RSA-WITH-AES-256-CBC-SHA384
+depends_on:MBEDTLS_SSL_PROTO_DTLS:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH:MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED:MBEDTLS_MD_CAN_SHA384
+handshake_fragmentation:MBEDTLS_SSL_MAX_FRAG_LEN_1024:0:1:"TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384"
 
 Handshake min/max version check, all -> 1.2
 depends_on:MBEDTLS_SSL_PROTO_TLS1_2:!MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_CAN_HANDLE_RSA_TEST_KEY
@@ -853,6 +869,54 @@
 depends_on:MBEDTLS_SSL_HAVE_AES:MBEDTLS_SSL_HAVE_CBC:MBEDTLS_MD_CAN_SHA256:MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH
 resize_buffers_renegotiate_mfl:MBEDTLS_SSL_MAX_FRAG_LEN_4096:MBEDTLS_SSL_LEGACY_BREAK_HANDSHAKE:"TLS-DHE-RSA-WITH-AES-256-CBC-SHA256"
 
+DTLS no legacy renegotiation with MFL=512, ECDHE-RSA-WITH-AES-256-CBC-SHA384
+depends_on:MBEDTLS_SSL_HAVE_AES:MBEDTLS_SSL_HAVE_CBC:MBEDTLS_MD_CAN_SHA384:MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH
+resize_buffers_renegotiate_mfl:MBEDTLS_SSL_MAX_FRAG_LEN_512:MBEDTLS_SSL_LEGACY_NO_RENEGOTIATION:"TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384"
+
+DTLS no legacy renegotiation with MFL=1024, ECDHE-RSA-WITH-AES-256-CBC-SHA384
+depends_on:MBEDTLS_SSL_HAVE_AES:MBEDTLS_SSL_HAVE_CBC:MBEDTLS_MD_CAN_SHA384:MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH
+resize_buffers_renegotiate_mfl:MBEDTLS_SSL_MAX_FRAG_LEN_1024:MBEDTLS_SSL_LEGACY_NO_RENEGOTIATION:"TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384"
+
+DTLS no legacy renegotiation with MFL=2048, ECDHE-RSA-WITH-AES-256-CBC-SHA384
+depends_on:MBEDTLS_SSL_HAVE_AES:MBEDTLS_SSL_HAVE_CBC:MBEDTLS_MD_CAN_SHA384:MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH
+resize_buffers_renegotiate_mfl:MBEDTLS_SSL_MAX_FRAG_LEN_2048:MBEDTLS_SSL_LEGACY_NO_RENEGOTIATION:"TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384"
+
+DTLS no legacy renegotiation with MFL=4096, ECDHE-RSA-WITH-AES-256-CBC-SHA384
+depends_on:MBEDTLS_SSL_HAVE_AES:MBEDTLS_SSL_HAVE_CBC:MBEDTLS_MD_CAN_SHA384:MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH
+resize_buffers_renegotiate_mfl:MBEDTLS_SSL_MAX_FRAG_LEN_4096:MBEDTLS_SSL_LEGACY_NO_RENEGOTIATION:"TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384"
+
+DTLS legacy allow renegotiation with MFL=512, ECDHE-RSA-WITH-AES-256-CBC-SHA384
+depends_on:MBEDTLS_SSL_HAVE_AES:MBEDTLS_SSL_HAVE_CBC:MBEDTLS_MD_CAN_SHA384:MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH
+resize_buffers_renegotiate_mfl:MBEDTLS_SSL_MAX_FRAG_LEN_512:MBEDTLS_SSL_LEGACY_ALLOW_RENEGOTIATION:"TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384"
+
+DTLS legacy allow renegotiation with MFL=1024, ECDHE-RSA-WITH-AES-256-CBC-SHA384
+depends_on:MBEDTLS_SSL_HAVE_AES:MBEDTLS_SSL_HAVE_CBC:MBEDTLS_MD_CAN_SHA384:MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH
+resize_buffers_renegotiate_mfl:MBEDTLS_SSL_MAX_FRAG_LEN_1024:MBEDTLS_SSL_LEGACY_ALLOW_RENEGOTIATION:"TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384"
+
+DTLS legacy allow renegotiation with MFL=2048, ECDHE-RSA-WITH-AES-256-CBC-SHA384
+depends_on:MBEDTLS_SSL_HAVE_AES:MBEDTLS_SSL_HAVE_CBC:MBEDTLS_MD_CAN_SHA384:MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH
+resize_buffers_renegotiate_mfl:MBEDTLS_SSL_MAX_FRAG_LEN_2048:MBEDTLS_SSL_LEGACY_ALLOW_RENEGOTIATION:"TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384"
+
+DTLS legacy allow renegotiation with MFL=4096, ECDHE-RSA-WITH-AES-256-CBC-SHA384
+depends_on:MBEDTLS_SSL_HAVE_AES:MBEDTLS_SSL_HAVE_CBC:MBEDTLS_MD_CAN_SHA384:MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH
+resize_buffers_renegotiate_mfl:MBEDTLS_SSL_MAX_FRAG_LEN_4096:MBEDTLS_SSL_LEGACY_ALLOW_RENEGOTIATION:"TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384"
+
+DTLS legacy break handshake renegotiation with MFL=512, ECDHE-RSA-WITH-AES-256-CBC-SHA384
+depends_on:MBEDTLS_SSL_HAVE_AES:MBEDTLS_SSL_HAVE_CBC:MBEDTLS_MD_CAN_SHA384:MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH
+resize_buffers_renegotiate_mfl:MBEDTLS_SSL_MAX_FRAG_LEN_512:MBEDTLS_SSL_LEGACY_BREAK_HANDSHAKE:"TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384"
+
+DTLS legacy break handshake renegotiation with MFL=1024, ECDHE-RSA-WITH-AES-256-CBC-SHA384
+depends_on:MBEDTLS_SSL_HAVE_AES:MBEDTLS_SSL_HAVE_CBC:MBEDTLS_MD_CAN_SHA384:MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH
+resize_buffers_renegotiate_mfl:MBEDTLS_SSL_MAX_FRAG_LEN_1024:MBEDTLS_SSL_LEGACY_BREAK_HANDSHAKE:"TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384"
+
+DTLS legacy break handshake renegotiation with MFL=2048, ECDHE-RSA-WITH-AES-256-CBC-SHA384
+depends_on:MBEDTLS_SSL_HAVE_AES:MBEDTLS_SSL_HAVE_CBC:MBEDTLS_MD_CAN_SHA384:MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH
+resize_buffers_renegotiate_mfl:MBEDTLS_SSL_MAX_FRAG_LEN_2048:MBEDTLS_SSL_LEGACY_BREAK_HANDSHAKE:"TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384"
+
+DTLS legacy break handshake renegotiation with MFL=4096, ECDHE-RSA-WITH-AES-256-CBC-SHA384
+depends_on:MBEDTLS_SSL_HAVE_AES:MBEDTLS_SSL_HAVE_CBC:MBEDTLS_MD_CAN_SHA384:MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH
+resize_buffers_renegotiate_mfl:MBEDTLS_SSL_MAX_FRAG_LEN_4096:MBEDTLS_SSL_LEGACY_BREAK_HANDSHAKE:"TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384"
+
 SSL DTLS replay: initial state, seqnum 0
 ssl_dtls_replay:"":"000000000000":0
 
@@ -2840,6 +2904,21 @@
 depends_on:PSA_WANT_ALG_SHA_256
 ssl_tls13_derive_secret:PSA_ALG_SHA_256:"e2d32d4ed66dd37897a0e80c84107503ce58bf8aad4cb55a5002d77ecb890ece":tls13_label_res_master:"c3c122e0bd907a4a3ff6112d8fd53dbf89c773d9552e8b6b9d56d361b3a97bf6":32:MBEDTLS_SSL_TLS1_3_CONTEXT_HASHED:"5e95bdf1f89005ea2e9aa0ba85e728e3c19c5fe0c699e3f5bee59faebd0b5406"
 
+SSL TLS 1.3 Exporter
+# Based on the "exp master" key from RFC 8448, expected result calculated with a HMAC-SHA256 calculator.
+depends_on:PSA_WANT_ALG_SHA_256
+ssl_tls13_exporter:PSA_ALG_SHA_256:"3fd93d4ffddc98e64b14dd107aedf8ee4add23f4510f58a4592d0b201bee56b4":"test":"context value":32:"83d0fac39f87c1b4fbcd261369f31149c535391a9199bd4c5daf89fe259c2e94"
+
+SSL TLS 1.3 Exporter, 0-byte label and context
+# Expected output taken from OpenSSL.
+depends_on:PSA_WANT_ALG_SHA_384
+ssl_tls13_exporter:PSA_ALG_SHA_384:"9f355772f34017927ecc81d16e653c7408f945e7f62dc632d3f59e6310ef49401e62a2e3be886e3f930d4bf6300ce30a":"":"":20:"18268580D7C6769194794A84B7A3EE35317DB88A"
+
+SSL TLS 1.3 Exporter, 249-byte label and 0-byte context
+# Expected output taken from OpenSSL.
+depends_on:PSA_WANT_ALG_SHA_384
+ssl_tls13_exporter:PSA_ALG_SHA_384:"c453aeae318ebae00617c430a0066cf586593a4b0150219107420798933cf9e6e4434337cccc2cae5429dc4f77401e39":"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678":"":20:"259531766AAA10FBAB6BF2D11D23264B321743D9"
+
 SSL TLS 1.3 Key schedule: Early secrets derivation helper
 # Vector from RFC 8448
 depends_on:PSA_WANT_ALG_SHA_256
@@ -3106,10 +3185,10 @@
 depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_SESSION_TICKETS:MBEDTLS_SSL_SRV_C
 ssl_serialize_session_load_buf_size:0:"":MBEDTLS_SSL_IS_SERVER:MBEDTLS_SSL_VERSION_TLS1_3
 
-Test configuration of groups for DHE through mbedtls_ssl_conf_curves()
+Test configuration of EC groups through mbedtls_ssl_conf_curves()
 conf_curve:
 
-Test configuration of groups for DHE through mbedtls_ssl_conf_groups()
+Test configuration of EC groups through mbedtls_ssl_conf_groups()
 conf_group:
 
 Version config: valid client TLS 1.2 only
@@ -3381,3 +3460,67 @@
 
 TLS 1.3 srv, max early data size, HRR, 98, wsz=49
 tls13_srv_max_early_data_size:TEST_EARLY_DATA_HRR:97:0
+
+TLS 1.2 Keying Material Exporter: Consistent results, no context
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_CAN_HANDLE_RSA_TEST_KEY
+ssl_tls_exporter_consistent_result:MBEDTLS_SSL_VERSION_TLS1_2:24:0
+
+TLS 1.2 Keying Material Exporter: Consistent results, with context
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_CAN_HANDLE_RSA_TEST_KEY
+ssl_tls_exporter_consistent_result:MBEDTLS_SSL_VERSION_TLS1_2:24:1
+
+TLS 1.2 Keying Material Exporter: Consistent results, large keys
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_CAN_HANDLE_RSA_TEST_KEY
+ssl_tls_exporter_consistent_result:MBEDTLS_SSL_VERSION_TLS1_2:255 * 32:0
+
+TLS 1.2 Keying Material Exporter: Uses label
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_CAN_HANDLE_RSA_TEST_KEY
+ssl_tls_exporter_uses_label:MBEDTLS_SSL_VERSION_TLS1_2
+
+TLS 1.2 Keying Material Exporter: Uses context
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_CAN_HANDLE_RSA_TEST_KEY
+ssl_tls_exporter_uses_context:MBEDTLS_SSL_VERSION_TLS1_2
+
+TLS 1.2 Keying Material Exporter: Context too long
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_CAN_HANDLE_RSA_TEST_KEY
+ssl_tls_exporter_rejects_bad_parameters:MBEDTLS_SSL_VERSION_TLS1_2:24:251:UINT16_MAX + 1
+
+TLS 1.2 Keying Material Exporter: Handshake not done
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_CAN_HANDLE_RSA_TEST_KEY
+ssl_tls_exporter_too_early:MBEDTLS_SSL_VERSION_TLS1_2:1:MBEDTLS_SSL_SERVER_CERTIFICATE
+
+TLS 1.3 Keying Material Exporter: Consistent results, no context
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_TEST_AT_LEAST_ONE_TLS1_3_CIPHERSUITE:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:PSA_WANT_ALG_RSA_PKCS1V15_SIGN:MBEDTLS_X509_RSASSA_PSS_SUPPORT
+ssl_tls_exporter_consistent_result:MBEDTLS_SSL_VERSION_TLS1_3:24:0
+
+TLS 1.3 Keying Material Exporter: Consistent results, with context
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_TEST_AT_LEAST_ONE_TLS1_3_CIPHERSUITE:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:PSA_WANT_ALG_RSA_PKCS1V15_SIGN:MBEDTLS_X509_RSASSA_PSS_SUPPORT
+ssl_tls_exporter_consistent_result:MBEDTLS_SSL_VERSION_TLS1_3:24:1
+
+TLS 1.3 Keying Material Exporter: Consistent results, large keys
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_TEST_AT_LEAST_ONE_TLS1_3_CIPHERSUITE:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:PSA_WANT_ALG_RSA_PKCS1V15_SIGN:MBEDTLS_X509_RSASSA_PSS_SUPPORT
+ssl_tls_exporter_consistent_result:MBEDTLS_SSL_VERSION_TLS1_3:255 * 32:0
+
+TLS 1.3 Keying Material Exporter: Uses label
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_TEST_AT_LEAST_ONE_TLS1_3_CIPHERSUITE:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:PSA_WANT_ALG_RSA_PKCS1V15_SIGN:MBEDTLS_X509_RSASSA_PSS_SUPPORT
+ssl_tls_exporter_uses_label:MBEDTLS_SSL_VERSION_TLS1_3
+
+TLS 1.3 Keying Material Exporter: Uses context
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_TEST_AT_LEAST_ONE_TLS1_3_CIPHERSUITE:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:PSA_WANT_ALG_RSA_PKCS1V15_SIGN:MBEDTLS_X509_RSASSA_PSS_SUPPORT
+ssl_tls_exporter_uses_context:MBEDTLS_SSL_VERSION_TLS1_3
+
+TLS 1.3 Keying Material Exporter: Uses length
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_TEST_AT_LEAST_ONE_TLS1_3_CIPHERSUITE:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:PSA_WANT_ALG_RSA_PKCS1V15_SIGN:MBEDTLS_X509_RSASSA_PSS_SUPPORT
+ssl_tls13_exporter_uses_length
+
+TLS 1.3 Keying Material Exporter: Exported key too long
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_TEST_AT_LEAST_ONE_TLS1_3_CIPHERSUITE:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:PSA_WANT_ALG_RSA_PKCS1V15_SIGN:MBEDTLS_X509_RSASSA_PSS_SUPPORT
+ssl_tls_exporter_rejects_bad_parameters:MBEDTLS_SSL_VERSION_TLS1_3:255 * 32 + 1:20:20
+
+TLS 1.3 Keying Material Exporter: Label too long
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_TEST_AT_LEAST_ONE_TLS1_3_CIPHERSUITE:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:PSA_WANT_ALG_RSA_PKCS1V15_SIGN:MBEDTLS_X509_RSASSA_PSS_SUPPORT
+ssl_tls_exporter_rejects_bad_parameters:MBEDTLS_SSL_VERSION_TLS1_3:24:250:10
+
+TLS 1.3 Keying Material Exporter: Handshake not done
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_TEST_AT_LEAST_ONE_TLS1_3_CIPHERSUITE:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:PSA_WANT_ALG_RSA_PKCS1V15_SIGN:MBEDTLS_X509_RSASSA_PSS_SUPPORT
+ssl_tls_exporter_too_early:MBEDTLS_SSL_VERSION_TLS1_3:1:MBEDTLS_SSL_SERVER_CERTIFICATE
diff --git a/tests/suites/test_suite_ssl.function b/tests/suites/test_suite_ssl.function
index a16ac64..8fa5120 100644
--- a/tests/suites/test_suite_ssl.function
+++ b/tests/suites/test_suite_ssl.function
@@ -63,6 +63,315 @@
 }
 #endif
 
+typedef enum {
+    RECOMBINE_NOMINAL,          /* param: ignored */
+    RECOMBINE_SPLIT_FIRST,      /* param: offset of split (<=0 means from end) */
+    RECOMBINE_TRUNCATE_FIRST,   /* param: offset of truncation (<=0 means from end) */
+    RECOMBINE_INSERT_EMPTY,     /* param: offset (<0 means from end) */
+    RECOMBINE_INSERT_RECORD,    /* param: record type */
+    RECOMBINE_COALESCE,         /* param: number of records (INT_MAX=all) */
+    RECOMBINE_COALESCE_SPLIT_ONCE, /* param: offset of split (<=0 means from end) */
+    RECOMBINE_COALESCE_SPLIT_BOTH_ENDS, /* param: offset, must be >0 */
+} recombine_records_instruction_t;
+
+/* Keep this in sync with the recombine_server_first_flight()
+ * See comment there. */
+#if defined(MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED) && \
+    defined(MBEDTLS_MD_CAN_SHA256) && \
+    defined(MBEDTLS_ECP_HAVE_SECP256R1) && \
+    defined(MBEDTLS_ECP_HAVE_SECP384R1) && \
+    defined(MBEDTLS_PK_CAN_ECDSA_SIGN) && \
+    defined(MBEDTLS_PK_CAN_ECDSA_VERIFY)
+
+/* Split the first record into two pieces of lengths offset and
+ * record_length-offset. If offset is zero or negative, count from the end of
+ * the record. */
+static int recombine_split_first_record(mbedtls_test_ssl_buffer *buf,
+                                        int offset)
+{
+    const size_t header_length = 5;
+    TEST_LE_U(header_length, buf->content_length);
+    size_t record_length = MBEDTLS_GET_UINT16_BE(buf->buffer, header_length - 2);
+
+    if (offset > 0) {
+        TEST_LE_S(offset, record_length);
+    } else {
+        TEST_LE_S(-offset, record_length);
+        offset = record_length + offset;
+    }
+
+    /* Check that we have room to insert a record header */
+    TEST_LE_U(buf->content_length + header_length, buf->capacity);
+
+    /* Make room for a record header */
+    size_t new_record_start = header_length + offset;
+    size_t new_content_start = new_record_start + header_length;
+    memmove(buf->buffer + new_content_start,
+            buf->buffer + new_record_start,
+            buf->content_length - new_record_start);
+    buf->content_length += header_length;
+
+    /* Construct a header for the new record based on the existing one */
+    memcpy(buf->buffer + new_record_start, buf->buffer, header_length);
+    MBEDTLS_PUT_UINT16_BE(record_length - offset,
+                          buf->buffer, new_content_start - 2);
+
+    /* Adjust the length of the first record */
+    MBEDTLS_PUT_UINT16_BE(offset, buf->buffer, header_length - 2);
+
+    return 0;
+
+exit:
+    return -1;
+}
+
+/* Truncate the first record, keeping only the first offset bytes.
+ * If offset is zero or negative, count from the end of the record.
+ * Remove the subsequent records.
+ */
+static int recombine_truncate_first_record(mbedtls_test_ssl_buffer *buf,
+                                           int offset)
+{
+    const size_t header_length = 5;
+    TEST_LE_U(header_length, buf->content_length);
+    size_t record_length = MBEDTLS_GET_UINT16_BE(buf->buffer, header_length - 2);
+
+    if (offset > 0) {
+        TEST_LE_S(offset, record_length);
+    } else {
+        TEST_LE_S(-offset, record_length);
+        offset = record_length + offset;
+    }
+
+    /* Adjust the length of the first record */
+    MBEDTLS_PUT_UINT16_BE(offset, buf->buffer, header_length - 2);
+
+    /* Wipe the rest */
+    size_t truncated_end = header_length + offset;
+    memset(buf->buffer + truncated_end, '!',
+           buf->content_length - truncated_end);
+    buf->content_length = truncated_end;
+
+    return 0;
+
+exit:
+    return -1;
+}
+
+/* Insert a (dummy) record at the given offset. If offset is negative,
+ * count from the end of the first record. */
+static int recombine_insert_record(mbedtls_test_ssl_buffer *buf,
+                                   int offset,
+                                   uint8_t inserted_record_type)
+{
+    const size_t header_length = 5;
+    TEST_LE_U(header_length, buf->content_length);
+    size_t record_length = MBEDTLS_GET_UINT16_BE(buf->buffer, header_length - 2);
+
+    if (offset >= 0) {
+        TEST_LE_S(offset, record_length);
+    } else {
+        TEST_LE_S(-offset, record_length);
+        offset = record_length + offset;
+    }
+
+    uint8_t inserted_content[42] = { 0 };
+    size_t inserted_content_length = 0;
+    switch (inserted_record_type) {
+        case MBEDTLS_SSL_MSG_ALERT:
+            inserted_content[0] = MBEDTLS_SSL_ALERT_LEVEL_WARNING;
+            inserted_content[1] = MBEDTLS_SSL_ALERT_MSG_NO_RENEGOTIATION;
+            inserted_content_length = 2;
+            break;
+        case MBEDTLS_SSL_MSG_CHANGE_CIPHER_SPEC:
+            inserted_content[0] = 0x01;
+            inserted_content_length = 1;
+            break;
+        case MBEDTLS_SSL_MSG_APPLICATION_DATA:
+            inserted_content_length = sizeof(inserted_content);
+            break;
+        default:
+            /* Leave the content empty */
+            break;
+    }
+
+    /* Check that we have room to insert two record headers plus the new
+     * content. */
+    TEST_LE_U(buf->content_length + 2 * header_length + inserted_content_length,
+              buf->capacity);
+
+    /* Make room for the inserted record and a record header for the fragment */
+    size_t inserted_record_start = header_length + offset;
+    size_t inserted_content_start = inserted_record_start + header_length;
+    size_t tail_record_start = inserted_content_start + inserted_content_length;
+    size_t tail_content_start = tail_record_start + header_length;
+    memmove(buf->buffer + tail_content_start,
+            buf->buffer + inserted_record_start,
+            buf->content_length - inserted_record_start);
+    buf->content_length += 2 * header_length;
+
+    /* Construct the inserted record based on the existing one */
+    memcpy(buf->buffer + inserted_record_start, buf->buffer, header_length);
+    buf->buffer[inserted_record_start] = inserted_record_type;
+    MBEDTLS_PUT_UINT16_BE(inserted_content_length,
+                          buf->buffer, inserted_content_start - 2);
+    memcpy(buf->buffer + inserted_content_start,
+           inserted_content, inserted_content_length);
+
+    /* Construct header for the last fragment based on the existing one */
+    memcpy(buf->buffer + tail_record_start, buf->buffer, header_length);
+    MBEDTLS_PUT_UINT16_BE(record_length - offset,
+                          buf->buffer, tail_content_start - 2);
+
+    /* Adjust the length of the first record */
+    MBEDTLS_PUT_UINT16_BE(offset, buf->buffer, header_length - 2);
+
+    return 0;
+
+exit:
+    return -1;
+}
+
+/* Coalesce TLS handshake records.
+ * DTLS is not supported.
+ * Encrypted or authenticated handshake records are not supported.
+ * Assume the buffer content is a valid sequence of records.
+ *
+ * Coalesce only the first max records, or all the records if there are
+ * fewer than max.
+ * Return the number of coalesced records, or -1 on error.
+ */
+static int recombine_coalesce_handshake_records(mbedtls_test_ssl_buffer *buf,
+                                                int max)
+{
+    const size_t header_length = 5;
+    TEST_LE_U(header_length, buf->content_length);
+    if (buf->buffer[0] != MBEDTLS_SSL_MSG_HANDSHAKE) {
+        return 0;
+    }
+
+    size_t record_length = MBEDTLS_GET_UINT16_BE(buf->buffer, header_length - 2);
+    TEST_LE_U(header_length + record_length, buf->content_length);
+
+    int count;
+    for (count = 1; count < max; count++) {
+        size_t next_start = header_length + record_length;
+        if (next_start >= buf->content_length) {
+            /* We've already reached the last record. */
+            break;
+        }
+
+        TEST_LE_U(next_start + header_length, buf->content_length);
+        if (buf->buffer[next_start] != MBEDTLS_SSL_MSG_HANDSHAKE) {
+            /* There's another record, but it isn't a handshake record. */
+            break;
+        }
+        size_t next_length =
+            MBEDTLS_GET_UINT16_BE(buf->buffer, next_start + header_length - 2);
+        TEST_LE_U(next_start + header_length + next_length, buf->content_length);
+
+        /* Erase the next record header */
+        memmove(buf->buffer + next_start,
+                buf->buffer + next_start + header_length,
+                buf->content_length - next_start);
+        buf->content_length -= header_length;
+        /* Update the first record length */
+        record_length += next_length;
+        TEST_LE_U(record_length, 0xffff);
+        MBEDTLS_PUT_UINT16_BE(record_length, buf->buffer, header_length - 2);
+    }
+
+    return count;
+
+exit:
+    return -1;
+}
+
+static int recombine_records(mbedtls_test_ssl_endpoint *server,
+                             recombine_records_instruction_t instruction,
+                             int param)
+{
+    mbedtls_test_ssl_buffer *buf = server->socket.output;
+    int ret;
+
+    /* buf is a circular buffer. For simplicity, this code assumes that
+     * the data is located at the beginning. This should be ok since
+     * this function is only meant to be used on the first flight
+     * emitted by a server. */
+    TEST_EQUAL(buf->start, 0);
+
+    switch (instruction) {
+        case RECOMBINE_NOMINAL:
+            break;
+
+        case RECOMBINE_SPLIT_FIRST:
+            ret = recombine_split_first_record(buf, param);
+            TEST_LE_S(0, ret);
+            break;
+
+        case RECOMBINE_TRUNCATE_FIRST:
+            ret = recombine_truncate_first_record(buf, param);
+            TEST_LE_S(0, ret);
+            break;
+
+        case RECOMBINE_INSERT_EMPTY:
+            /* Insert an empty handshake record. */
+            ret = recombine_insert_record(buf, param, MBEDTLS_SSL_MSG_HANDSHAKE);
+            TEST_LE_S(0, ret);
+            break;
+
+        case RECOMBINE_INSERT_RECORD:
+            /* Insert an extra record at a position where splitting
+             * would be ok. */
+            ret = recombine_insert_record(buf, 5, param);
+            TEST_LE_S(0, ret);
+            break;
+
+        case RECOMBINE_COALESCE:
+            ret = recombine_coalesce_handshake_records(buf, param);
+            /* If param != INT_MAX, enforce that there were that many
+             * records to coalesce. In particular, 1 < param < INT_MAX
+             * ensures that library will see some coalesced records. */
+            if (param == INT_MAX) {
+                TEST_LE_S(1, ret);
+            } else {
+                TEST_EQUAL(ret, param);
+            }
+            break;
+
+        case RECOMBINE_COALESCE_SPLIT_ONCE:
+            ret = recombine_coalesce_handshake_records(buf, INT_MAX);
+            /* Require at least two coalesced records, otherwise this
+             * doesn't lead to a meaningful test (use
+             * RECOMBINE_SPLIT_FIRST instead). */
+            TEST_LE_S(2, ret);
+            ret = recombine_split_first_record(buf, param);
+            TEST_LE_S(0, ret);
+            break;
+
+        case RECOMBINE_COALESCE_SPLIT_BOTH_ENDS:
+            ret = recombine_coalesce_handshake_records(buf, INT_MAX);
+            /* Accept a single record, which will be split at both ends */
+            TEST_LE_S(1, ret);
+            TEST_LE_S(1, param);
+            ret = recombine_split_first_record(buf, -param);
+            TEST_LE_S(0, ret);
+            ret = recombine_split_first_record(buf, param);
+            TEST_LE_S(0, ret);
+            break;
+
+        default:
+            TEST_FAIL("Instructions not understood");
+    }
+
+    return 1;
+
+exit:
+    return 0;
+}
+
+#endif /* MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED etc */
+
 /* END_HEADER */
 
 /* BEGIN_DEPENDENCIES
@@ -1655,6 +1964,37 @@
 }
 /* END_CASE */
 
+/* BEGIN_CASE depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_KEYING_MATERIAL_EXPORT */
+void ssl_tls13_exporter(int hash_alg,
+                        data_t *secret,
+                        char *label,
+                        char *context_value,
+                        int desired_length,
+                        data_t *expected)
+{
+    unsigned char dst[100];
+
+    /* Check sanity of test parameters. */
+    TEST_ASSERT((size_t) desired_length <= sizeof(dst));
+    TEST_ASSERT((size_t) desired_length == expected->len);
+
+    PSA_INIT();
+
+    TEST_ASSERT(mbedtls_ssl_tls13_exporter(
+                    (psa_algorithm_t) hash_alg,
+                    secret->x, secret->len,
+                    (unsigned char *) label, strlen(label),
+                    (unsigned char *) context_value, strlen(context_value),
+                    dst, desired_length) == 0);
+
+    TEST_MEMORY_COMPARE(dst, desired_length,
+                        expected->x, desired_length);
+
+exit:
+    PSA_DONE();
+}
+/* END_CASE */
+
 /* BEGIN_CASE depends_on:MBEDTLS_SSL_PROTO_TLS1_3 */
 void ssl_tls13_derive_early_secrets(int hash_alg,
                                     data_t *secret,
@@ -2761,10 +3101,11 @@
 }
 /* END_CASE */
 
-/* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:!MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_PKCS1_V15:MBEDTLS_RSA_C:MBEDTLS_SSL_HAVE_AES:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_DEBUG_C:MBEDTLS_SSL_MAX_FRAGMENT_LENGTH:MBEDTLS_SSL_HAVE_CBC:MBEDTLS_MD_CAN_SHA256:MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED */
+/* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:!MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_PKCS1_V15:MBEDTLS_RSA_C:MBEDTLS_SSL_HAVE_AES:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_DEBUG_C:MBEDTLS_SSL_MAX_FRAGMENT_LENGTH:MBEDTLS_SSL_HAVE_CBC */
 void handshake_fragmentation(int mfl,
                              int expected_srv_hs_fragmentation,
-                             int expected_cli_hs_fragmentation)
+                             int expected_cli_hs_fragmentation,
+                             char *ciphersuite)
 {
     mbedtls_test_handshake_test_options options;
     mbedtls_test_ssl_log_pattern srv_pattern, cli_pattern;
@@ -2778,7 +3119,7 @@
     options.expected_negotiated_version = MBEDTLS_SSL_VERSION_TLS1_2;
     options.mfl = mfl;
     /* Set cipher to one using CBC so that record splitting can be tested */
-    options.cipher = "TLS-DHE-RSA-WITH-AES-256-CBC-SHA256";
+    options.cipher = ciphersuite;
     options.srv_auth_mode = MBEDTLS_SSL_VERIFY_REQUIRED;
     options.srv_log_obj = &srv_pattern;
     options.cli_log_obj = &cli_pattern;
@@ -2801,6 +3142,165 @@
 }
 /* END_CASE */
 
+/* This test case doesn't actually depend on certificates,
+ * but our helper code for mbedtls_test_ssl_endpoint does.
+ * Also, it needs specific hashes, algs and curves for the
+ * hardcoded test certificates. In principle both RSA and ECDSA
+ * can be used, but we hardcode ECDSA in order to avoid having
+ * to express dependencies like "RSA or ECDSA with those curves". */
+/* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:MBEDTLS_MD_CAN_SHA256:MBEDTLS_ECP_HAVE_SECP256R1:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_PK_CAN_ECDSA_SIGN:MBEDTLS_PK_CAN_ECDSA_VERIFY */
+void recombine_server_first_flight(int version,
+                                   int instruction, int param,
+                                   char *client_log, char *server_log,
+                                   int goal_state, int expected_ret)
+{
+    /* Make sure we have a buffer that's large enough for the longest
+     * data that the library might ever send, plus a bit extra so that
+     * we can inject more content. The library won't ever send more than
+     * 2^14 bytes of handshake messages, so we round that up. In practice
+     * we could surely get away with a much smaller buffer. The main
+     * variable part is the server certificate. */
+    enum { BUFFSIZE = 17000 };
+    mbedtls_test_ssl_endpoint client;
+    memset(&client, 0, sizeof(client));
+    mbedtls_test_ssl_endpoint server;
+    memset(&server, 0, sizeof(server));
+    mbedtls_test_handshake_test_options client_options;
+    mbedtls_test_init_handshake_options(&client_options);
+    mbedtls_test_handshake_test_options server_options;
+    mbedtls_test_init_handshake_options(&server_options);
+#if defined(MBEDTLS_DEBUG_C)
+    mbedtls_test_ssl_log_pattern cli_pattern = { .pattern = client_log };
+    mbedtls_test_ssl_log_pattern srv_pattern = { .pattern = server_log };
+#else
+    (void) client_log;
+    (void) server_log;
+#endif
+    int ret = 0;
+
+    MD_OR_USE_PSA_INIT();
+#if defined(MBEDTLS_DEBUG_C)
+    mbedtls_debug_set_threshold(3);
+#endif
+
+    // Does't really matter but we want to know to declare dependencies.
+    client_options.pk_alg = MBEDTLS_PK_ECDSA;
+    server_options.pk_alg = MBEDTLS_PK_ECDSA;
+
+    client_options.client_min_version = version;
+    client_options.client_max_version = version;
+#if defined(MBEDTLS_DEBUG_C)
+    client_options.cli_log_obj = &cli_pattern;
+    client_options.cli_log_fun = mbedtls_test_ssl_log_analyzer;
+#endif
+    TEST_EQUAL(mbedtls_test_ssl_endpoint_init(&client, MBEDTLS_SSL_IS_CLIENT,
+                                              &client_options, NULL, NULL,
+                                              NULL), 0);
+
+    server_options.server_min_version = version;
+    server_options.server_max_version = version;
+#if defined(MBEDTLS_DEBUG_C)
+    server_options.srv_log_obj = &srv_pattern;
+    server_options.srv_log_fun = mbedtls_test_ssl_log_analyzer;
+#endif
+    TEST_EQUAL(mbedtls_test_ssl_endpoint_init(&server, MBEDTLS_SSL_IS_SERVER,
+                                              &server_options, NULL, NULL,
+                                              NULL), 0);
+
+    TEST_EQUAL(mbedtls_test_mock_socket_connect(&client.socket,
+                                                &server.socket,
+                                                BUFFSIZE), 0);
+
+    /* Client: emit the first flight from the client */
+    while (ret == 0) {
+        mbedtls_test_set_step(client.ssl.state);
+        ret = mbedtls_ssl_handshake_step(&client.ssl);
+    }
+    TEST_EQUAL(ret, MBEDTLS_ERR_SSL_WANT_READ);
+    ret = 0;
+    TEST_EQUAL(client.ssl.state, MBEDTLS_SSL_SERVER_HELLO);
+
+    /* Server: parse the first flight from the client
+     * and emit the first flight from the server */
+    while (ret == 0) {
+        mbedtls_test_set_step(1000 + server.ssl.state);
+        ret = mbedtls_ssl_handshake_step(&server.ssl);
+    }
+    TEST_EQUAL(ret, MBEDTLS_ERR_SSL_WANT_READ);
+    ret = 0;
+    TEST_EQUAL(server.ssl.state, MBEDTLS_SSL_SERVER_HELLO_DONE + 1);
+
+    /* Recombine the first flight from the server */
+    TEST_ASSERT(recombine_records(&server, instruction, param));
+
+    /* Client: parse the first flight from the server
+     * and emit the second flight from the client */
+    while (ret == 0 && !mbedtls_ssl_is_handshake_over(&client.ssl)) {
+        mbedtls_test_set_step(client.ssl.state);
+        ret = mbedtls_ssl_handshake_step(&client.ssl);
+        if (client.ssl.state == goal_state && ret != 0) {
+            TEST_EQUAL(ret, expected_ret);
+            goto goal_reached;
+        }
+    }
+#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
+    /* A default TLS 1.3 handshake has only 1 flight from the server,
+     * while the default (non-resumption) 1.2 handshake has two. */
+    if (version >= MBEDTLS_SSL_VERSION_TLS1_3 &&
+        goal_state >= MBEDTLS_SSL_HANDSHAKE_OVER) {
+        TEST_EQUAL(ret, 0);
+    } else
+#endif
+    {
+        TEST_EQUAL(ret, MBEDTLS_ERR_SSL_WANT_READ);
+    }
+    ret = 0;
+
+    /* Server: parse the first flight from the client
+     * and emit the second flight from the server */
+    if (instruction == RECOMBINE_TRUNCATE_FIRST) {
+        /* Close without a notification. The case of closing with a
+         * notification is tested via RECOMBINE_INSERT_RECORD to insert
+         * an alert record (which we reject, making the client SSL
+         * context become invalid). */
+        mbedtls_test_mock_socket_close(&server.socket);
+        goto goal_reached;
+    }
+    while (ret == 0 && !mbedtls_ssl_is_handshake_over(&server.ssl)) {
+        mbedtls_test_set_step(1000 + server.ssl.state);
+        ret = mbedtls_ssl_handshake_step(&server.ssl);
+    }
+    TEST_EQUAL(ret, 0);
+
+    /* Client: parse the second flight from the server */
+    while (ret == 0 && !mbedtls_ssl_is_handshake_over(&client.ssl)) {
+        mbedtls_test_set_step(client.ssl.state);
+        ret = mbedtls_ssl_handshake_step(&client.ssl);
+    }
+    if (client.ssl.state == goal_state) {
+        TEST_EQUAL(ret, expected_ret);
+    } else {
+        TEST_EQUAL(ret, 0);
+    }
+
+goal_reached:
+#if defined(MBEDTLS_DEBUG_C)
+    TEST_ASSERT(cli_pattern.counter >= 1);
+    TEST_ASSERT(srv_pattern.counter >= 1);
+#endif
+
+exit:
+    mbedtls_test_ssl_endpoint_free(&client, NULL);
+    mbedtls_test_ssl_endpoint_free(&server, NULL);
+    mbedtls_test_free_handshake_options(&client_options);
+    mbedtls_test_free_handshake_options(&server_options);
+    MD_OR_USE_PSA_DONE();
+#if defined(MBEDTLS_DEBUG_C)
+    mbedtls_debug_set_threshold(0);
+#endif
+}
+/* END_CASE */
+
 /* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:!MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_PKCS1_V15:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_RSA_C:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_SSL_PROTO_DTLS:MBEDTLS_SSL_RENEGOTIATION:MBEDTLS_MD_CAN_SHA256:MBEDTLS_CAN_HANDLE_RSA_TEST_KEY */
 void renegotiation(int legacy_renegotiation)
 {
@@ -3059,7 +3559,7 @@
 }
 /* END_CASE */
 
-/* BEGIN_CASE depends_on:MBEDTLS_DEPRECATED_REMOVED */
+/* BEGIN_CASE */
 void conf_group()
 {
     uint16_t iana_tls_group_list[] = { MBEDTLS_SSL_IANA_TLS_GROUP_SECP192R1,
@@ -3071,8 +3571,9 @@
     mbedtls_ssl_config_init(&conf);
 
     mbedtls_ssl_conf_rng(&conf, mbedtls_test_random, NULL);
-    mbedtls_ssl_conf_max_tls_version(&conf, MBEDTLS_SSL_VERSION_TLS1_2);
-    mbedtls_ssl_conf_min_tls_version(&conf, MBEDTLS_SSL_VERSION_TLS1_2);
+    mbedtls_ssl_config_defaults(&conf, MBEDTLS_SSL_IS_CLIENT,
+                                MBEDTLS_SSL_TRANSPORT_STREAM,
+                                MBEDTLS_SSL_PRESET_DEFAULT);
 
     mbedtls_ssl_conf_groups(&conf, iana_tls_group_list);
 
@@ -5035,3 +5536,452 @@
     PSA_DONE();
 }
 /* END_CASE */
+
+/* BEGIN_CASE depends_on:MBEDTLS_DEBUG_C:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED */
+void inject_client_content_on_the_wire(int pk_alg,
+                                       int state, data_t *data,
+                                       char *log_pattern, int expected_ret)
+{
+    /* This function allows us to inject content at a specific state
+     * in the handshake, or when it's completed. The content is injected
+     * on the mock TCP socket, as if we were an active network attacker.
+     *
+     * This function is suitable to inject:
+     * - crafted records, at any point;
+     * - valid records that contain crafted handshake messages, but only
+     *   when the traffic is still unprotected (for TLS 1.2 that's most of the
+     *   handshake, for TLS 1.3 that's only the Hello messages);
+     * - handshake messages that are fragmented in a specific way,
+     *   under the same conditions as above.
+     */
+    enum { BUFFSIZE = 16384 };
+    mbedtls_test_ssl_endpoint server, client;
+    mbedtls_platform_zeroize(&server, sizeof(server));
+    mbedtls_platform_zeroize(&client, sizeof(client));
+    mbedtls_test_handshake_test_options options;
+    mbedtls_test_init_handshake_options(&options);
+    mbedtls_test_ssl_log_pattern srv_pattern;
+    memset(&srv_pattern, 0, sizeof(srv_pattern));
+    int ret = -1;
+
+    PSA_INIT();
+
+    srv_pattern.pattern = log_pattern;
+    options.srv_log_obj = &srv_pattern;
+    options.srv_log_fun = mbedtls_test_ssl_log_analyzer;
+    mbedtls_debug_set_threshold(3);
+
+    options.pk_alg = pk_alg;
+
+    ret = mbedtls_test_ssl_endpoint_init(&server, MBEDTLS_SSL_IS_SERVER,
+                                         &options, NULL, NULL, NULL);
+    TEST_EQUAL(ret,  0);
+
+    ret = mbedtls_test_ssl_endpoint_init(&client, MBEDTLS_SSL_IS_CLIENT,
+                                         &options, NULL, NULL, NULL);
+    TEST_EQUAL(ret,  0);
+
+    ret = mbedtls_test_mock_socket_connect(&server.socket, &client.socket,
+                                           BUFFSIZE);
+    TEST_EQUAL(ret,  0);
+
+    /* Make the server move to the required state */
+    ret = mbedtls_test_move_handshake_to_state(&client.ssl, &server.ssl, state);
+    TEST_EQUAL(ret, 0);
+
+    /* Send the crafted message */
+    ret = mbedtls_test_mock_tcp_send_b(&client.socket, data->x, data->len);
+    TEST_EQUAL(ret, (int) data->len);
+
+    /* Have the server process it.
+     * Need the loop because a server that support 1.3 and 1.2
+     * will process a 1.2 ClientHello in two steps.
+     */
+    do {
+        ret = mbedtls_ssl_handshake_step(&server.ssl);
+    } while (ret == 0 && server.ssl.state == state);
+    TEST_EQUAL(ret,  expected_ret);
+    TEST_ASSERT(srv_pattern.counter >= 1);
+
+exit:
+    mbedtls_test_free_handshake_options(&options);
+    mbedtls_test_ssl_endpoint_free(&server, NULL);
+    mbedtls_test_ssl_endpoint_free(&client, NULL);
+    mbedtls_debug_set_threshold(0);
+    PSA_DONE();
+}
+/* END_CASE */
+
+/* BEGIN_CASE depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_DEBUG_C:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_MD_CAN_SHA256:MBEDTLS_ECP_HAVE_SECP256R1:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_PK_CAN_ECDSA_SIGN:MBEDTLS_PK_CAN_ECDSA_VERIFY */
+void send_large_fragmented_hello(int hs_len_int, int first_frag_content_len_int,
+                                 char *log_pattern, int expected_ret)
+{
+    /* This function sends a long message (claiming to be a ClientHello)
+     * fragmented in 1-byte fragments (except the initial fragment).
+     * The purpose is to test how the stack reacts when receiving:
+     * - a message larger than our buffer;
+     * - a message smaller than our buffer, but where the intermediate size of
+     *   holding all the fragments (including overhead) is larger than our
+     *   buffer.
+     */
+    enum { BUFFSIZE = 16384 };
+    mbedtls_test_ssl_endpoint server, client;
+    mbedtls_platform_zeroize(&server, sizeof(server));
+    mbedtls_platform_zeroize(&client, sizeof(client));
+
+    mbedtls_test_handshake_test_options options;
+    mbedtls_test_init_handshake_options(&options);
+
+    mbedtls_test_ssl_log_pattern srv_pattern;
+    memset(&srv_pattern, 0, sizeof(srv_pattern));
+
+    unsigned char *first_frag = NULL;
+    int ret = -1;
+
+    size_t hs_len = (size_t) hs_len_int;
+    size_t first_frag_content_len = (size_t) first_frag_content_len_int;
+
+    PSA_INIT();
+
+    srv_pattern.pattern = log_pattern;
+    options.srv_log_obj = &srv_pattern;
+    options.srv_log_fun = mbedtls_test_ssl_log_analyzer;
+    mbedtls_debug_set_threshold(1);
+
+    // Does't really matter but we want to know to declare dependencies.
+    options.pk_alg = MBEDTLS_PK_ECDSA;
+
+    ret = mbedtls_test_ssl_endpoint_init(&server, MBEDTLS_SSL_IS_SERVER,
+                                         &options, NULL, NULL, NULL);
+    TEST_EQUAL(ret,  0);
+
+    ret = mbedtls_test_ssl_endpoint_init(&client, MBEDTLS_SSL_IS_CLIENT,
+                                         &options, NULL, NULL, NULL);
+    TEST_EQUAL(ret,  0);
+
+    ret = mbedtls_test_mock_socket_connect(&server.socket, &client.socket,
+                                           BUFFSIZE);
+    TEST_EQUAL(ret,  0);
+
+    /* Make the server move past the initial dummy state */
+    ret = mbedtls_test_move_handshake_to_state(&client.ssl, &server.ssl,
+                                               MBEDTLS_SSL_CLIENT_HELLO);
+    TEST_EQUAL(ret, 0);
+
+    /* Prepare initial fragment */
+    const size_t first_len = 5 // record header, see below
+                             + 4 // handshake header, see balow
+                             + first_frag_content_len;
+    TEST_CALLOC(first_frag, first_len);
+    unsigned char *p = first_frag;
+    // record header
+    // record type: handshake
+    *p++ = 0x16,
+    // record version (actually common to TLS 1.2 and TLS 1.3)
+    *p++ = 0x03,
+    *p++ = 0x03,
+    // record length: two bytes
+    *p++ = (unsigned char) (((4 + first_frag_content_len) >> 8) & 0xff);
+    *p++ = (unsigned char) (((4 + first_frag_content_len) >> 0) & 0xff);
+    // handshake header
+    // handshake type: ClientHello
+    *p++ = 0x01,
+    // handshake length: three bytes
+    *p++ = (unsigned char) ((hs_len >> 16) & 0xff);
+    *p++ = (unsigned char) ((hs_len >>  8) & 0xff);
+    *p++ = (unsigned char) ((hs_len >>  0) & 0xff);
+    // handshake content: dummy value
+    memset(p, 0x2a, first_frag_content_len);
+
+    /* Send initial fragment and have the server process it. */
+    ret = mbedtls_test_mock_tcp_send_b(&client.socket, first_frag, first_len);
+    TEST_ASSERT(ret >= 0 && (size_t) ret == first_len);
+
+    ret = mbedtls_ssl_handshake_step(&server.ssl);
+    TEST_EQUAL(ret, MBEDTLS_ERR_SSL_WANT_READ);
+
+    /* Dummy 1-byte fragment to repeatedly send next */
+    const unsigned char next[] = {
+        0x16, 0x03, 0x03, 0x00, 0x01, // record header (see above)
+        0x2a, // Dummy handshake message content
+    };
+    for (size_t left = hs_len - first_frag_content_len; left != 0; left--) {
+        ret = mbedtls_test_mock_tcp_send_b(&client.socket, next, sizeof(next));
+        TEST_ASSERT(ret >= 0 && (size_t) ret == sizeof(next));
+
+        ret = mbedtls_ssl_handshake_step(&server.ssl);
+        if (ret != MBEDTLS_ERR_SSL_WANT_READ) {
+            break;
+        }
+    }
+    TEST_EQUAL(ret, expected_ret);
+    TEST_EQUAL(srv_pattern.counter, 1);
+
+exit:
+    mbedtls_test_free_handshake_options(&options);
+    mbedtls_test_ssl_endpoint_free(&server, NULL);
+    mbedtls_test_ssl_endpoint_free(&client, NULL);
+    mbedtls_debug_set_threshold(0);
+    mbedtls_free(first_frag);
+    PSA_DONE();
+}
+/* END_CASE */
+
+/* BEGIN_CASE depends_on:MBEDTLS_SSL_KEYING_MATERIAL_EXPORT:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:PSA_WANT_ECC_SECP_R1_384:PSA_WANT_ALG_SHA_256 */
+void ssl_tls_exporter_consistent_result(int proto, int exported_key_length, int use_context)
+{
+    /* Test that the client and server generate the same key. */
+
+    int ret = -1;
+    uint8_t *key_buffer_server = NULL;
+    uint8_t *key_buffer_client = NULL;
+    mbedtls_test_ssl_endpoint client_ep, server_ep;
+    mbedtls_test_handshake_test_options options;
+
+    MD_OR_USE_PSA_INIT();
+
+    ret = mbedtls_test_ssl_do_handshake_with_endpoints(&server_ep, &client_ep, &options, proto);
+    TEST_ASSERT(ret == 0);
+
+    TEST_ASSERT(exported_key_length > 0);
+    TEST_CALLOC(key_buffer_server, exported_key_length);
+    TEST_CALLOC(key_buffer_client, exported_key_length);
+
+    memset(key_buffer_server, 0, exported_key_length);
+    memset(key_buffer_client, 0, exported_key_length);
+
+    char label[] = "test-label";
+    unsigned char context[128] = { 0 };
+    ret = mbedtls_ssl_export_keying_material(&server_ep.ssl,
+                                             key_buffer_server, (size_t) exported_key_length,
+                                             label, sizeof(label),
+                                             context, sizeof(context), use_context);
+    TEST_ASSERT(ret == 0);
+    ret = mbedtls_ssl_export_keying_material(&client_ep.ssl,
+                                             key_buffer_client, (size_t) exported_key_length,
+                                             label, sizeof(label),
+                                             context, sizeof(context), use_context);
+    TEST_ASSERT(ret == 0);
+    TEST_ASSERT(memcmp(key_buffer_server, key_buffer_client, (size_t) exported_key_length) == 0);
+
+exit:
+    mbedtls_test_ssl_endpoint_free(&server_ep, NULL);
+    mbedtls_test_ssl_endpoint_free(&client_ep, NULL);
+    mbedtls_test_free_handshake_options(&options);
+    mbedtls_free(key_buffer_server);
+    mbedtls_free(key_buffer_client);
+    MD_OR_USE_PSA_DONE();
+}
+/* END_CASE */
+
+/* BEGIN_CASE depends_on:MBEDTLS_SSL_KEYING_MATERIAL_EXPORT:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:PSA_WANT_ECC_SECP_R1_384:PSA_WANT_ALG_SHA_256 */
+void ssl_tls_exporter_uses_label(int proto)
+{
+    /* Test that the client and server export different keys when using different labels. */
+
+    int ret = -1;
+    mbedtls_test_ssl_endpoint client_ep, server_ep;
+    mbedtls_test_handshake_test_options options;
+
+    MD_OR_USE_PSA_INIT();
+
+    ret = mbedtls_test_ssl_do_handshake_with_endpoints(&server_ep, &client_ep, &options, proto);
+    TEST_ASSERT(ret == 0);
+
+    char label_server[] = "test-label-server";
+    char label_client[] = "test-label-client";
+    uint8_t key_buffer_server[24] = { 0 };
+    uint8_t key_buffer_client[24] = { 0 };
+    unsigned char context[128] = { 0 };
+    ret = mbedtls_ssl_export_keying_material(&server_ep.ssl,
+                                             key_buffer_server, sizeof(key_buffer_server),
+                                             label_server, sizeof(label_server),
+                                             context, sizeof(context), 1);
+    TEST_ASSERT(ret == 0);
+    ret = mbedtls_ssl_export_keying_material(&client_ep.ssl,
+                                             key_buffer_client, sizeof(key_buffer_client),
+                                             label_client, sizeof(label_client),
+                                             context, sizeof(context), 1);
+    TEST_ASSERT(ret == 0);
+    TEST_ASSERT(memcmp(key_buffer_server, key_buffer_client, sizeof(key_buffer_server)) != 0);
+
+exit:
+    mbedtls_test_ssl_endpoint_free(&server_ep, NULL);
+    mbedtls_test_ssl_endpoint_free(&client_ep, NULL);
+    mbedtls_test_free_handshake_options(&options);
+    MD_OR_USE_PSA_DONE();
+}
+/* END_CASE */
+
+/* BEGIN_CASE depends_on:MBEDTLS_SSL_KEYING_MATERIAL_EXPORT:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:PSA_WANT_ECC_SECP_R1_384:PSA_WANT_ALG_SHA_256 */
+void ssl_tls_exporter_uses_context(int proto)
+{
+    /* Test that the client and server export different keys when using different contexts. */
+
+    int ret = -1;
+    mbedtls_test_ssl_endpoint client_ep, server_ep;
+    mbedtls_test_handshake_test_options options;
+
+    MD_OR_USE_PSA_INIT();
+
+    ret = mbedtls_test_ssl_do_handshake_with_endpoints(&server_ep, &client_ep, &options, proto);
+    TEST_ASSERT(ret == 0);
+
+    char label[] = "test-label";
+    uint8_t key_buffer_server[24] = { 0 };
+    uint8_t key_buffer_client[24] = { 0 };
+    unsigned char context_server[128] = { 0 };
+    unsigned char context_client[128] = { 23 };
+    ret = mbedtls_ssl_export_keying_material(&server_ep.ssl,
+                                             key_buffer_server, sizeof(key_buffer_server),
+                                             label, sizeof(label),
+                                             context_server, sizeof(context_server), 1);
+    TEST_ASSERT(ret == 0);
+    ret = mbedtls_ssl_export_keying_material(&client_ep.ssl,
+                                             key_buffer_client, sizeof(key_buffer_client),
+                                             label, sizeof(label),
+                                             context_client, sizeof(context_client), 1);
+    TEST_ASSERT(ret == 0);
+    TEST_ASSERT(memcmp(key_buffer_server, key_buffer_client, sizeof(key_buffer_server)) != 0);
+
+exit:
+    mbedtls_test_ssl_endpoint_free(&server_ep, NULL);
+    mbedtls_test_ssl_endpoint_free(&client_ep, NULL);
+    mbedtls_test_free_handshake_options(&options);
+    MD_OR_USE_PSA_DONE();
+}
+/* END_CASE */
+
+/* BEGIN_CASE depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_TEST_AT_LEAST_ONE_TLS1_3_CIPHERSUITE:MBEDTLS_SSL_KEYING_MATERIAL_EXPORT:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:PSA_WANT_ECC_SECP_R1_384:PSA_WANT_ALG_SHA_256 */
+void ssl_tls13_exporter_uses_length(void)
+{
+    /* In TLS 1.3, when two keys are exported with the same parameters except one is shorter,
+     * the shorter key should NOT be a prefix of the longer one. */
+
+    int ret = -1;
+    mbedtls_test_ssl_endpoint client_ep, server_ep;
+    mbedtls_test_handshake_test_options options;
+
+    MD_OR_USE_PSA_INIT();
+
+    ret = mbedtls_test_ssl_do_handshake_with_endpoints(&server_ep,
+                                                       &client_ep,
+                                                       &options,
+                                                       MBEDTLS_SSL_VERSION_TLS1_3);
+    TEST_ASSERT(ret == 0);
+
+    char label[] = "test-label";
+    uint8_t key_buffer_server[16] = { 0 };
+    uint8_t key_buffer_client[24] = { 0 };
+    unsigned char context[128] = { 0 };
+    ret = mbedtls_ssl_export_keying_material(&server_ep.ssl,
+                                             key_buffer_server, sizeof(key_buffer_server),
+                                             label, sizeof(label),
+                                             context, sizeof(context), 1);
+    TEST_ASSERT(ret == 0);
+    ret = mbedtls_ssl_export_keying_material(&client_ep.ssl,
+                                             key_buffer_client, sizeof(key_buffer_client),
+                                             label, sizeof(label),
+                                             context, sizeof(context), 1);
+    TEST_ASSERT(ret == 0);
+    TEST_ASSERT(memcmp(key_buffer_server, key_buffer_client, sizeof(key_buffer_server)) != 0);
+
+exit:
+    mbedtls_test_ssl_endpoint_free(&server_ep, NULL);
+    mbedtls_test_ssl_endpoint_free(&client_ep, NULL);
+    mbedtls_test_free_handshake_options(&options);
+    MD_OR_USE_PSA_DONE();
+}
+/* END_CASE */
+
+/* BEGIN_CASE depends_on:MBEDTLS_SSL_KEYING_MATERIAL_EXPORT:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:PSA_WANT_ECC_SECP_R1_384:PSA_WANT_ALG_SHA_256 */
+void ssl_tls_exporter_rejects_bad_parameters(
+    int proto, int exported_key_length, int label_length, int context_length)
+{
+    int ret = -1;
+    uint8_t *key_buffer = NULL;
+    char *label = NULL;
+    uint8_t *context = NULL;
+    mbedtls_test_ssl_endpoint client_ep, server_ep;
+    mbedtls_test_handshake_test_options options;
+
+    TEST_ASSERT(exported_key_length > 0);
+    TEST_ASSERT(label_length > 0);
+    TEST_ASSERT(context_length > 0);
+    TEST_CALLOC(key_buffer, exported_key_length);
+    TEST_CALLOC(label, label_length);
+    TEST_CALLOC(context, context_length);
+
+    MD_OR_USE_PSA_INIT();
+
+    ret = mbedtls_test_ssl_do_handshake_with_endpoints(&server_ep, &client_ep, &options, proto);
+    TEST_ASSERT(ret == 0);
+
+    ret = mbedtls_ssl_export_keying_material(&client_ep.ssl,
+                                             key_buffer, exported_key_length,
+                                             label, label_length,
+                                             context, context_length, 1);
+    TEST_ASSERT(ret == MBEDTLS_ERR_SSL_BAD_INPUT_DATA);
+
+exit:
+    mbedtls_test_ssl_endpoint_free(&server_ep, NULL);
+    mbedtls_test_ssl_endpoint_free(&client_ep, NULL);
+    mbedtls_test_free_handshake_options(&options);
+    mbedtls_free(key_buffer);
+    mbedtls_free(label);
+    mbedtls_free(context);
+    MD_OR_USE_PSA_DONE();
+}
+/* END_CASE */
+
+/* BEGIN_CASE depends_on:MBEDTLS_SSL_KEYING_MATERIAL_EXPORT:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:PSA_WANT_ECC_SECP_R1_384:PSA_WANT_ALG_SHA_256 */
+void ssl_tls_exporter_too_early(int proto, int check_server, int state)
+{
+    enum { BUFFSIZE = 1024 };
+
+    int ret = -1;
+    mbedtls_test_ssl_endpoint server_ep, client_ep;
+
+    mbedtls_test_handshake_test_options options;
+    mbedtls_test_init_handshake_options(&options);
+    options.server_min_version = proto;
+    options.client_min_version = proto;
+    options.server_max_version = proto;
+    options.client_max_version = proto;
+
+    MD_OR_USE_PSA_INIT();
+
+    ret = mbedtls_test_ssl_endpoint_init(&server_ep, MBEDTLS_SSL_IS_SERVER, &options,
+                                         NULL, NULL, NULL);
+    TEST_ASSERT(ret == 0);
+    ret = mbedtls_test_ssl_endpoint_init(&client_ep, MBEDTLS_SSL_IS_CLIENT, &options,
+                                         NULL, NULL, NULL);
+    TEST_ASSERT(ret == 0);
+
+    ret = mbedtls_test_mock_socket_connect(&client_ep.socket, &server_ep.socket, BUFFSIZE);
+    TEST_ASSERT(ret == 0);
+
+    if (check_server) {
+        ret = mbedtls_test_move_handshake_to_state(&server_ep.ssl, &client_ep.ssl, state);
+    } else {
+        ret = mbedtls_test_move_handshake_to_state(&client_ep.ssl, &server_ep.ssl, state);
+    }
+    TEST_ASSERT(ret == 0 || ret == MBEDTLS_ERR_SSL_WANT_READ || MBEDTLS_ERR_SSL_WANT_WRITE);
+
+    char label[] = "test-label";
+    uint8_t key_buffer[24] = { 0 };
+    ret = mbedtls_ssl_export_keying_material(check_server ? &server_ep.ssl : &client_ep.ssl,
+                                             key_buffer, sizeof(key_buffer),
+                                             label, sizeof(label),
+                                             NULL, 0, 0);
+
+    /* FIXME: A more appropriate error code should be created for this case. */
+    TEST_ASSERT(ret == MBEDTLS_ERR_SSL_BAD_INPUT_DATA);
+
+exit:
+    mbedtls_test_ssl_endpoint_free(&server_ep, NULL);
+    mbedtls_test_ssl_endpoint_free(&client_ep, NULL);
+    mbedtls_test_free_handshake_options(&options);
+    MD_OR_USE_PSA_DONE();
+}
+/* END_CASE */
diff --git a/tests/suites/test_suite_ssl.records.data b/tests/suites/test_suite_ssl.records.data
new file mode 100644
index 0000000..01ce227
--- /dev/null
+++ b/tests/suites/test_suite_ssl.records.data
@@ -0,0 +1,162 @@
+Recombine server flight 1: TLS 1.2, nominal
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_NOMINAL:0:"<= handshake wrapup":"<= handshake wrapup":MBEDTLS_SSL_HANDSHAKE_OVER:0
+
+Recombine server flight 1: TLS 1.3, nominal
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_CHACHAPOLY
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_3:RECOMBINE_NOMINAL:0:"<= handshake wrapup":"<= handshake wrapup":MBEDTLS_SSL_HANDSHAKE_OVER:0
+
+Recombine server flight 1: TLS 1.2, coalesce 2
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_COALESCE:2:"<= handshake wrapup":"<= handshake wrapup":MBEDTLS_SSL_HANDSHAKE_OVER:0
+
+Recombine server flight 1: TLS 1.2, coalesce 3
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_COALESCE:3:"<= handshake wrapup":"<= handshake wrapup":MBEDTLS_SSL_HANDSHAKE_OVER:0
+
+Recombine server flight 1: TLS 1.2, coalesce all
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_COALESCE:INT_MAX:"<= handshake wrapup":"<= handshake wrapup":MBEDTLS_SSL_HANDSHAKE_OVER:0
+
+# TLS 1.3 has a single non-encrypted handshake record, so this doesn't
+# actually perform any coalescing. Run the test case anyway, but this does
+# very little beyond exercising the test code itself a little.
+Recombine server flight 1: TLS 1.3, coalesce all
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_CHACHAPOLY
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_3:RECOMBINE_COALESCE:INT_MAX:"<= handshake wrapup":"<= handshake wrapup":MBEDTLS_SSL_HANDSHAKE_OVER:0
+
+Recombine server flight 1: TLS 1.2, split first at 4
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_SPLIT_FIRST:4:"initial handshake fragment\: 4, 0..4 of":"<= handshake wrapup":MBEDTLS_SSL_HANDSHAKE_OVER:0
+
+Recombine server flight 1: TLS 1.3, split first at 4
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_CHACHAPOLY
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_3:RECOMBINE_SPLIT_FIRST:4:"initial handshake fragment\: 4, 0..4 of":"<= handshake wrapup":MBEDTLS_SSL_HANDSHAKE_OVER:0
+
+Recombine server flight 1: TLS 1.2, split first at end-1
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_SPLIT_FIRST:-1:"subsequent handshake fragment\: 1,":"<= handshake wrapup":MBEDTLS_SSL_HANDSHAKE_OVER:0
+
+Recombine server flight 1: TLS 1.3, split first at end-1
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_CHACHAPOLY
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_3:RECOMBINE_SPLIT_FIRST:-1:"subsequent handshake fragment\: 1,":"<= handshake wrapup":MBEDTLS_SSL_HANDSHAKE_OVER:0
+
+# The library doesn't support an initial handshake fragment that doesn't
+# contain the full 4-byte handshake header.
+Recombine server flight 1: TLS 1.2, split first at 3 (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_SPLIT_FIRST:3:"handshake message too short\: 3":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_INVALID_RECORD
+
+Recombine server flight 1: TLS 1.3, split first at 3 (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_CHACHAPOLY
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_3:RECOMBINE_SPLIT_FIRST:3:"handshake message too short\: 3":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_INVALID_RECORD
+
+Recombine server flight 1: TLS 1.2, split first at 2 (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_SPLIT_FIRST:2:"handshake message too short\: 2":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_INVALID_RECORD
+
+Recombine server flight 1: TLS 1.3, split first at 2 (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_CHACHAPOLY
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_3:RECOMBINE_SPLIT_FIRST:2:"handshake message too short\: 2":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_INVALID_RECORD
+
+Recombine server flight 1: TLS 1.2, split first at 1 (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_SPLIT_FIRST:1:"handshake message too short\: 1":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_INVALID_RECORD
+
+Recombine server flight 1: TLS 1.3, split first at 1 (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_CHACHAPOLY
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_3:RECOMBINE_SPLIT_FIRST:1:"handshake message too short\: 1":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_INVALID_RECORD
+
+Recombine server flight 1: TLS 1.2, truncate at 4 (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_TRUNCATE_FIRST:4:"initial handshake fragment\: 4, 0..4 of":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_WANT_READ
+
+Recombine server flight 1: TLS 1.3, truncate at 4 (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_CHACHAPOLY
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_3:RECOMBINE_TRUNCATE_FIRST:4:"initial handshake fragment\: 4, 0..4 of":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_WANT_READ
+
+Recombine server flight 1: TLS 1.2, insert empty record after first (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_SPLIT_FIRST:0:"rejecting empty record":"":MBEDTLS_SSL_SERVER_CERTIFICATE:MBEDTLS_ERR_SSL_INVALID_RECORD
+
+Recombine server flight 1: TLS 1.3, insert empty record after first (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_CHACHAPOLY
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_3:RECOMBINE_SPLIT_FIRST:0:"rejecting empty record":"":MBEDTLS_SSL_ENCRYPTED_EXTENSIONS:MBEDTLS_ERR_SSL_INVALID_RECORD
+
+Recombine server flight 1: TLS 1.2, insert empty record at start (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_INSERT_EMPTY:0:"rejecting empty record":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_INVALID_RECORD
+
+Recombine server flight 1: TLS 1.3, insert empty record at start (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_CHACHAPOLY
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_3:RECOMBINE_INSERT_EMPTY:0:"rejecting empty record":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_INVALID_RECORD
+
+Recombine server flight 1: TLS 1.2, insert empty record at 42 (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_INSERT_EMPTY:42:"rejecting empty record":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_INVALID_RECORD
+
+Recombine server flight 1: TLS 1.3, insert empty record at 42 (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_CHACHAPOLY
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_3:RECOMBINE_INSERT_EMPTY:42:"rejecting empty record":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_INVALID_RECORD
+
+Recombine server flight 1: TLS 1.2, insert ChangeCipherSpec record at 5 (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_INSERT_RECORD:MBEDTLS_SSL_MSG_CHANGE_CIPHER_SPEC:"non-handshake message in the middle of a fragmented handshake message":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE
+
+Recombine server flight 1: TLS 1.3, insert ChangeCipherSpec record at 5 (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_CHACHAPOLY
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_3:RECOMBINE_INSERT_RECORD:MBEDTLS_SSL_MSG_CHANGE_CIPHER_SPEC:"non-handshake message in the middle of a fragmented handshake message":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE
+
+Recombine server flight 1: TLS 1.2, insert alert record at 5 (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_INSERT_RECORD:MBEDTLS_SSL_MSG_ALERT:"non-handshake message in the middle of a fragmented handshake message":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE
+
+Recombine server flight 1: TLS 1.3, insert alert record at 5 (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_CHACHAPOLY
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_3:RECOMBINE_INSERT_RECORD:MBEDTLS_SSL_MSG_ALERT:"non-handshake message in the middle of a fragmented handshake message":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE
+
+Recombine server flight 1: TLS 1.2, insert data record at 5 (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_INSERT_RECORD:MBEDTLS_SSL_MSG_APPLICATION_DATA:"non-handshake message in the middle of a fragmented handshake message":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE
+
+Recombine server flight 1: TLS 1.3, insert data record at 5 (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_CHACHAPOLY
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_3:RECOMBINE_INSERT_RECORD:MBEDTLS_SSL_MSG_APPLICATION_DATA:"non-handshake message in the middle of a fragmented handshake message":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE
+
+Recombine server flight 1: TLS 1.2, insert CID record at 5 (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_INSERT_RECORD:MBEDTLS_SSL_MSG_CID:"unknown record type":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_INVALID_RECORD
+
+Recombine server flight 1: TLS 1.3, insert CID record at 5 (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_CHACHAPOLY
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_3:RECOMBINE_INSERT_RECORD:MBEDTLS_SSL_MSG_CID:"unknown record type":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_INVALID_RECORD
+
+Recombine server flight 1: TLS 1.2, insert unknown record at 5 (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_INSERT_RECORD:255:"unknown record type 255":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_INVALID_RECORD
+
+Recombine server flight 1: TLS 1.3, insert unknown record at 5 (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_CHACHAPOLY
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_3:RECOMBINE_INSERT_RECORD:255:"unknown record type 255":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_INVALID_RECORD
+
+# Since there is a single unencrypted handshake message in the first flight
+# from the server, and the test code that recombines handshake records can only
+# handle plaintext records, we can't have TLS 1.3 tests with coalesced
+# handshake messages. Hence most coalesce-and-split test cases are 1.2-only.
+
+Recombine server flight 1: TLS 1.2, coalesce and split at 4
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_COALESCE_SPLIT_ONCE:4:"initial handshake fragment\: 4, 0..4 of":"<= handshake wrapup":MBEDTLS_SSL_HANDSHAKE_OVER:0
+
+# The last message of the first flight from the server is ServerHelloDone,
+# which is an empty handshake message, i.e. of length 4. The library doesn't
+# support fragmentation of a handshake header, so the last place where we
+# can split the flight is 4+1 = 5 bytes before it ends, with 1 byte in the
+# previous handshake message and 4 bytes of ServerHelloDone including header.
+Recombine server flight 1: TLS 1.2, coalesce and split at end-5
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_COALESCE_SPLIT_ONCE:-5:"subsequent handshake fragment\: 5,":"<= handshake wrapup":MBEDTLS_SSL_HANDSHAKE_OVER:0
+
+Recombine server flight 1: TLS 1.2, coalesce and split at both ends
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_COALESCE_SPLIT_BOTH_ENDS:5:"subsequent handshake fragment\: 5,":"<= handshake wrapup":MBEDTLS_SSL_HANDSHAKE_OVER:0
diff --git a/tests/suites/test_suite_ssl.tls-defrag.data b/tests/suites/test_suite_ssl.tls-defrag.data
new file mode 100644
index 0000000..8fca923
--- /dev/null
+++ b/tests/suites/test_suite_ssl.tls-defrag.data
@@ -0,0 +1,215 @@
+# (Minimal) ClientHello breakdown:
+# 160303rlrl - record header, 2-byte record contents len
+# 01hlhlhl - handshake header, 3-byte handshake message len
+# 0303 - protocol version: 1.2
+# 0123456789abcdef (repeated, 4 times total) - 32-byte "random"
+# 00 - session ID (empty)
+# 0002cvcv - ciphersuite list: 2-byte len + list of 2-byte values (see below)
+# 0100 - compression methods: 1-byte len then "null" (only legal value now)
+# [then end, or extensions, see notes below]
+# elel - 2-byte extensions length
+# ...
+# 000a - elliptic_curves aka supported_groups
+# 0004 - extension length
+# 0002 - length of named_curve_list / named_group_list
+# 0017 - secp256r1 aka NIST P-256
+# ...
+# 002b - supported version (for TLS 1.3)
+# 0003 - extension length
+# 02   - length of versions
+# 0304 - TLS 1.3 ("SSL 3.4")
+# ...
+# 000d - signature algorithms
+# 0004 - extension length
+# 0002 - SignatureSchemeList length
+# 0403 - ecdsa_secp256r1_sha256
+# ...
+# 0033 - key share
+# 0002 - extension length
+# 0000 - length of client_shares (empty is valid)
+#
+# Note: currently our TLS "1.3 or 1.2" code requires extension length to be
+# present even it it's 0. This is not strictly compliant but doesn't matter
+# much in practice as these days everyone wants to use signature_algorithms
+# (for hashes better than SHA-1), secure_renego (even if you have renego
+# disabled), and most people want either ECC or PSK related extensions.
+# See https://github.com/Mbed-TLS/mbedtls/issues/9963
+#
+# Also, currently we won't negotiate ECC ciphersuites unless at least the
+# supported_groups extension is present, see
+# https://github.com/Mbed-TLS/mbedtls/issues/7458
+#
+# For TLS 1.3 with ephemeral key exchange, mandatory extensions are:
+# - supported versions (as for all of TLS 1.3)
+# - supported groups
+# - key share
+# - signature algorithms
+# (see ssl_tls13_client_hello_has_exts_for_ephemeral_key_exchange()).
+#
+# Note: cccc is currently not assigned, so can be used get a consistent
+# "no matching ciphersuite" behaviour regardless of the configuration.
+# c02b is MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (1.2)
+# 1301 is MBEDTLS_TLS1_3_AES_128_GCM_SHA256 (1.3)
+
+# See "ClientHello breakdown" above
+# MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 with secp256r1
+Inject ClientHello - TLS 1.2 good (for reference)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED:MBEDTLS_SSL_HAVE_AES:MBEDTLS_MD_CAN_SHA256:MBEDTLS_SSL_HAVE_GCM:MBEDTLS_ECP_HAVE_SECP256R1:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_MD_CAN_SHA1
+inject_client_content_on_the_wire:MBEDTLS_PK_ECDSA:MBEDTLS_SSL_CLIENT_HELLO:"16030300370100003303030123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef000002c02b01000008000a000400020017":"<= parse client hello":0
+
+# See "ClientHello breakdown" above
+# Same as the above test with s/c02b/cccc/ as the ciphersuite
+Inject ClientHello - TLS 1.2 unknown ciphersuite (for reference)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED:MBEDTLS_SSL_HAVE_AES:MBEDTLS_MD_CAN_SHA256:MBEDTLS_SSL_HAVE_GCM:MBEDTLS_ECP_HAVE_SECP256R1:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_MD_CAN_SHA1
+inject_client_content_on_the_wire:MBEDTLS_PK_ECDSA:MBEDTLS_SSL_CLIENT_HELLO:"160303002f0100002b03030123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef000002cccc01000000":"got no ciphersuites in common":MBEDTLS_ERR_SSL_HANDSHAKE_FAILURE
+
+# See "ClientHello breakdown" above
+# ephemeral with secp256r1 + MBEDTLS_TLS1_3_AES_128_GCM_SHA256
+Inject ClientHello - TLS 1.3 good (for reference)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_AES:MBEDTLS_MD_CAN_SHA256:MBEDTLS_SSL_HAVE_GCM:MBEDTLS_ECP_HAVE_SECP256R1:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_PK_CAN_ECDSA_SIGN:MBEDTLS_PK_CAN_ECDSA_VERIFY
+inject_client_content_on_the_wire:MBEDTLS_PK_ECDSA:MBEDTLS_SSL_CLIENT_HELLO:"160303004c0100004803030123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef00000213010100001d000a000400020017002b0003020304000d000400020403003300020000":"key exchange mode\: ephemeral":0
+
+# See "ClientHello breakdown" above
+# Same as the above test with s/1301/cccc/ as the ciphersuite
+Inject ClientHello - TLS 1.3 unknown ciphersuite (for reference)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_AES:MBEDTLS_MD_CAN_SHA256:MBEDTLS_SSL_HAVE_GCM:MBEDTLS_ECP_HAVE_SECP256R1:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_PK_CAN_ECDSA_SIGN:MBEDTLS_PK_CAN_ECDSA_VERIFY
+inject_client_content_on_the_wire:MBEDTLS_PK_ECDSA:MBEDTLS_SSL_CLIENT_HELLO:"160303004c0100004803030123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef000002cccc0100001d000a000400020017002b0003020304000d000400020403003300020000":"No matched ciphersuite":MBEDTLS_ERR_SSL_HANDSHAKE_FAILURE
+
+# See "ClientHello breakdown" above
+# ephemeral with secp256r1 + MBEDTLS_TLS1_3_AES_128_GCM_SHA256
+# The purpose of this test case is to ensure nothing bad happens when the
+# connection is closed while we're waiting for more fragments.
+Inject ClientHello - TLS 1.3 4 + 71 then EOF (missing 1 byte)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_AES:MBEDTLS_MD_CAN_SHA256:MBEDTLS_SSL_HAVE_GCM:MBEDTLS_ECP_HAVE_SECP256R1:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_PK_CAN_ECDSA_SIGN:MBEDTLS_PK_CAN_ECDSA_VERIFY
+inject_client_content_on_the_wire:MBEDTLS_PK_ECDSA:MBEDTLS_SSL_CLIENT_HELLO:"160303000401000048160303004703030123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef00000213010100001d000a000400020017002b0003020304000d0004000204030033000200":"waiting for more handshake fragments":MBEDTLS_ERR_SSL_WANT_READ
+
+# See "ClientHello breakdown" above
+# ephemeral with secp256r1 + MBEDTLS_TLS1_3_AES_128_GCM_SHA256
+# The purpose of this test case is to ensure nothing bad happens when the
+# connection is closed while we're waiting for more fragments.
+Inject ClientHello - TLS 1.3 4 then EOF (missing 72 bytes)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_AES:MBEDTLS_MD_CAN_SHA256:MBEDTLS_SSL_HAVE_GCM:MBEDTLS_ECP_HAVE_SECP256R1:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_PK_CAN_ECDSA_SIGN:MBEDTLS_PK_CAN_ECDSA_VERIFY
+inject_client_content_on_the_wire:MBEDTLS_PK_ECDSA:MBEDTLS_SSL_CLIENT_HELLO:"160303000401000048":"waiting for more handshake fragments":MBEDTLS_ERR_SSL_WANT_READ
+
+# See "ClientHello breakdown" above
+# ephemeral with secp256r1 + MBEDTLS_TLS1_3_AES_128_GCM_SHA256
+Inject ClientHello - TLS 1.3 fragmented 4 + 72 OK
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_AES:MBEDTLS_MD_CAN_SHA256:MBEDTLS_SSL_HAVE_GCM:MBEDTLS_ECP_HAVE_SECP256R1:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_PK_CAN_ECDSA_SIGN:MBEDTLS_PK_CAN_ECDSA_VERIFY
+inject_client_content_on_the_wire:MBEDTLS_PK_ECDSA:MBEDTLS_SSL_CLIENT_HELLO:"160303000401000048160303004803030123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef00000213010100001d000a000400020017002b0003020304000d000400020403003300020000":"key exchange mode\: ephemeral":0
+
+# See "ClientHello breakdown" above
+# ephemeral with secp256r1 + MBEDTLS_TLS1_3_AES_128_GCM_SHA256
+Inject ClientHello - TLS 1.3 fragmented 3 + 73 rejected
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_AES:MBEDTLS_MD_CAN_SHA256:MBEDTLS_SSL_HAVE_GCM:MBEDTLS_ECP_HAVE_SECP256R1:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_PK_CAN_ECDSA_SIGN:MBEDTLS_PK_CAN_ECDSA_VERIFY
+inject_client_content_on_the_wire:MBEDTLS_PK_ECDSA:MBEDTLS_SSL_CLIENT_HELLO:"160303000301000016030300494803030123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef00000213010100001d000a000400020017002b0003020304000d000400020403003300020000":"handshake message too short":MBEDTLS_ERR_SSL_INVALID_RECORD
+
+# See "ClientHello breakdown" above
+# ephemeral with secp256r1 + MBEDTLS_TLS1_3_AES_128_GCM_SHA256
+Inject ClientHello - TLS 1.3 fragmented 2 + 74 rejected
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_AES:MBEDTLS_MD_CAN_SHA256:MBEDTLS_SSL_HAVE_GCM:MBEDTLS_ECP_HAVE_SECP256R1:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_PK_CAN_ECDSA_SIGN:MBEDTLS_PK_CAN_ECDSA_VERIFY
+inject_client_content_on_the_wire:MBEDTLS_PK_ECDSA:MBEDTLS_SSL_CLIENT_HELLO:"16030300020100160303004a004803030123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef00000213010100001d000a000400020017002b0003020304000d000400020403003300020000":"handshake message too short":MBEDTLS_ERR_SSL_INVALID_RECORD
+
+# See "ClientHello breakdown" above
+# ephemeral with secp256r1 + MBEDTLS_TLS1_3_AES_128_GCM_SHA256
+Inject ClientHello - TLS 1.3 fragmented 1 + 75 rejected
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_AES:MBEDTLS_MD_CAN_SHA256:MBEDTLS_SSL_HAVE_GCM:MBEDTLS_ECP_HAVE_SECP256R1:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_PK_CAN_ECDSA_SIGN:MBEDTLS_PK_CAN_ECDSA_VERIFY
+inject_client_content_on_the_wire:MBEDTLS_PK_ECDSA:MBEDTLS_SSL_CLIENT_HELLO:"160303000101160303004b00004803030123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef00000213010100001d000a000400020017002b0003020304000d000400020403003300020000":"handshake message too short":MBEDTLS_ERR_SSL_INVALID_RECORD
+
+# See "ClientHello breakdown" above
+# ephemeral with secp256r1 + MBEDTLS_TLS1_3_AES_128_GCM_SHA256
+Inject ClientHello - TLS 1.3 fragmented 0 + 76 rejected
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_AES:MBEDTLS_MD_CAN_SHA256:MBEDTLS_SSL_HAVE_GCM:MBEDTLS_ECP_HAVE_SECP256R1:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_PK_CAN_ECDSA_SIGN:MBEDTLS_PK_CAN_ECDSA_VERIFY
+inject_client_content_on_the_wire:MBEDTLS_PK_ECDSA:MBEDTLS_SSL_CLIENT_HELLO:"1603030000160303004c0100004803030123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef00000213010100001d000a000400020017002b0003020304000d000400020403003300020000":"ssl_get_next_record() returned":MBEDTLS_ERR_SSL_INVALID_RECORD
+
+# See "ClientHello breakdown" above
+# ephemeral with secp256r1 + MBEDTLS_TLS1_3_AES_128_GCM_SHA256
+Inject ClientHello - TLS 1.3 fragmented 72 + 4 OK
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_AES:MBEDTLS_MD_CAN_SHA256:MBEDTLS_SSL_HAVE_GCM:MBEDTLS_ECP_HAVE_SECP256R1:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_PK_CAN_ECDSA_SIGN:MBEDTLS_PK_CAN_ECDSA_VERIFY
+inject_client_content_on_the_wire:MBEDTLS_PK_ECDSA:MBEDTLS_SSL_CLIENT_HELLO:"16030300480100004803030123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef00000213010100001d000a000400020017002b0003020304000d0004000204030033160303000400020000":"key exchange mode\: ephemeral":0
+
+# See "ClientHello breakdown" above
+# ephemeral with secp256r1 + MBEDTLS_TLS1_3_AES_128_GCM_SHA256
+Inject ClientHello - TLS 1.3 fragmented 73 + 3 OK
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_AES:MBEDTLS_MD_CAN_SHA256:MBEDTLS_SSL_HAVE_GCM:MBEDTLS_ECP_HAVE_SECP256R1:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_PK_CAN_ECDSA_SIGN:MBEDTLS_PK_CAN_ECDSA_VERIFY
+inject_client_content_on_the_wire:MBEDTLS_PK_ECDSA:MBEDTLS_SSL_CLIENT_HELLO:"16030300490100004803030123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef00000213010100001d000a000400020017002b0003020304000d0004000204030033001603030003020000":"key exchange mode\: ephemeral":0
+
+# See "ClientHello breakdown" above
+# ephemeral with secp256r1 + MBEDTLS_TLS1_3_AES_128_GCM_SHA256
+Inject ClientHello - TLS 1.3 fragmented 74 + 2 OK
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_AES:MBEDTLS_MD_CAN_SHA256:MBEDTLS_SSL_HAVE_GCM:MBEDTLS_ECP_HAVE_SECP256R1:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_PK_CAN_ECDSA_SIGN:MBEDTLS_PK_CAN_ECDSA_VERIFY
+inject_client_content_on_the_wire:MBEDTLS_PK_ECDSA:MBEDTLS_SSL_CLIENT_HELLO:"160303004a0100004803030123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef00000213010100001d000a000400020017002b0003020304000d0004000204030033000216030300020000":"key exchange mode\: ephemeral":0
+
+# See "ClientHello breakdown" above
+# ephemeral with secp256r1 + MBEDTLS_TLS1_3_AES_128_GCM_SHA256
+Inject ClientHello - TLS 1.3 fragmented 73 + 1 OK
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_AES:MBEDTLS_MD_CAN_SHA256:MBEDTLS_SSL_HAVE_GCM:MBEDTLS_ECP_HAVE_SECP256R1:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_PK_CAN_ECDSA_SIGN:MBEDTLS_PK_CAN_ECDSA_VERIFY
+inject_client_content_on_the_wire:MBEDTLS_PK_ECDSA:MBEDTLS_SSL_CLIENT_HELLO:"160303004b0100004803030123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef00000213010100001d000a000400020017002b0003020304000d0004000204030033000200160303000100":"key exchange mode\: ephemeral":0
+
+# See "ClientHello breakdown" above
+# ephemeral with secp256r1 + MBEDTLS_TLS1_3_AES_128_GCM_SHA256
+Inject ClientHello - TLS 1.3 fragmented 4 + appdata + 72 rejected
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_AES:MBEDTLS_MD_CAN_SHA256:MBEDTLS_SSL_HAVE_GCM:MBEDTLS_ECP_HAVE_SECP256R1:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_PK_CAN_ECDSA_SIGN:MBEDTLS_PK_CAN_ECDSA_VERIFY
+inject_client_content_on_the_wire:MBEDTLS_PK_ECDSA:MBEDTLS_SSL_CLIENT_HELLO:"16030300040100004817030300020102160303004803030123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef00000213010100001d000a000400020017002b0003020304000d000400020403003300020000":"non-handshake message in the middle of a fragmented handshake message":MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE
+
+# See "ClientHello breakdown" above
+# ephemeral with secp256r1 + MBEDTLS_TLS1_3_AES_128_GCM_SHA256
+Inject ClientHello - TLS 1.3 fragmented 4 + alert(warn) + 72 rejected
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_AES:MBEDTLS_MD_CAN_SHA256:MBEDTLS_SSL_HAVE_GCM:MBEDTLS_ECP_HAVE_SECP256R1:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_PK_CAN_ECDSA_SIGN:MBEDTLS_PK_CAN_ECDSA_VERIFY
+inject_client_content_on_the_wire:MBEDTLS_PK_ECDSA:MBEDTLS_SSL_CLIENT_HELLO:"1603030004010000481503030002015a160303004803030123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef00000213010100001d000a000400020017002b0003020304000d000400020403003300020000":"non-handshake message in the middle of a fragmented handshake message":MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE
+
+# See "ClientHello breakdown" above
+# ephemeral with secp256r1 + MBEDTLS_TLS1_3_AES_128_GCM_SHA256
+Inject ClientHello - TLS 1.3 fragmented 4 + alert(fatal) + 72 rejected
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_AES:MBEDTLS_MD_CAN_SHA256:MBEDTLS_SSL_HAVE_GCM:MBEDTLS_ECP_HAVE_SECP256R1:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_PK_CAN_ECDSA_SIGN:MBEDTLS_PK_CAN_ECDSA_VERIFY
+inject_client_content_on_the_wire:MBEDTLS_PK_ECDSA:MBEDTLS_SSL_CLIENT_HELLO:"1603030004010000481503030002025a160303004803030123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef00000213010100001d000a000400020017002b0003020304000d000400020403003300020000":"non-handshake message in the middle of a fragmented handshake message":MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE
+
+# See "ClientHello breakdown" above
+# ephemeral with secp256r1 + MBEDTLS_TLS1_3_AES_128_GCM_SHA256
+Inject ClientHello - TLS 1.3 fragmented 4 + CCS + 72 rejected
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_AES:MBEDTLS_MD_CAN_SHA256:MBEDTLS_SSL_HAVE_GCM:MBEDTLS_ECP_HAVE_SECP256R1:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_PK_CAN_ECDSA_SIGN:MBEDTLS_PK_CAN_ECDSA_VERIFY
+inject_client_content_on_the_wire:MBEDTLS_PK_ECDSA:MBEDTLS_SSL_CLIENT_HELLO:"160303000401000048140303000101160303004803030123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef00000213010100001d000a000400020017002b0003020304000d000400020403003300020000":"non-handshake message in the middle of a fragmented handshake message":MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE
+
+# See "ClientHello breakdown" above
+# ephemeral with secp256r1 + MBEDTLS_TLS1_3_AES_128_GCM_SHA256
+Inject ClientHello - TLS 1.3 fragmented 4 + invalid type + 72 rejected
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_HAVE_AES:MBEDTLS_MD_CAN_SHA256:MBEDTLS_SSL_HAVE_GCM:MBEDTLS_ECP_HAVE_SECP256R1:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_PK_CAN_ECDSA_SIGN:MBEDTLS_PK_CAN_ECDSA_VERIFY
+inject_client_content_on_the_wire:MBEDTLS_PK_ECDSA:MBEDTLS_SSL_CLIENT_HELLO:"1603030004010000481003030002015a160303004803030123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef00000213010100001d000a000400020017002b0003020304000d000400020403003300020000":"unknown record type":MBEDTLS_ERR_SSL_INVALID_RECORD
+
+# The buffer is actually larger than IN_CONTENT_LEN as we leave room for
+# record protection overhead (IV, MAC/tag, padding (up to 256 bytes)), CID...
+# The maximum size for an unencrypted (and without CID which is DTLS only)
+# handshake message we can hold in the buffer is
+#   MBEDTLS_SSL_IN_BUFFER_LEN - MBEDTLS_SSL_HEADER_LEN - 4
+# (the 4 is for the handshake header).
+# However, due to overhead, fragmented messages need to be 5 bytes shorter in
+# order to actually fit (leave room for an extra record header).
+Send large fragmented ClientHello: reassembled 1 byte larger than the buffer
+send_large_fragmented_hello:MBEDTLS_SSL_IN_BUFFER_LEN - MBEDTLS_SSL_HEADER_LEN - 3:0:"requesting more data than fits":MBEDTLS_ERR_SSL_BAD_INPUT_DATA
+
+Send large fragmented ClientHello: would just fit except for overhead
+send_large_fragmented_hello:MBEDTLS_SSL_IN_BUFFER_LEN - MBEDTLS_SSL_HEADER_LEN - 4:0:"requesting more data than fits":MBEDTLS_ERR_SSL_BAD_INPUT_DATA
+
+Send large fragmented ClientHello: would fit except for overhead (1)
+send_large_fragmented_hello:MBEDTLS_SSL_IN_BUFFER_LEN - MBEDTLS_SSL_HEADER_LEN - 5:0:"requesting more data than fits":MBEDTLS_ERR_SSL_BAD_INPUT_DATA
+
+Send large fragmented ClientHello: would fit except for overhead (2)
+send_large_fragmented_hello:MBEDTLS_SSL_IN_BUFFER_LEN - MBEDTLS_SSL_HEADER_LEN - 6:0:"requesting more data than fits":MBEDTLS_ERR_SSL_BAD_INPUT_DATA
+
+Send large fragmented ClientHello: would fit except for overhead (3)
+send_large_fragmented_hello:MBEDTLS_SSL_IN_BUFFER_LEN - MBEDTLS_SSL_HEADER_LEN - 7:0:"requesting more data than fits":MBEDTLS_ERR_SSL_BAD_INPUT_DATA
+
+Send large fragmented ClientHello: would fit except for overhead (4)
+send_large_fragmented_hello:MBEDTLS_SSL_IN_BUFFER_LEN - MBEDTLS_SSL_HEADER_LEN - 8:0:"requesting more data than fits":MBEDTLS_ERR_SSL_BAD_INPUT_DATA
+
+# Since we're sending dummy contents (all 0x2a) for the ClientHello,
+# the first thing that's going to fail is the version check. The fact that we
+# got around to checking it confirms reassembly completed sucessfully.
+Send large fragmented ClientHello: just fits
+send_large_fragmented_hello:MBEDTLS_SSL_IN_BUFFER_LEN - MBEDTLS_SSL_HEADER_LEN - 9:0:"Unsupported version of TLS":MBEDTLS_ERR_SSL_BAD_PROTOCOL_VERSION
+
+# We're generating a virtual record header for the reassembled HS message,
+# which requires that the length fits in two bytes. Of course we won't get
+# there because if the length doesn't fit in two bytes then the message won't
+# fit in the buffer, but still add a test just in case.
+Send large fragmented ClientHello: length doesn't fit in two bytes
+send_large_fragmented_hello:0x10000:0:"requesting more data than fits":MBEDTLS_ERR_SSL_BAD_INPUT_DATA
diff --git a/tests/suites/test_suite_test_helpers.function b/tests/suites/test_suite_test_helpers.function
index 8c5d5ad..0139faf 100644
--- a/tests/suites/test_suite_test_helpers.function
+++ b/tests/suites/test_suite_test_helpers.function
@@ -15,7 +15,7 @@
 /* Test that poison+unpoison leaves the memory accessible. */
 /* We can't test that poisoning makes the memory inaccessible:
  * there's no sane way to catch an Asan/Valgrind complaint.
- * That negative testing is done in programs/test/metatest.c. */
+ * That negative testing is done in framework/tests/programs/metatest.c. */
 void memory_poison_unpoison(int align, int size)
 {
     unsigned char *buf = NULL;
diff --git a/tests/suites/test_suite_version.data b/tests/suites/test_suite_version.data
index cc71a4e..bdf6fa0 100644
--- a/tests/suites/test_suite_version.data
+++ b/tests/suites/test_suite_version.data
@@ -1,8 +1,8 @@
 Check compile time library version
-check_compiletime_version:"3.6.2"
+check_compiletime_version:"3.6.3"
 
 Check runtime library version
-check_runtime_version:"3.6.2"
+check_runtime_version:"3.6.3"
 
 Check for MBEDTLS_VERSION_C
 check_feature:"MBEDTLS_VERSION_C":0