From c44430657e8d963a14f64dc846a546165b1a3b33 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 14 Mar 2025 18:40:50 +0100 Subject: [PATCH 01/61] Minor documentation fixes Signed-off-by: Gilles Peskine --- include/mbedtls/pk.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/include/mbedtls/pk.h b/include/mbedtls/pk.h index 52f4cc6c9e..a0b616bcd9 100644 --- a/include/mbedtls/pk.h +++ b/include/mbedtls/pk.h @@ -218,8 +218,11 @@ typedef struct mbedtls_pk_info_t mbedtls_pk_info_t; * \brief Public key container */ typedef struct mbedtls_pk_context { - const mbedtls_pk_info_t *MBEDTLS_PRIVATE(pk_info); /**< Public key information */ - void *MBEDTLS_PRIVATE(pk_ctx); /**< Underlying public key context */ + /** Method table */ + const mbedtls_pk_info_t *MBEDTLS_PRIVATE(pk_info); + /** Underlying type-specific key context */ + void *MBEDTLS_PRIVATE(pk_ctx); + /* The following field is used to store the ID of a private key in the * following cases: * - opaque key when MBEDTLS_USE_PSA_CRYPTO is defined @@ -838,7 +841,7 @@ int mbedtls_pk_verify_ext(mbedtls_pk_type_t type, const void *options, * length up to the hash length), depending on the padding mode * in the underlying RSA context. For a pk object constructed * by parsing, this is PKCS#1 v1.5 by default. Use - * mbedtls_pk_verify_ext() to explicitly select a different + * mbedtls_pk_sign_ext() to explicitly select a different * algorithm. * * \return 0 on success, or a specific error code. From 6966659a31ef839d6d741426f3e3eac11728cad4 Mon Sep 17 00:00:00 2001 From: David Horstmann Date: Wed, 14 Jan 2026 15:49:33 +0000 Subject: [PATCH 02/61] Add new X509 verification result for 'not started' Add a new verification result bitflag MBEDTLS_X509_VERIFY_NOT_STARTED to use as a safe initial value for verify_result. This is better than the current initial value which is 0 (indicating success). Signed-off-by: David Horstmann --- include/mbedtls/x509.h | 1 + include/mbedtls/x509_crt.h | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/include/mbedtls/x509.h b/include/mbedtls/x509.h index 6b104613d7..ac324fddf6 100644 --- a/include/mbedtls/x509.h +++ b/include/mbedtls/x509.h @@ -108,6 +108,7 @@ #define MBEDTLS_X509_BADCRL_BAD_MD 0x020000 /**< The CRL is signed with an unacceptable hash. */ #define MBEDTLS_X509_BADCRL_BAD_PK 0x040000 /**< The CRL is signed with an unacceptable PK alg (eg RSA vs ECDSA). */ #define MBEDTLS_X509_BADCRL_BAD_KEY 0x080000 /**< The CRL is signed with an unacceptable key (eg bad curve, RSA too short). */ +#define MBEDTLS_X509_VERIFY_NOT_STARTED 0x100000 /**< No verification has yet been performed (used as a safe initial value). */ /** \} name X509 Verify codes */ /** \} addtogroup x509_module */ diff --git a/include/mbedtls/x509_crt.h b/include/mbedtls/x509_crt.h index 6b96039597..6ac17af67d 100644 --- a/include/mbedtls/x509_crt.h +++ b/include/mbedtls/x509_crt.h @@ -209,7 +209,10 @@ mbedtls_x509_crt_profile; "The CRL is signed with an unacceptable PK alg (eg RSA vs ECDSA).") \ X509_CRT_ERROR_INFO(MBEDTLS_X509_BADCRL_BAD_KEY, \ "MBEDTLS_X509_BADCRL_BAD_KEY", \ - "The CRL is signed with an unacceptable key (eg bad curve, RSA too short).") + "The CRL is signed with an unacceptable key (eg bad curve, RSA too short).") \ + X509_CRT_ERROR_INFO(MBEDTLS_X509_VERIFY_NOT_STARTED, \ + "MBEDTLS_X509_VERIFY_NOT_STARTED", \ + "No verification has yet been performed.") /** * Container for writing a certificate (CRT) From 710aaa7ae762b69c9b31592e5f80d885a8d7b32d Mon Sep 17 00:00:00 2001 From: David Horstmann Date: Wed, 3 Sep 2025 11:21:00 +0100 Subject: [PATCH 03/61] Set verify_result to failure by default At initialization, set the verify_result field of the ssl session to MBEDTLS_X509_VERIFY_NOT_STARTED, rather than 0 as it is by default currently. This prevents mbedtls_ssl_get_verify_result() from indicating that certificate verification has passed if it is called prior to the handshake happening. Signed-off-by: David Horstmann --- library/ssl_tls.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/ssl_tls.c b/library/ssl_tls.c index 30cde27923..0585a53ef0 100644 --- a/library/ssl_tls.c +++ b/library/ssl_tls.c @@ -1048,6 +1048,8 @@ void mbedtls_ssl_transform_init(mbedtls_ssl_transform *transform) void mbedtls_ssl_session_init(mbedtls_ssl_session *session) { memset(session, 0, sizeof(mbedtls_ssl_session)); + /* Set verify_result to indicate failure by default. */ + session->verify_result = MBEDTLS_X509_VERIFY_NOT_STARTED; } MBEDTLS_CHECK_RETURN_CRITICAL From b413935518337f12d33d29b7b8d5d68f7c1c741a Mon Sep 17 00:00:00 2001 From: David Horstmann Date: Tue, 7 Oct 2025 16:07:57 +0100 Subject: [PATCH 04/61] Add non-regression test for verify_result init Write a testcase to get verify_result before we have performed a handshake and make sure that it is initialised to a failure value. Signed-off-by: David Horstmann --- tests/suites/test_suite_ssl.data | 3 +++ tests/suites/test_suite_ssl.function | 33 ++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/tests/suites/test_suite_ssl.data b/tests/suites/test_suite_ssl.data index 92bda3efd1..53cf1a0796 100644 --- a/tests/suites/test_suite_ssl.data +++ b/tests/suites/test_suite_ssl.data @@ -3524,3 +3524,6 @@ 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 + +Default verify_result before doing a handshake +verify_result_without_handshake diff --git a/tests/suites/test_suite_ssl.function b/tests/suites/test_suite_ssl.function index 6797a4dd7e..86fac0078b 100644 --- a/tests/suites/test_suite_ssl.function +++ b/tests/suites/test_suite_ssl.function @@ -5999,3 +5999,36 @@ exit: MD_OR_USE_PSA_DONE(); } /* END_CASE */ + +/* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED */ +void verify_result_without_handshake(void) +{ + /* Test the result of verification before we perform a handshake. */ + mbedtls_ssl_context ssl; + mbedtls_ssl_config conf; + + PSA_INIT(); + + mbedtls_ssl_init(&ssl); + mbedtls_ssl_config_init(&conf); + + TEST_EQUAL(mbedtls_ssl_config_defaults(&conf, + MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT), 0); + + mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_OPTIONAL); + mbedtls_ssl_conf_ca_chain(&conf, NULL, NULL); + + TEST_EQUAL(mbedtls_ssl_setup(&ssl, &conf), 0); + + uint32_t verify_result = mbedtls_ssl_get_verify_result(&ssl); + + TEST_EQUAL(verify_result, MBEDTLS_X509_VERIFY_NOT_STARTED); + +exit: + mbedtls_ssl_config_free(&conf); + mbedtls_ssl_free(&ssl); + PSA_DONE(); +} +/* END_CASE */ From 6ca2d7da8b124e728b62e99d769c1b6fd14d442c Mon Sep 17 00:00:00 2001 From: David Horstmann Date: Wed, 8 Oct 2025 10:49:24 +0100 Subject: [PATCH 05/61] Add ChangeLog entry for verify_result hardening Signed-off-by: David Horstmann --- ChangeLog.d/verify-result-default-value.txt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 ChangeLog.d/verify-result-default-value.txt diff --git a/ChangeLog.d/verify-result-default-value.txt b/ChangeLog.d/verify-result-default-value.txt new file mode 100644 index 0000000000..d85dfe2670 --- /dev/null +++ b/ChangeLog.d/verify-result-default-value.txt @@ -0,0 +1,5 @@ +Changes + * Harden mbedtls_ssl_get_verify_result() against misuse. + Return failure if the handshake has not yet been attempted. Previously + the result of verification was zero-initialized so the function would + return 0 (indicating success). From c42f73fe34a6f833fbfd76b165ed23541ae28d71 Mon Sep 17 00:00:00 2001 From: David Horstmann Date: Wed, 28 Jan 2026 17:49:19 +0000 Subject: [PATCH 06/61] Switch to a default value of -1u Since we explicitly document the value 0xFFFFFFFF or -1u as representing 'result not available', we can use it as a sensible default value without creating an API change. Use this value instead of introducing a new verification result value. Signed-off-by: David Horstmann --- include/mbedtls/x509.h | 1 - include/mbedtls/x509_crt.h | 5 +---- library/ssl_tls.c | 4 ++-- tests/suites/test_suite_ssl.function | 2 +- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/include/mbedtls/x509.h b/include/mbedtls/x509.h index ac324fddf6..6b104613d7 100644 --- a/include/mbedtls/x509.h +++ b/include/mbedtls/x509.h @@ -108,7 +108,6 @@ #define MBEDTLS_X509_BADCRL_BAD_MD 0x020000 /**< The CRL is signed with an unacceptable hash. */ #define MBEDTLS_X509_BADCRL_BAD_PK 0x040000 /**< The CRL is signed with an unacceptable PK alg (eg RSA vs ECDSA). */ #define MBEDTLS_X509_BADCRL_BAD_KEY 0x080000 /**< The CRL is signed with an unacceptable key (eg bad curve, RSA too short). */ -#define MBEDTLS_X509_VERIFY_NOT_STARTED 0x100000 /**< No verification has yet been performed (used as a safe initial value). */ /** \} name X509 Verify codes */ /** \} addtogroup x509_module */ diff --git a/include/mbedtls/x509_crt.h b/include/mbedtls/x509_crt.h index 6ac17af67d..6b96039597 100644 --- a/include/mbedtls/x509_crt.h +++ b/include/mbedtls/x509_crt.h @@ -209,10 +209,7 @@ mbedtls_x509_crt_profile; "The CRL is signed with an unacceptable PK alg (eg RSA vs ECDSA).") \ X509_CRT_ERROR_INFO(MBEDTLS_X509_BADCRL_BAD_KEY, \ "MBEDTLS_X509_BADCRL_BAD_KEY", \ - "The CRL is signed with an unacceptable key (eg bad curve, RSA too short).") \ - X509_CRT_ERROR_INFO(MBEDTLS_X509_VERIFY_NOT_STARTED, \ - "MBEDTLS_X509_VERIFY_NOT_STARTED", \ - "No verification has yet been performed.") + "The CRL is signed with an unacceptable key (eg bad curve, RSA too short).") /** * Container for writing a certificate (CRT) diff --git a/library/ssl_tls.c b/library/ssl_tls.c index 0585a53ef0..eb015d20da 100644 --- a/library/ssl_tls.c +++ b/library/ssl_tls.c @@ -1048,8 +1048,8 @@ void mbedtls_ssl_transform_init(mbedtls_ssl_transform *transform) void mbedtls_ssl_session_init(mbedtls_ssl_session *session) { memset(session, 0, sizeof(mbedtls_ssl_session)); - /* Set verify_result to indicate failure by default. */ - session->verify_result = MBEDTLS_X509_VERIFY_NOT_STARTED; + /* Set verify_result to -1u to indicate 'result not available'. */ + session->verify_result = 0xFFFFFFFF; } MBEDTLS_CHECK_RETURN_CRITICAL diff --git a/tests/suites/test_suite_ssl.function b/tests/suites/test_suite_ssl.function index 86fac0078b..276f08f42c 100644 --- a/tests/suites/test_suite_ssl.function +++ b/tests/suites/test_suite_ssl.function @@ -6024,7 +6024,7 @@ void verify_result_without_handshake(void) uint32_t verify_result = mbedtls_ssl_get_verify_result(&ssl); - TEST_EQUAL(verify_result, MBEDTLS_X509_VERIFY_NOT_STARTED); + TEST_EQUAL(verify_result, 0xFFFFFFFF); exit: mbedtls_ssl_config_free(&conf); From 01ef42d5fec4639f9d720e182cf0a1fd746d918a Mon Sep 17 00:00:00 2001 From: David Horstmann Date: Thu, 5 Feb 2026 14:17:47 +0000 Subject: [PATCH 07/61] Initialize verify_result in session free Initialize the verify_result field in mbedtls_ssl_session_free(). Previously we were just zeroising the entire session object, which would yield a default 'success' value if the same object were reused. Test that this initialisation is actually happening by setting verify_result manually to zero and calling mbedtls_ssl_session_free() on the session before checking its value. Signed-off-by: David Horstmann --- library/ssl_tls.c | 3 +++ tests/suites/test_suite_ssl.function | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/library/ssl_tls.c b/library/ssl_tls.c index eb015d20da..a65740463c 100644 --- a/library/ssl_tls.c +++ b/library/ssl_tls.c @@ -5005,6 +5005,9 @@ void mbedtls_ssl_session_free(mbedtls_ssl_session *session) #endif mbedtls_platform_zeroize(session, sizeof(mbedtls_ssl_session)); + + /* Set verify_result to -1u to indicate 'result not available'. */ + session->verify_result = 0xFFFFFFFF; } #if defined(MBEDTLS_SSL_CONTEXT_SERIALIZATION) diff --git a/tests/suites/test_suite_ssl.function b/tests/suites/test_suite_ssl.function index 276f08f42c..552c06a7b9 100644 --- a/tests/suites/test_suite_ssl.function +++ b/tests/suites/test_suite_ssl.function @@ -6026,6 +6026,16 @@ void verify_result_without_handshake(void) TEST_EQUAL(verify_result, 0xFFFFFFFF); + /* Set the verify result manually and check that session_free resets it. */ + + /* Set the verify result to 0. */ + ssl.session_negotiate->verify_result = 0; + + mbedtls_ssl_session_free(ssl.session_negotiate); + + verify_result = mbedtls_ssl_get_verify_result(&ssl); + TEST_EQUAL(verify_result, 0xFFFFFFFF); + exit: mbedtls_ssl_config_free(&conf); mbedtls_ssl_free(&ssl); From 79b698088758c9a036475acacfc28c3bfead2836 Mon Sep 17 00:00:00 2001 From: David Horstmann Date: Mon, 16 Feb 2026 10:57:09 +0000 Subject: [PATCH 08/61] Set verify_result in non-verification cases When we are using PSK or when authmode == MBEDTLS_SSL_VERIFY_NONE, we intentionally do not verify the certificate. In these cases, do not keep verify_result at -1u but set it to MBEDTLS_X509_BADCERT_SKIP_VERIFY to indicate that no certificate verification took place. Signed-off-by: David Horstmann --- library/ssl_tls.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/ssl_tls.c b/library/ssl_tls.c index a65740463c..88c5b8a43c 100644 --- a/library/ssl_tls.c +++ b/library/ssl_tls.c @@ -2288,6 +2288,9 @@ int mbedtls_ssl_set_hs_psk(mbedtls_ssl_context *ssl, return MBEDTLS_ERR_SSL_HW_ACCEL_FAILED; } + /* Since we're not using a certificate, set verify_result to skipped */ + ssl->session_negotiate->verify_result = MBEDTLS_X509_BADCERT_SKIP_VERIFY; + /* Allow calling psa_destroy_key() on psk remove */ ssl->handshake->psk_opaque_is_internal = 1; return mbedtls_ssl_set_hs_psk_opaque(ssl, key); @@ -7934,6 +7937,7 @@ static int ssl_parse_certificate_coordinate(mbedtls_ssl_context *ssl, ssl->handshake->ciphersuite_info; if (!mbedtls_ssl_ciphersuite_uses_srv_cert(ciphersuite_info)) { + ssl->session_negotiate->verify_result = MBEDTLS_X509_BADCERT_SKIP_VERIFY; return SSL_CERTIFICATE_SKIP; } @@ -9878,6 +9882,7 @@ int mbedtls_ssl_verify_certificate(mbedtls_ssl_context *ssl, void *rs_ctx) { if (authmode == MBEDTLS_SSL_VERIFY_NONE) { + ssl->session_negotiate->verify_result = MBEDTLS_X509_BADCERT_SKIP_VERIFY; return 0; } From c0faf73be6c4a23f1af5ddf8bce0e07916587160 Mon Sep 17 00:00:00 2001 From: David Horstmann Date: Mon, 16 Feb 2026 16:18:01 +0000 Subject: [PATCH 09/61] Reword ChangeLog entry We do not return failure, but return -1u which is documented as a value that indicates that the result is not available. Signed-off-by: David Horstmann --- ChangeLog.d/verify-result-default-value.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ChangeLog.d/verify-result-default-value.txt b/ChangeLog.d/verify-result-default-value.txt index d85dfe2670..2cf3f0c21b 100644 --- a/ChangeLog.d/verify-result-default-value.txt +++ b/ChangeLog.d/verify-result-default-value.txt @@ -1,5 +1,5 @@ Changes * Harden mbedtls_ssl_get_verify_result() against misuse. - Return failure if the handshake has not yet been attempted. Previously - the result of verification was zero-initialized so the function would - return 0 (indicating success). + If the handshake has not yet been attempted, return -1u to indicate + that the result is not available. Previously the result of verification + was zero-initialized so the function would return 0 (indicating success). From d179019ec22c4a29db9d9f15ad086d37b71e0bf7 Mon Sep 17 00:00:00 2001 From: David Horstmann Date: Tue, 17 Feb 2026 14:41:59 +0000 Subject: [PATCH 10/61] Set verify_result to 0 when not checking certs When we are using PSK or authmode is MBEDTLS_SSL_VERIFY_NONE, set verify_result to 0 rather than MBEDTLS_X509_BADCERT_SKIP_VERIFY. This is specific to the 3.6 LTS, to preserve the previous behaviour of the library in these cases, which was determined by the default value of verify_result being 0. Signed-off-by: David Horstmann --- library/ssl_tls.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/ssl_tls.c b/library/ssl_tls.c index 88c5b8a43c..ca3be8b50b 100644 --- a/library/ssl_tls.c +++ b/library/ssl_tls.c @@ -2288,8 +2288,8 @@ int mbedtls_ssl_set_hs_psk(mbedtls_ssl_context *ssl, return MBEDTLS_ERR_SSL_HW_ACCEL_FAILED; } - /* Since we're not using a certificate, set verify_result to skipped */ - ssl->session_negotiate->verify_result = MBEDTLS_X509_BADCERT_SKIP_VERIFY; + /* Since we're not using a certificate, set verify_result to success */ + ssl->session_negotiate->verify_result = 0; /* Allow calling psa_destroy_key() on psk remove */ ssl->handshake->psk_opaque_is_internal = 1; @@ -7937,7 +7937,7 @@ static int ssl_parse_certificate_coordinate(mbedtls_ssl_context *ssl, ssl->handshake->ciphersuite_info; if (!mbedtls_ssl_ciphersuite_uses_srv_cert(ciphersuite_info)) { - ssl->session_negotiate->verify_result = MBEDTLS_X509_BADCERT_SKIP_VERIFY; + ssl->session_negotiate->verify_result = 0; return SSL_CERTIFICATE_SKIP; } @@ -9882,7 +9882,7 @@ int mbedtls_ssl_verify_certificate(mbedtls_ssl_context *ssl, void *rs_ctx) { if (authmode == MBEDTLS_SSL_VERIFY_NONE) { - ssl->session_negotiate->verify_result = MBEDTLS_X509_BADCERT_SKIP_VERIFY; + ssl->session_negotiate->verify_result = 0; return 0; } From 4d14271515d1c76e5ec70792ce0e53b90653cb82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Tue, 10 Feb 2026 10:03:57 +0100 Subject: [PATCH 11/61] PK: avoid large stack buffer in to/from PSA functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit But still retain the ability to function without a heap when only ECC is enabled. Signed-off-by: Manuel Pégourié-Gonnard --- library/pk.c | 64 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/library/pk.c b/library/pk.c index 51f0c24088..61d333c8f6 100644 --- a/library/pk.c +++ b/library/pk.c @@ -35,6 +35,15 @@ #include #include +#if defined(MBEDTLS_RSA_C) +#define PK_HAVE_KEYS_LARGER_THAN_ECC +#endif + +#if defined(PK_HAVE_KEYS_LARGER_THAN_ECC) +#include "mbedtls/platform.h" // for calloc/free +#endif + + /* * Initialise a mbedtls_pk_context */ @@ -589,16 +598,36 @@ static psa_status_t export_import_into_psa(mbedtls_svc_key_id_t old_key_id, const psa_key_attributes_t *attributes, mbedtls_svc_key_id_t *new_key_id) { - unsigned char key_buffer[PSA_EXPORT_KEY_PAIR_MAX_SIZE]; +#if defined(PK_HAVE_KEYS_LARGER_THAN_ECC) + unsigned char *key_buffer = NULL; + size_t key_buffer_size = 0; +#else + unsigned char key_buffer[MBEDTLS_PSA_MAX_EC_KEY_PAIR_LENGTH]; + const size_t key_buffer_size = sizeof(key_buffer); +#endif size_t key_length = 0; + +#if defined(PK_HAVE_KEYS_LARGER_THAN_ECC) + key_buffer_size = PSA_EXPORT_KEY_PAIR_MAX_SIZE; + key_buffer = mbedtls_calloc(1, key_buffer_size); + if (key_buffer == NULL) { + return MBEDTLS_ERR_PK_ALLOC_FAILED; + } +#endif + psa_status_t status = psa_export_key(old_key_id, - key_buffer, sizeof(key_buffer), + key_buffer, key_buffer_size, &key_length); if (status != PSA_SUCCESS) { - return status; + goto cleanup; } status = psa_import_key(attributes, key_buffer, key_length, new_key_id); mbedtls_platform_zeroize(key_buffer, key_length); + +cleanup: +#if defined(PK_HAVE_KEYS_LARGER_THAN_ECC) + mbedtls_free(key_buffer); +#endif return status; } @@ -865,8 +894,13 @@ static int copy_from_psa(mbedtls_svc_key_id_t key_id, psa_key_attributes_t key_attr = PSA_KEY_ATTRIBUTES_INIT; psa_key_type_t key_type; size_t key_bits; - /* Use a buffer size large enough to contain either a key pair or public key. */ - unsigned char exp_key[PSA_EXPORT_KEY_PAIR_OR_PUBLIC_MAX_SIZE]; +#if defined(PK_HAVE_KEYS_LARGER_THAN_ECC) + unsigned char *exp_key = NULL; + size_t exp_key_size = 0; +#else + unsigned char exp_key[MBEDTLS_PSA_MAX_EC_KEY_PAIR_LENGTH]; + const size_t exp_key_size = sizeof(exp_key); +#endif size_t exp_key_len; int ret = MBEDTLS_ERR_PK_BAD_INPUT_DATA; @@ -879,10 +913,18 @@ static int copy_from_psa(mbedtls_svc_key_id_t key_id, return MBEDTLS_ERR_PK_BAD_INPUT_DATA; } +#if defined(PK_HAVE_KEYS_LARGER_THAN_ECC) + exp_key_size = PSA_EXPORT_KEY_PAIR_MAX_SIZE; + exp_key = mbedtls_calloc(1, exp_key_size); + if (exp_key == NULL) { + return MBEDTLS_ERR_PK_ALLOC_FAILED; + } +#endif + if (public_only) { - status = psa_export_public_key(key_id, exp_key, sizeof(exp_key), &exp_key_len); + status = psa_export_public_key(key_id, exp_key, exp_key_size, &exp_key_len); } else { - status = psa_export_key(key_id, exp_key, sizeof(exp_key), &exp_key_len); + status = psa_export_key(key_id, exp_key, exp_key_size, &exp_key_len); } if (status != PSA_SUCCESS) { ret = PSA_PK_TO_MBEDTLS_ERR(status); @@ -964,12 +1006,16 @@ static int copy_from_psa(mbedtls_svc_key_id_t key_id, #endif /* MBEDTLS_PK_HAVE_ECC_KEYS */ { (void) key_bits; - return MBEDTLS_ERR_PK_BAD_INPUT_DATA; + ret = MBEDTLS_ERR_PK_BAD_INPUT_DATA; + goto exit; } exit: + mbedtls_platform_zeroize(exp_key, exp_key_size); +#if defined(PK_HAVE_KEYS_LARGER_THAN_ECC) + mbedtls_free(exp_key); +#endif psa_reset_key_attributes(&key_attr); - mbedtls_platform_zeroize(exp_key, sizeof(exp_key)); return ret; } From eb8289d072b075e022a059e3dc02ec2839170f31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Tue, 10 Feb 2026 10:26:45 +0100 Subject: [PATCH 12/61] PK: adjust heap buffer size for key type+bits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/pk.c | 48 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/library/pk.c b/library/pk.c index 61d333c8f6..f4d1123657 100644 --- a/library/pk.c +++ b/library/pk.c @@ -593,8 +593,39 @@ int mbedtls_pk_get_psa_attributes(const mbedtls_pk_context *pk, return 0; } +#if defined(MBEDTLS_PSA_CRYPTO_CLIENT) +#if defined(PK_HAVE_KEYS_LARGER_THAN_ECC) +static size_t pk_export_max_size(psa_key_type_t key_type, size_t bits) +{ +#if defined(PSA_WANT_KEY_TYPE_ECC_PUBLIC_KEY) + if (PSA_KEY_TYPE_IS_ECC_PUBLIC_KEY(key_type)) { + return PSA_KEY_EXPORT_ECC_PUBLIC_KEY_MAX_SIZE(bits); + } +#endif +#if defined(PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_BASIC) + if (PSA_KEY_TYPE_IS_ECC_KEY_PAIR(key_type)) { + return PSA_KEY_EXPORT_ECC_KEY_PAIR_MAX_SIZE(bits); + } +#endif +#if defined(PSA_WANT_KEY_TYPE_RSA_PUBLIC_KEY) + if (key_type == PSA_KEY_TYPE_RSA_PUBLIC_KEY) { + return PSA_KEY_EXPORT_RSA_PUBLIC_KEY_MAX_SIZE(bits); + } +#endif +#if defined(PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_BASIC) + if (key_type == PSA_KEY_TYPE_RSA_KEY_PAIR) { + return PSA_KEY_EXPORT_RSA_KEY_PAIR_MAX_SIZE(bits); + } +#endif + /* failsafe */ + return PSA_EXPORT_KEY_PAIR_MAX_SIZE; +} +#endif /* PK_HAVE_KEYS_LARGER_THAN_ECC */ +#endif /* MBEDTLS_PSA_CRYPTO_CLIENT */ + #if defined(MBEDTLS_PK_USE_PSA_EC_DATA) || defined(MBEDTLS_USE_PSA_CRYPTO) static psa_status_t export_import_into_psa(mbedtls_svc_key_id_t old_key_id, + psa_key_type_t old_type, size_t old_bits, const psa_key_attributes_t *attributes, mbedtls_svc_key_id_t *new_key_id) { @@ -608,11 +639,14 @@ static psa_status_t export_import_into_psa(mbedtls_svc_key_id_t old_key_id, size_t key_length = 0; #if defined(PK_HAVE_KEYS_LARGER_THAN_ECC) - key_buffer_size = PSA_EXPORT_KEY_PAIR_MAX_SIZE; + key_buffer_size = pk_export_max_size(old_type, old_bits); key_buffer = mbedtls_calloc(1, key_buffer_size); if (key_buffer == NULL) { return MBEDTLS_ERR_PK_ALLOC_FAILED; } +#else + (void) old_type; + (void) old_bits; #endif psa_status_t status = psa_export_key(old_key_id, @@ -657,11 +691,13 @@ static int copy_into_psa(mbedtls_svc_key_id_t old_key_id, return MBEDTLS_ERR_PK_BAD_INPUT_DATA; } psa_key_type_t old_type = psa_get_key_type(&old_attributes); + size_t old_bits = psa_get_key_bits(&old_attributes); psa_reset_key_attributes(&old_attributes); if (old_type != psa_get_key_type(attributes)) { return MBEDTLS_ERR_PK_TYPE_MISMATCH; } - status = export_import_into_psa(old_key_id, attributes, new_key_id); + status = export_import_into_psa(old_key_id, old_type, old_bits, + attributes, new_key_id); } return PSA_PK_TO_MBEDTLS_ERR(status); } @@ -913,8 +949,14 @@ static int copy_from_psa(mbedtls_svc_key_id_t key_id, return MBEDTLS_ERR_PK_BAD_INPUT_DATA; } + key_type = psa_get_key_type(&key_attr); + if (public_only) { + key_type = PSA_KEY_TYPE_PUBLIC_KEY_OF_KEY_PAIR(key_type); + } + key_bits = psa_get_key_bits(&key_attr); + #if defined(PK_HAVE_KEYS_LARGER_THAN_ECC) - exp_key_size = PSA_EXPORT_KEY_PAIR_MAX_SIZE; + exp_key_size = pk_export_max_size(key_type, key_bits); exp_key = mbedtls_calloc(1, exp_key_size); if (exp_key == NULL) { return MBEDTLS_ERR_PK_ALLOC_FAILED; From 210c61336182ce3e48ca0050123fd7611e353039 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Wed, 11 Feb 2026 13:00:06 +0100 Subject: [PATCH 13/61] PK: fix stack buffer size for ECC keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes 2 out of the 3 tests cases that were failing in test_suite_pk. The last failure will be adressed in the next commit. Signed-off-by: Manuel Pégourié-Gonnard --- library/pk.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/library/pk.c b/library/pk.c index f4d1123657..e33a497b17 100644 --- a/library/pk.c +++ b/library/pk.c @@ -43,6 +43,12 @@ #include "mbedtls/platform.h" // for calloc/free #endif +/* We know for ECC, pubkey are longer than privkeys, but double check */ +#define PK_MAX_EC_KEYPAIR_OR_PUBKEY_LENGTH MBEDTLS_PSA_MAX_EC_PUBKEY_LENGTH +#if MBEDTLS_PSA_MAX_EC_KEY_PAIR_LENGTH > PK_MAX_EC_KEYPAIR_OR_PUBKEY_LENGTH +#undef PK_MAX_EC_KEYPAIR_OR_PUBKEY_LENGTH +#define PK_MAX_EC_KEYPAIR_OR_PUBKEY_LENGTH MBEDTLS_PSA_MAX_EC_KEY_PAIR_LENGTH +#endif /* * Initialise a mbedtls_pk_context @@ -633,7 +639,7 @@ static psa_status_t export_import_into_psa(mbedtls_svc_key_id_t old_key_id, unsigned char *key_buffer = NULL; size_t key_buffer_size = 0; #else - unsigned char key_buffer[MBEDTLS_PSA_MAX_EC_KEY_PAIR_LENGTH]; + unsigned char key_buffer[PK_MAX_EC_KEYPAIR_OR_PUBKEY_LENGTH]; const size_t key_buffer_size = sizeof(key_buffer); #endif size_t key_length = 0; @@ -934,7 +940,7 @@ static int copy_from_psa(mbedtls_svc_key_id_t key_id, unsigned char *exp_key = NULL; size_t exp_key_size = 0; #else - unsigned char exp_key[MBEDTLS_PSA_MAX_EC_KEY_PAIR_LENGTH]; + unsigned char exp_key[PK_MAX_EC_KEYPAIR_OR_PUBKEY_LENGTH]; const size_t exp_key_size = sizeof(exp_key); #endif size_t exp_key_len; From 00fd34ef18049deaa5f6befc942ad51aaa87cd98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 12 Feb 2026 10:07:57 +0100 Subject: [PATCH 14/61] PK: return helpful error on API misuse MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes test_suite_pk pass again, but beyond that I think it's the right thing to do. Signed-off-by: Manuel Pégourié-Gonnard --- library/pk.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/library/pk.c b/library/pk.c index e33a497b17..c580c44a1b 100644 --- a/library/pk.c +++ b/library/pk.c @@ -967,6 +967,13 @@ static int copy_from_psa(mbedtls_svc_key_id_t key_id, if (exp_key == NULL) { return MBEDTLS_ERR_PK_ALLOC_FAILED; } +#else + /* In case we're passed non-ECC key (API misuse), return a sensible error + * now. Otherwise we might get BUFFER_TOO_SMALL when exporting below, which + * is unlikely to be helpful to the user as the buffer is internal. */ + if (!PSA_KEY_TYPE_IS_ECC(key_type)) { + return MBEDTLS_ERR_PK_BAD_INPUT_DATA; + } #endif if (public_only) { From 1cc0e98f683f9496c76e5d2d5e2dc6941b2edbb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 12 Feb 2026 11:04:00 +0100 Subject: [PATCH 15/61] PSA: add and use PSA_EXPORT_ASYMMETRIC_KEY_MAX_SIZE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- include/psa/crypto_extra.h | 4 ++-- include/psa/crypto_sizes.h | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/include/psa/crypto_extra.h b/include/psa/crypto_extra.h index 89a38a8054..9324b48895 100644 --- a/include/psa/crypto_extra.h +++ b/include/psa/crypto_extra.h @@ -39,9 +39,9 @@ extern "C" { #define MBEDTLS_PSA_STATIC_KEY_SLOT_BUFFER_SIZE 1 -#if PSA_EXPORT_KEY_PAIR_OR_PUBLIC_MAX_SIZE > MBEDTLS_PSA_STATIC_KEY_SLOT_BUFFER_SIZE +#if PSA_EXPORT_ASYMMETRIC_KEY_MAX_SIZE > MBEDTLS_PSA_STATIC_KEY_SLOT_BUFFER_SIZE #undef MBEDTLS_PSA_STATIC_KEY_SLOT_BUFFER_SIZE -#define MBEDTLS_PSA_STATIC_KEY_SLOT_BUFFER_SIZE PSA_EXPORT_KEY_PAIR_OR_PUBLIC_MAX_SIZE +#define MBEDTLS_PSA_STATIC_KEY_SLOT_BUFFER_SIZE PSA_EXPORT_ASYMMETRIC_KEY_MAX_SIZE #endif /* This covers ciphers, AEADs and CMAC. */ diff --git a/include/psa/crypto_sizes.h b/include/psa/crypto_sizes.h index 87b8c39fa6..69fc7d79f2 100644 --- a/include/psa/crypto_sizes.h +++ b/include/psa/crypto_sizes.h @@ -1038,10 +1038,16 @@ PSA_KEY_EXPORT_FFDH_PUBLIC_KEY_MAX_SIZE(PSA_VENDOR_FFDH_MAX_KEY_BITS) #endif -#define PSA_EXPORT_KEY_PAIR_OR_PUBLIC_MAX_SIZE \ +/* This is the name that was standardized in PSA Crypto v1.3 */ +#define PSA_EXPORT_ASYMMETRIC_KEY_MAX_SIZE \ ((PSA_EXPORT_KEY_PAIR_MAX_SIZE > PSA_EXPORT_PUBLIC_KEY_MAX_SIZE) ? \ PSA_EXPORT_KEY_PAIR_MAX_SIZE : PSA_EXPORT_PUBLIC_KEY_MAX_SIZE) +/* This is our old custom name from before it was in the spec, + * keep it around in case users were relying on it. */ +#define PSA_EXPORT_KEY_PAIR_OR_PUBLIC_MAX_SIZE \ + PSA_EXPORT_ASYMMETRIC_KEY_MAX_SIZE + /** Sufficient output buffer size for psa_raw_key_agreement(). * * This macro returns a compile-time constant if its arguments are From 70454dc75b3670b512d48dcd95904581ab06c10e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 12 Feb 2026 11:45:49 +0100 Subject: [PATCH 16/61] PK: rework failure case of internal size function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/pk.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/library/pk.c b/library/pk.c index c580c44a1b..5047fa3b30 100644 --- a/library/pk.c +++ b/library/pk.c @@ -601,6 +601,15 @@ int mbedtls_pk_get_psa_attributes(const mbedtls_pk_context *pk, #if defined(MBEDTLS_PSA_CRYPTO_CLIENT) #if defined(PK_HAVE_KEYS_LARGER_THAN_ECC) +/* + * Size needed to export a key of a type supported by PK. + * + * Compared to PSA_EXPORT_KEY_OUTPUT_SIZE() this is better for code size: + * - using a macro in multiple places results in multiple copies of the code; + * - this function only handles key types supported in PK. + * + * Return 0 on unexpected types. Callers need to check for that value. + */ static size_t pk_export_max_size(psa_key_type_t key_type, size_t bits) { #if defined(PSA_WANT_KEY_TYPE_ECC_PUBLIC_KEY) @@ -623,8 +632,7 @@ static size_t pk_export_max_size(psa_key_type_t key_type, size_t bits) return PSA_KEY_EXPORT_RSA_KEY_PAIR_MAX_SIZE(bits); } #endif - /* failsafe */ - return PSA_EXPORT_KEY_PAIR_MAX_SIZE; + return 0; } #endif /* PK_HAVE_KEYS_LARGER_THAN_ECC */ #endif /* MBEDTLS_PSA_CRYPTO_CLIENT */ @@ -644,6 +652,7 @@ static psa_status_t export_import_into_psa(mbedtls_svc_key_id_t old_key_id, #endif size_t key_length = 0; + /* We are exporting from a PK object, so we know key type is valid for PK */ #if defined(PK_HAVE_KEYS_LARGER_THAN_ECC) key_buffer_size = pk_export_max_size(old_type, old_bits); key_buffer = mbedtls_calloc(1, key_buffer_size); @@ -963,6 +972,9 @@ static int copy_from_psa(mbedtls_svc_key_id_t key_id, #if defined(PK_HAVE_KEYS_LARGER_THAN_ECC) exp_key_size = pk_export_max_size(key_type, key_bits); + if (exp_key_size == 0) { + return MBEDTLS_ERR_PK_BAD_INPUT_DATA; + } exp_key = mbedtls_calloc(1, exp_key_size); if (exp_key == NULL) { return MBEDTLS_ERR_PK_ALLOC_FAILED; From 925341971dba653a811055af163cb9e75d5b0f1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 12 Feb 2026 11:52:19 +0100 Subject: [PATCH 17/61] PK: validate type upfront when copying from PSA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The type was validated near the end of the function when importing, but if makes more sense to validate upfront before we possibly allocate a buffer, export the key to it etc. This also guarantees a sensible error value without requiring a special case when exporting on the stack. Signed-off-by: Manuel Pégourié-Gonnard --- library/pk.c | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/library/pk.c b/library/pk.c index 5047fa3b30..f9b05a3a51 100644 --- a/library/pk.c +++ b/library/pk.c @@ -608,7 +608,8 @@ int mbedtls_pk_get_psa_attributes(const mbedtls_pk_context *pk, * - using a macro in multiple places results in multiple copies of the code; * - this function only handles key types supported in PK. * - * Return 0 on unexpected types. Callers need to check for that value. + * WARNING: callers need to ensure the type is supported before calling this + * function, possibly by calling is_valid_for_pk(). */ static size_t pk_export_max_size(psa_key_type_t key_type, size_t bits) { @@ -937,6 +938,22 @@ int mbedtls_pk_import_into_psa(const mbedtls_pk_context *pk, } } +static int is_valid_for_pk(psa_key_type_t key_type) +{ +#if defined(MBEDTLS_PK_HAVE_ECC_KEYS) + if (PSA_KEY_TYPE_IS_ECC(key_type)) { + return 1; + } +#endif +#if defined(MBEDTLS_RSA_C) + if (key_type == PSA_KEY_TYPE_RSA_PUBLIC_KEY || + key_type == PSA_KEY_TYPE_RSA_KEY_PAIR) { + return 1; + } +#endif + return 0; +} + static int copy_from_psa(mbedtls_svc_key_id_t key_id, mbedtls_pk_context *pk, int public_only) @@ -965,6 +982,10 @@ static int copy_from_psa(mbedtls_svc_key_id_t key_id, } key_type = psa_get_key_type(&key_attr); + if (!is_valid_for_pk(key_type)) { + return MBEDTLS_ERR_PK_BAD_INPUT_DATA; + } + if (public_only) { key_type = PSA_KEY_TYPE_PUBLIC_KEY_OF_KEY_PAIR(key_type); } @@ -972,20 +993,10 @@ static int copy_from_psa(mbedtls_svc_key_id_t key_id, #if defined(PK_HAVE_KEYS_LARGER_THAN_ECC) exp_key_size = pk_export_max_size(key_type, key_bits); - if (exp_key_size == 0) { - return MBEDTLS_ERR_PK_BAD_INPUT_DATA; - } exp_key = mbedtls_calloc(1, exp_key_size); if (exp_key == NULL) { return MBEDTLS_ERR_PK_ALLOC_FAILED; } -#else - /* In case we're passed non-ECC key (API misuse), return a sensible error - * now. Otherwise we might get BUFFER_TOO_SMALL when exporting below, which - * is unlikely to be helpful to the user as the buffer is internal. */ - if (!PSA_KEY_TYPE_IS_ECC(key_type)) { - return MBEDTLS_ERR_PK_BAD_INPUT_DATA; - } #endif if (public_only) { From 56771d12b80705deeed4fc52538c98cbdc18041a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 12 Feb 2026 12:06:54 +0100 Subject: [PATCH 18/61] PK: improve naming & doc of internal macro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/pk.c | 47 ++++++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/library/pk.c b/library/pk.c index f9b05a3a51..55e14bf6fc 100644 --- a/library/pk.c +++ b/library/pk.c @@ -35,20 +35,29 @@ #include #include -#if defined(MBEDTLS_RSA_C) -#define PK_HAVE_KEYS_LARGER_THAN_ECC +/* + * We're trying to statisfy two kinds of users: + * - those who don't want to use the heap; + * - those who can't afford large stack buffers. + * + * The current compromise is that if ECC is the only key type supported in PK, + * then we export keys on the stack, and otherwise we use the heap. + */ +#if !defined(MBEDTLS_RSA_C) +#define PK_EXPORT_KEYS_ON_THE_STACK #endif -#if defined(PK_HAVE_KEYS_LARGER_THAN_ECC) +#if defined(PK_EXPORT_KEYS_ON_THE_STACK) +/* We know for ECC, pubkey are longer than privkeys, but double check */ +#define PK_EXPORT_KEY_STACK_BUFFER_SIZE MBEDTLS_PSA_MAX_EC_PUBKEY_LENGTH +#if MBEDTLS_PSA_MAX_EC_KEY_PAIR_LENGTH > PK_EXPORT_KEY_STACK_BUFFER_SIZE +#undef PK_EXPORT_KEY_STACK_BUFFER_SIZE +#define PK_EXPORT_KEY_STACK_BUFFER_SIZE MBEDTLS_PSA_MAX_EC_KEY_PAIR_LENGTH +#endif +#else #include "mbedtls/platform.h" // for calloc/free #endif -/* We know for ECC, pubkey are longer than privkeys, but double check */ -#define PK_MAX_EC_KEYPAIR_OR_PUBKEY_LENGTH MBEDTLS_PSA_MAX_EC_PUBKEY_LENGTH -#if MBEDTLS_PSA_MAX_EC_KEY_PAIR_LENGTH > PK_MAX_EC_KEYPAIR_OR_PUBKEY_LENGTH -#undef PK_MAX_EC_KEYPAIR_OR_PUBKEY_LENGTH -#define PK_MAX_EC_KEYPAIR_OR_PUBKEY_LENGTH MBEDTLS_PSA_MAX_EC_KEY_PAIR_LENGTH -#endif /* * Initialise a mbedtls_pk_context @@ -600,7 +609,7 @@ int mbedtls_pk_get_psa_attributes(const mbedtls_pk_context *pk, } #if defined(MBEDTLS_PSA_CRYPTO_CLIENT) -#if defined(PK_HAVE_KEYS_LARGER_THAN_ECC) +#if !defined(PK_EXPORT_KEYS_ON_THE_STACK) /* * Size needed to export a key of a type supported by PK. * @@ -635,7 +644,7 @@ static size_t pk_export_max_size(psa_key_type_t key_type, size_t bits) #endif return 0; } -#endif /* PK_HAVE_KEYS_LARGER_THAN_ECC */ +#endif /* PK_EXPORT_KEYS_ON_THE_STACK */ #endif /* MBEDTLS_PSA_CRYPTO_CLIENT */ #if defined(MBEDTLS_PK_USE_PSA_EC_DATA) || defined(MBEDTLS_USE_PSA_CRYPTO) @@ -644,17 +653,17 @@ static psa_status_t export_import_into_psa(mbedtls_svc_key_id_t old_key_id, const psa_key_attributes_t *attributes, mbedtls_svc_key_id_t *new_key_id) { -#if defined(PK_HAVE_KEYS_LARGER_THAN_ECC) +#if !defined(PK_EXPORT_KEYS_ON_THE_STACK) unsigned char *key_buffer = NULL; size_t key_buffer_size = 0; #else - unsigned char key_buffer[PK_MAX_EC_KEYPAIR_OR_PUBKEY_LENGTH]; + unsigned char key_buffer[PK_EXPORT_KEY_STACK_BUFFER_SIZE]; const size_t key_buffer_size = sizeof(key_buffer); #endif size_t key_length = 0; /* We are exporting from a PK object, so we know key type is valid for PK */ -#if defined(PK_HAVE_KEYS_LARGER_THAN_ECC) +#if !defined(PK_EXPORT_KEYS_ON_THE_STACK) key_buffer_size = pk_export_max_size(old_type, old_bits); key_buffer = mbedtls_calloc(1, key_buffer_size); if (key_buffer == NULL) { @@ -675,7 +684,7 @@ static psa_status_t export_import_into_psa(mbedtls_svc_key_id_t old_key_id, mbedtls_platform_zeroize(key_buffer, key_length); cleanup: -#if defined(PK_HAVE_KEYS_LARGER_THAN_ECC) +#if !defined(PK_EXPORT_KEYS_ON_THE_STACK) mbedtls_free(key_buffer); #endif return status; @@ -962,11 +971,11 @@ static int copy_from_psa(mbedtls_svc_key_id_t key_id, psa_key_attributes_t key_attr = PSA_KEY_ATTRIBUTES_INIT; psa_key_type_t key_type; size_t key_bits; -#if defined(PK_HAVE_KEYS_LARGER_THAN_ECC) +#if !defined(PK_EXPORT_KEYS_ON_THE_STACK) unsigned char *exp_key = NULL; size_t exp_key_size = 0; #else - unsigned char exp_key[PK_MAX_EC_KEYPAIR_OR_PUBKEY_LENGTH]; + unsigned char exp_key[PK_EXPORT_KEY_STACK_BUFFER_SIZE]; const size_t exp_key_size = sizeof(exp_key); #endif size_t exp_key_len; @@ -991,7 +1000,7 @@ static int copy_from_psa(mbedtls_svc_key_id_t key_id, } key_bits = psa_get_key_bits(&key_attr); -#if defined(PK_HAVE_KEYS_LARGER_THAN_ECC) +#if !defined(PK_EXPORT_KEYS_ON_THE_STACK) exp_key_size = pk_export_max_size(key_type, key_bits); exp_key = mbedtls_calloc(1, exp_key_size); if (exp_key == NULL) { @@ -1090,7 +1099,7 @@ static int copy_from_psa(mbedtls_svc_key_id_t key_id, exit: mbedtls_platform_zeroize(exp_key, exp_key_size); -#if defined(PK_HAVE_KEYS_LARGER_THAN_ECC) +#if !defined(PK_EXPORT_KEYS_ON_THE_STACK) mbedtls_free(exp_key); #endif psa_reset_key_attributes(&key_attr); From 6b90afb2bba12487253e0fd683ac4b95aeeef08f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 12 Feb 2026 12:20:13 +0100 Subject: [PATCH 19/61] PK: ensure we test with keys on the stack or heap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/pk.c | 21 +------------------ library/pk_internal.h | 21 +++++++++++++++++++ ...test_suite_config.crypto_combinations.data | 8 +++++++ tests/suites/test_suite_config.function | 2 +- 4 files changed, 31 insertions(+), 21 deletions(-) diff --git a/library/pk.c b/library/pk.c index 55e14bf6fc..e34cba8937 100644 --- a/library/pk.c +++ b/library/pk.c @@ -35,26 +35,7 @@ #include #include -/* - * We're trying to statisfy two kinds of users: - * - those who don't want to use the heap; - * - those who can't afford large stack buffers. - * - * The current compromise is that if ECC is the only key type supported in PK, - * then we export keys on the stack, and otherwise we use the heap. - */ -#if !defined(MBEDTLS_RSA_C) -#define PK_EXPORT_KEYS_ON_THE_STACK -#endif - -#if defined(PK_EXPORT_KEYS_ON_THE_STACK) -/* We know for ECC, pubkey are longer than privkeys, but double check */ -#define PK_EXPORT_KEY_STACK_BUFFER_SIZE MBEDTLS_PSA_MAX_EC_PUBKEY_LENGTH -#if MBEDTLS_PSA_MAX_EC_KEY_PAIR_LENGTH > PK_EXPORT_KEY_STACK_BUFFER_SIZE -#undef PK_EXPORT_KEY_STACK_BUFFER_SIZE -#define PK_EXPORT_KEY_STACK_BUFFER_SIZE MBEDTLS_PSA_MAX_EC_KEY_PAIR_LENGTH -#endif -#else +#if !defined(PK_EXPORT_KEYS_ON_THE_STACK) #include "mbedtls/platform.h" // for calloc/free #endif diff --git a/library/pk_internal.h b/library/pk_internal.h index e86a3a09d2..4d6081cbe2 100644 --- a/library/pk_internal.h +++ b/library/pk_internal.h @@ -44,6 +44,27 @@ #define PEM_BEGIN_ENCRYPTED_PRIVATE_KEY_PKCS8 "-----BEGIN ENCRYPTED PRIVATE KEY-----" #define PEM_END_ENCRYPTED_PRIVATE_KEY_PKCS8 "-----END ENCRYPTED PRIVATE KEY-----" +/* + * We're trying to statisfy two kinds of users: + * - those who don't want to use the heap; + * - those who can't afford large stack buffers. + * + * The current compromise is that if ECC is the only key type supported in PK, + * then we export keys on the stack, and otherwise we use the heap. + */ +#if !defined(MBEDTLS_RSA_C) +#define PK_EXPORT_KEYS_ON_THE_STACK +#endif + +#if defined(PK_EXPORT_KEYS_ON_THE_STACK) +/* We know for ECC, pubkey are longer than privkeys, but double check */ +#define PK_EXPORT_KEY_STACK_BUFFER_SIZE MBEDTLS_PSA_MAX_EC_PUBKEY_LENGTH +#if MBEDTLS_PSA_MAX_EC_KEY_PAIR_LENGTH > PK_EXPORT_KEY_STACK_BUFFER_SIZE +#undef PK_EXPORT_KEY_STACK_BUFFER_SIZE +#define PK_EXPORT_KEY_STACK_BUFFER_SIZE MBEDTLS_PSA_MAX_EC_KEY_PAIR_LENGTH +#endif +#endif + #if defined(MBEDTLS_PK_HAVE_ECC_KEYS) && !defined(MBEDTLS_PK_USE_PSA_EC_DATA) /** * Public function mbedtls_pk_ec() can be used to get direct access to the diff --git a/tests/suites/test_suite_config.crypto_combinations.data b/tests/suites/test_suite_config.crypto_combinations.data index 9cc09ec895..0f8585a519 100644 --- a/tests/suites/test_suite_config.crypto_combinations.data +++ b/tests/suites/test_suite_config.crypto_combinations.data @@ -12,3 +12,11 @@ pass: Config: ECC: Montgomery curves only depends_on:!MBEDTLS_ECP_SHORT_WEIERSTRASS_ENABLED:MBEDTLS_ECP_MONTGOMERY_ENABLED pass: + +Config: PK export keys on the stack (ECC is the only PK key type) +depends_on:PK_EXPORT_KEYS_ON_THE_STACK +pass: + +Config: PK export keys on the heap (ECC is not the only PK key type) +depends_on:!PK_EXPORT_KEYS_ON_THE_STACK +pass: diff --git a/tests/suites/test_suite_config.function b/tests/suites/test_suite_config.function index 9e9dd01990..8c68c0c5d3 100644 --- a/tests/suites/test_suite_config.function +++ b/tests/suites/test_suite_config.function @@ -1,5 +1,5 @@ /* BEGIN_HEADER */ - +#include "pk_internal.h" /* END_HEADER */ /* BEGIN_CASE */ From 4bebabb6878e19ea26ce64a34b7a330ff30c90da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 12 Feb 2026 12:46:15 +0100 Subject: [PATCH 20/61] PSA: improve PSA_EXPORT_KEY_OUTPUT_SIZE; PK: use it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- include/psa/crypto_sizes.h | 8 ++----- library/pk.c | 43 ++------------------------------------ 2 files changed, 4 insertions(+), 47 deletions(-) diff --git a/include/psa/crypto_sizes.h b/include/psa/crypto_sizes.h index 69fc7d79f2..1bf84de8c4 100644 --- a/include/psa/crypto_sizes.h +++ b/include/psa/crypto_sizes.h @@ -912,15 +912,11 @@ * If the parameters are not valid, the return value is unspecified. */ #define PSA_EXPORT_KEY_OUTPUT_SIZE(key_type, key_bits) \ - (PSA_KEY_TYPE_IS_UNSTRUCTURED(key_type) ? PSA_BITS_TO_BYTES(key_bits) : \ - PSA_KEY_TYPE_IS_DH(key_type) ? PSA_BITS_TO_BYTES(key_bits) : \ - (key_type) == PSA_KEY_TYPE_RSA_KEY_PAIR ? PSA_KEY_EXPORT_RSA_KEY_PAIR_MAX_SIZE(key_bits) : \ + ((key_type) == PSA_KEY_TYPE_RSA_KEY_PAIR ? PSA_KEY_EXPORT_RSA_KEY_PAIR_MAX_SIZE(key_bits) : \ (key_type) == PSA_KEY_TYPE_RSA_PUBLIC_KEY ? PSA_KEY_EXPORT_RSA_PUBLIC_KEY_MAX_SIZE(key_bits) : \ - (key_type) == PSA_KEY_TYPE_DSA_KEY_PAIR ? PSA_KEY_EXPORT_DSA_KEY_PAIR_MAX_SIZE(key_bits) : \ - (key_type) == PSA_KEY_TYPE_DSA_PUBLIC_KEY ? PSA_KEY_EXPORT_DSA_PUBLIC_KEY_MAX_SIZE(key_bits) : \ PSA_KEY_TYPE_IS_ECC_KEY_PAIR(key_type) ? PSA_KEY_EXPORT_ECC_KEY_PAIR_MAX_SIZE(key_bits) : \ PSA_KEY_TYPE_IS_ECC_PUBLIC_KEY(key_type) ? PSA_KEY_EXPORT_ECC_PUBLIC_KEY_MAX_SIZE(key_bits) : \ - 0u) + PSA_BITS_TO_BYTES(key_bits)) /*unstructured; FFDH public or private*/ /** Sufficient output buffer size for psa_export_public_key(). * diff --git a/library/pk.c b/library/pk.c index e34cba8937..b158547613 100644 --- a/library/pk.c +++ b/library/pk.c @@ -589,45 +589,6 @@ int mbedtls_pk_get_psa_attributes(const mbedtls_pk_context *pk, return 0; } -#if defined(MBEDTLS_PSA_CRYPTO_CLIENT) -#if !defined(PK_EXPORT_KEYS_ON_THE_STACK) -/* - * Size needed to export a key of a type supported by PK. - * - * Compared to PSA_EXPORT_KEY_OUTPUT_SIZE() this is better for code size: - * - using a macro in multiple places results in multiple copies of the code; - * - this function only handles key types supported in PK. - * - * WARNING: callers need to ensure the type is supported before calling this - * function, possibly by calling is_valid_for_pk(). - */ -static size_t pk_export_max_size(psa_key_type_t key_type, size_t bits) -{ -#if defined(PSA_WANT_KEY_TYPE_ECC_PUBLIC_KEY) - if (PSA_KEY_TYPE_IS_ECC_PUBLIC_KEY(key_type)) { - return PSA_KEY_EXPORT_ECC_PUBLIC_KEY_MAX_SIZE(bits); - } -#endif -#if defined(PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_BASIC) - if (PSA_KEY_TYPE_IS_ECC_KEY_PAIR(key_type)) { - return PSA_KEY_EXPORT_ECC_KEY_PAIR_MAX_SIZE(bits); - } -#endif -#if defined(PSA_WANT_KEY_TYPE_RSA_PUBLIC_KEY) - if (key_type == PSA_KEY_TYPE_RSA_PUBLIC_KEY) { - return PSA_KEY_EXPORT_RSA_PUBLIC_KEY_MAX_SIZE(bits); - } -#endif -#if defined(PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_BASIC) - if (key_type == PSA_KEY_TYPE_RSA_KEY_PAIR) { - return PSA_KEY_EXPORT_RSA_KEY_PAIR_MAX_SIZE(bits); - } -#endif - return 0; -} -#endif /* PK_EXPORT_KEYS_ON_THE_STACK */ -#endif /* MBEDTLS_PSA_CRYPTO_CLIENT */ - #if defined(MBEDTLS_PK_USE_PSA_EC_DATA) || defined(MBEDTLS_USE_PSA_CRYPTO) static psa_status_t export_import_into_psa(mbedtls_svc_key_id_t old_key_id, psa_key_type_t old_type, size_t old_bits, @@ -645,7 +606,7 @@ static psa_status_t export_import_into_psa(mbedtls_svc_key_id_t old_key_id, /* We are exporting from a PK object, so we know key type is valid for PK */ #if !defined(PK_EXPORT_KEYS_ON_THE_STACK) - key_buffer_size = pk_export_max_size(old_type, old_bits); + key_buffer_size = PSA_EXPORT_KEY_OUTPUT_SIZE(old_type, old_bits); key_buffer = mbedtls_calloc(1, key_buffer_size); if (key_buffer == NULL) { return MBEDTLS_ERR_PK_ALLOC_FAILED; @@ -982,7 +943,7 @@ static int copy_from_psa(mbedtls_svc_key_id_t key_id, key_bits = psa_get_key_bits(&key_attr); #if !defined(PK_EXPORT_KEYS_ON_THE_STACK) - exp_key_size = pk_export_max_size(key_type, key_bits); + exp_key_size = PSA_EXPORT_KEY_OUTPUT_SIZE(key_type, key_bits); exp_key = mbedtls_calloc(1, exp_key_size); if (exp_key == NULL) { return MBEDTLS_ERR_PK_ALLOC_FAILED; From 3a16bd53062e7a4cba107a9c1f55229b1b9530ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Thu, 19 Feb 2026 09:58:14 +0100 Subject: [PATCH 21/61] Add ChangeLog entry for PK large stack buffers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- ChangeLog.d/pk-large-stack-buffers.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 ChangeLog.d/pk-large-stack-buffers.txt diff --git a/ChangeLog.d/pk-large-stack-buffers.txt b/ChangeLog.d/pk-large-stack-buffers.txt new file mode 100644 index 0000000000..78a66a4519 --- /dev/null +++ b/ChangeLog.d/pk-large-stack-buffers.txt @@ -0,0 +1,7 @@ +Bugfix + * Some functions in PK were using large buffers (around 2KB in the default + configuration) on the stack, which was a problem in environments with a + small stack. Those buffers are now allocated on the heap, except in + configurations where ECC is the only supported key type in PK, making PK + still independent of the heap in such configurations (if the ECC driver + itself is not using the heap). Fixes #476. From ee2c3e819ff961f74c0aa04c6b891ef012ef3eb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Mon, 23 Feb 2026 09:34:49 +0100 Subject: [PATCH 22/61] Adjust guard for 3.6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In all.sh component test_psa_crypto_config_accel_rsa_crypto, where RSA is provided only by a drivers, we were using a stack buffer. However that was not correct, as "opaque" (PSA-held) RSA keys are still possible. This was pointed by failing test cases in test_suite_pk, such as "PSA import into PSA: opaque RSA, EXPORT (ok)". As usual with 3.6, we need more complicated pre-processor conditions. Signed-off-by: Manuel Pégourié-Gonnard --- library/pk_internal.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/library/pk_internal.h b/library/pk_internal.h index 4d6081cbe2..202f011ea3 100644 --- a/library/pk_internal.h +++ b/library/pk_internal.h @@ -51,8 +51,11 @@ * * The current compromise is that if ECC is the only key type supported in PK, * then we export keys on the stack, and otherwise we use the heap. + * + * RSA can either be used directly or indirectly via opaque keys if enabled. */ -#if !defined(MBEDTLS_RSA_C) +#if !defined(MBEDTLS_RSA_C) && \ + !(defined(MBEDTLS_USE_PSA_CRYPTO) && defined(PSA_WANT_KEY_TYPE_RSA_PUBLIC_KEY)) #define PK_EXPORT_KEYS_ON_THE_STACK #endif From a76d2129ba88fd796a8e1765040f574d669adf5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Mon, 23 Feb 2026 09:54:12 +0100 Subject: [PATCH 23/61] PK: avoid using a > 2kB stack buffer in RSA -> PSA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Here the code path is guarded by RSA_C so using the heap is clearly OK. Signed-off-by: Manuel Pégourié-Gonnard --- library/pk.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/library/pk.c b/library/pk.c index b158547613..254ed64d3e 100644 --- a/library/pk.c +++ b/library/pk.c @@ -681,20 +681,26 @@ static int import_pair_into_psa(const mbedtls_pk_context *pk, if (psa_get_key_type(attributes) != PSA_KEY_TYPE_RSA_KEY_PAIR) { return MBEDTLS_ERR_PK_TYPE_MISMATCH; } - unsigned char key_buffer[ - PSA_KEY_EXPORT_RSA_KEY_PAIR_MAX_SIZE(PSA_VENDOR_RSA_MAX_KEY_BITS)]; - unsigned char *const key_end = key_buffer + sizeof(key_buffer); + size_t key_bits = psa_get_key_bits(attributes); + size_t key_buffer_size = PSA_KEY_EXPORT_RSA_KEY_PAIR_MAX_SIZE(key_bits); + unsigned char *key_buffer = mbedtls_calloc(1, key_buffer_size); + if (key_buffer == NULL) { + return MBEDTLS_ERR_PK_ALLOC_FAILED; + } + unsigned char *const key_end = key_buffer + key_buffer_size; unsigned char *key_data = key_end; int ret = mbedtls_rsa_write_key(mbedtls_pk_rsa(*pk), key_buffer, &key_data); if (ret < 0) { - return ret; + goto cleanup_rsa; } size_t key_length = key_end - key_data; ret = PSA_PK_TO_MBEDTLS_ERR(psa_import_key(attributes, key_data, key_length, key_id)); - mbedtls_platform_zeroize(key_data, key_length); +cleanup_rsa: + mbedtls_platform_zeroize(key_buffer, key_buffer_size); + mbedtls_free(key_buffer); return ret; } #endif /* MBEDTLS_RSA_C */ From 06830d69df11939c2cac78b6458d8da1f1fa91ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Mon, 23 Feb 2026 10:01:29 +0100 Subject: [PATCH 24/61] PK: avoid 1kB stack buffer in rsa_alt_check_pair() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Again, guarded by RSA_C, so use of heap is clearly OK. Signed-off-by: Manuel Pégourié-Gonnard --- library/pk_wrap.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/library/pk_wrap.c b/library/pk_wrap.c index 19196b559a..c174ce3b42 100644 --- a/library/pk_wrap.c +++ b/library/pk_wrap.c @@ -1336,7 +1336,6 @@ static int rsa_alt_check_pair(mbedtls_pk_context *pub, mbedtls_pk_context *prv, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng) { - unsigned char sig[MBEDTLS_MPI_MAX_SIZE]; unsigned char hash[32]; size_t sig_len = 0; int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; @@ -1345,21 +1344,29 @@ static int rsa_alt_check_pair(mbedtls_pk_context *pub, mbedtls_pk_context *prv, return MBEDTLS_ERR_RSA_KEY_CHECK_FAILED; } + size_t sig_size = (rsa_get_bitlen(pub) + 7) / 8; + unsigned char *sig = mbedtls_calloc(1, sig_size); + if (sig == NULL) { + return MBEDTLS_ERR_PK_ALLOC_FAILED; + } + memset(hash, 0x2a, sizeof(hash)); if ((ret = rsa_alt_sign_wrap(prv, MBEDTLS_MD_NONE, hash, sizeof(hash), - sig, sizeof(sig), &sig_len, + sig, sig_size, &sig_len, f_rng, p_rng)) != 0) { - return ret; + goto cleanup; } if (rsa_verify_wrap(pub, MBEDTLS_MD_NONE, hash, sizeof(hash), sig, sig_len) != 0) { - return MBEDTLS_ERR_RSA_KEY_CHECK_FAILED; + ret = MBEDTLS_ERR_RSA_KEY_CHECK_FAILED; } - return 0; +cleanup: + mbedtls_free(sig); + return ret; } #endif /* MBEDTLS_RSA_C */ From 3e7657c81f6e114e8caa99aa8c0f445080b74637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Mon, 23 Feb 2026 10:14:21 +0100 Subject: [PATCH 25/61] PK: use smarter size for stack buffer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The original macro was accounting for the size of public FFDH keys, which are not a concern for PK. Use sizes for key types supported by PK, this brings the size of the buffer from 1kB to 528 bytes in the default config. Signed-off-by: Manuel Pégourié-Gonnard --- library/pk.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/library/pk.c b/library/pk.c index 254ed64d3e..6b8240a304 100644 --- a/library/pk.c +++ b/library/pk.c @@ -39,6 +39,25 @@ #include "mbedtls/platform.h" // for calloc/free #endif +#define MBEDTLS_PK_MAX_EC_PUBKEY_RAW_LEN \ + PSA_KEY_EXPORT_ECC_PUBLIC_KEY_MAX_SIZE(PSA_VENDOR_ECC_MAX_CURVE_BITS) + +#define MBEDTLS_PK_MAX_RSA_PUBKEY_RAW_LEN \ + PSA_KEY_EXPORT_RSA_PUBLIC_KEY_MAX_SIZE(PSA_VENDOR_RSA_MAX_KEY_BITS) + +#define MBEDTLS_PK_MAX_PUBKEY_RAW_LEN 0 +#if (defined(MBEDTLS_ECP_C) || \ + (defined(MBEDTLS_USE_PSA_CRYPTO) && defined(PSA_WANT_KEY_TYPE_ECC_PUBLIC_KEY))) && \ + MBEDTLS_PK_MAX_EC_PUBKEY_RAW_LEN > MBEDTLS_PK_MAX_PUBKEY_RAW_LEN +#undef MBEDTLS_PK_MAX_PUBKEY_RAW_LEN +#define MBEDTLS_PK_MAX_PUBKEY_RAW_LEN MBEDTLS_PK_MAX_EC_PUBKEY_RAW_LEN +#endif +#if (defined(MBEDTLS_RSA_C) || \ + (defined(MBEDTLS_USE_PSA_CRYPTO) && defined(PSA_WANT_KEY_TYPE_RSA_PUBLIC_KEY))) && \ + MBEDTLS_PK_MAX_RSA_PUBKEY_RAW_LEN > MBEDTLS_PK_MAX_PUBKEY_RAW_LEN +#undef MBEDTLS_PK_MAX_PUBKEY_RAW_LEN +#define MBEDTLS_PK_MAX_PUBKEY_RAW_LEN MBEDTLS_PK_MAX_RSA_PUBKEY_RAW_LEN +#endif /* * Initialise a mbedtls_pk_context @@ -779,7 +798,7 @@ static int import_public_into_psa(const mbedtls_pk_context *pk, #if defined(MBEDTLS_RSA_C) || \ (defined(MBEDTLS_PK_HAVE_ECC_KEYS) && !defined(MBEDTLS_PK_USE_PSA_EC_DATA)) || \ defined(MBEDTLS_USE_PSA_CRYPTO) - unsigned char key_buffer[PSA_EXPORT_PUBLIC_KEY_MAX_SIZE]; + unsigned char key_buffer[MBEDTLS_PK_MAX_PUBKEY_RAW_LEN]; #endif unsigned char *key_data = NULL; size_t key_length = 0; From c5121ed11e928d7fb95dfb71f813efb1886e2722 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Mon, 23 Feb 2026 11:04:23 +0100 Subject: [PATCH 26/61] PK: only use PSA macros when the header has been included... MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/pk.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/pk.c b/library/pk.c index 6b8240a304..88eee3c515 100644 --- a/library/pk.c +++ b/library/pk.c @@ -39,6 +39,7 @@ #include "mbedtls/platform.h" // for calloc/free #endif +#if defined(MBEDTLS_PSA_CRYPTO_CLIENT) #define MBEDTLS_PK_MAX_EC_PUBKEY_RAW_LEN \ PSA_KEY_EXPORT_ECC_PUBLIC_KEY_MAX_SIZE(PSA_VENDOR_ECC_MAX_CURVE_BITS) @@ -58,6 +59,7 @@ #undef MBEDTLS_PK_MAX_PUBKEY_RAW_LEN #define MBEDTLS_PK_MAX_PUBKEY_RAW_LEN MBEDTLS_PK_MAX_RSA_PUBKEY_RAW_LEN #endif +#endif /* MBEDTLS_PSA_CRYPTO_CLIENT */ /* * Initialise a mbedtls_pk_context From 4a8d476568d1ce171d266d062ac8f6055c6da714 Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Tue, 13 Jan 2026 15:42:11 +0100 Subject: [PATCH 27/61] ssl_tls.c: Allow client hello fragmentation Signed-off-by: Ronald Cron --- library/ssl_tls.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/library/ssl_tls.c b/library/ssl_tls.c index a8687277ea..d7773be3e0 100644 --- a/library/ssl_tls.c +++ b/library/ssl_tls.c @@ -3286,13 +3286,6 @@ size_t mbedtls_ssl_get_output_max_frag_len(const mbedtls_ssl_context *ssl) #if defined(MBEDTLS_SSL_PROTO_DTLS) size_t mbedtls_ssl_get_current_mtu(const mbedtls_ssl_context *ssl) { - /* Return unlimited mtu for client hello messages to avoid fragmentation. */ - if (ssl->conf->endpoint == MBEDTLS_SSL_IS_CLIENT && - (ssl->state == MBEDTLS_SSL_CLIENT_HELLO || - ssl->state == MBEDTLS_SSL_SERVER_HELLO)) { - return 0; - } - if (ssl->handshake == NULL || ssl->handshake->mtu == 0) { return ssl->mtu; } From faa9d95638993ffdc89690622eb05224005c4b84 Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Wed, 14 Jan 2026 16:32:48 +0100 Subject: [PATCH 28/61] ssl-opt.sh: Relax deps of handshake defrag tests Relax the dependencies of the tests about handshake message defragmentation/reassembly on server side. TLS 1.3 does not need to be enable anymore for this to work for TLS 1.2 handshake messages. Signed-off-by: Ronald Cron --- scripts/generate_tls_handshake_tests.py | 3 --- tests/ssl-opt.sh | 3 --- 2 files changed, 6 deletions(-) diff --git a/scripts/generate_tls_handshake_tests.py b/scripts/generate_tls_handshake_tests.py index 30f27b1b37..76c8e45d57 100755 --- a/scripts/generate_tls_handshake_tests.py +++ b/scripts/generate_tls_handshake_tests.py @@ -6,12 +6,9 @@ Generate miscellaneous TLS test cases relating to the handshake. # Copyright The Mbed TLS Contributors # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later -import sys - import framework_scripts_path # pylint: disable=unused-import from mbedtls_framework import tls_handshake_tests if __name__ == '__main__': - sys.argv[1:1] = ["--no-tls12-client-hello-defragmentation-support"] tls_handshake_tests.main() diff --git a/tests/ssl-opt.sh b/tests/ssl-opt.sh index c129db0946..ef84feb3b4 100755 --- a/tests/ssl-opt.sh +++ b/tests/ssl-opt.sh @@ -14853,7 +14853,6 @@ run_test "Handshake defragmentation on server: len=256, client-initiated rene -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" \ @@ -14870,7 +14869,6 @@ run_test "Handshake defragmentation on server: len=128, client-initiated rene -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" \ @@ -14887,7 +14885,6 @@ run_test "Handshake defragmentation on server: len=4, client-initiated renego -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" \ From 0a8c35d273257d7102ef8af1e351fa9c7bb688d9 Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Wed, 21 Jan 2026 11:33:35 +0100 Subject: [PATCH 29/61] ssl_server2.c: DTLS: Attempt to read the response to the close notification Signed-off-by: Ronald Cron --- programs/ssl/ssl_server2.c | 50 ++++++++++++++++++- tests/compat.sh | 1 + tests/scripts/components-configuration-tls.sh | 1 + 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/programs/ssl/ssl_server2.c b/programs/ssl/ssl_server2.c index 82c01e1168..b01fcfcb05 100644 --- a/programs/ssl/ssl_server2.c +++ b/programs/ssl/ssl_server2.c @@ -4235,7 +4235,55 @@ close_notify: } while (ret == MBEDTLS_ERR_SSL_WANT_WRITE); ret = 0; - mbedtls_printf(" done\n"); + /* + * In the DTLS case, attempt to read a possible response to the close + * notification. This avoids reconnecting to the same client when we + * reset and later receive its close-notification response during + * step 3 (waiting for a client to connect). + * + * Stop waiting for the response if the connection has already ended. + * + * The waiting loop below relies on mbedtls_ssl_read() returning regularly + * in order to keep the total waiting time approximately bounded to 1s. If + * no read timeout is configured (see the read_timeout option), or if the + * configured timeout is close to or larger than 1s, the total waiting time + * may exceed 1s by a significant margin. + */ +#if defined(MBEDTLS_SSL_PROTO_DTLS) && defined(MBEDTLS_HAVE_TIME) + if (opt.transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM) { + mbedtls_ms_time_t start = mbedtls_ms_time(); + for (;;) { + ret = mbedtls_ssl_read(&ssl, buf, opt.buffer_size); + /* + * mbedtls_ssl_read() returned some data or timed out, loop if we + * have not spent already too much time, quite arbitrarily 1s. + */ + if ((ret > 0) || (ret == MBEDTLS_ERR_SSL_TIMEOUT)) { + if ((mbedtls_ms_time() - start) < 1000) { + continue; + } + } + + if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) { + mbedtls_printf(" done, received client close notification.\n"); + } else { + /* ret = 0, silent transport EOF or ret < 0 except + * MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY. Note that we do not + * handle specifically the non-fatal error codes like + * MBEDTLS_ERR_SSL_WANT_READ as we do not really expect them + * here. + */ + mbedtls_printf(" done\n"); + } + break; + } + ret = 0; + } else +#endif /* MBEDTLS_SSL_PROTO_DTLS && MBEDTLS_HAVE_TIME */ + { + mbedtls_printf(" done\n"); + } + fflush(stdout); #if defined(MBEDTLS_SSL_CACHE_C) if (opt.cache_remove > 0) { diff --git a/tests/compat.sh b/tests/compat.sh index 22da5ee4ed..1bbed59929 100755 --- a/tests/compat.sh +++ b/tests/compat.sh @@ -660,6 +660,7 @@ setup_arguments() # with OpenSSL 1.0.1h, -www, -WWW and -HTTP break DTLS handshakes if is_dtls "$MODE"; then O_SERVER_ARGS="$O_SERVER_ARGS" + M_SERVER_ARGS="$M_SERVER_ARGS read_timeout=1000" else O_SERVER_ARGS="$O_SERVER_ARGS -www" fi diff --git a/tests/scripts/components-configuration-tls.sh b/tests/scripts/components-configuration-tls.sh index 23c9d68b9f..d2a6049777 100644 --- a/tests/scripts/components-configuration-tls.sh +++ b/tests/scripts/components-configuration-tls.sh @@ -385,6 +385,7 @@ component_test_tls1_2_ccm_psk_psa () { component_test_tls1_2_ccm_psk_dtls_legacy () { msg "build: configs/config-ccm-psk-dtls1_2.h" cp configs/config-ccm-psk-dtls1_2.h "$CONFIG_H" + scripts/config.py set MBEDTLS_HAVE_TIME # test-ref-configs works by overwriting mbedtls_config.h; this makes cmake # want to re-generate generated files that depend on it, quite correctly. # However this doesn't work as the generation script expects a specific From 17acd871c13bb9dbfb8b0fbdcd12cb5cb1950813 Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Mon, 12 Jan 2026 12:45:40 +0100 Subject: [PATCH 30/61] ssl_tls12_server.c: Document replay check and update in ssl_parse_client_hello() Signed-off-by: Ronald Cron --- library/ssl_tls12_server.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/ssl_tls12_server.c b/library/ssl_tls12_server.c index acf4bfb07b..b2703c8cd2 100644 --- a/library/ssl_tls12_server.c +++ b/library/ssl_tls12_server.c @@ -981,6 +981,9 @@ read_record_header: memcpy(&ssl->cur_out_ctr[2], ssl->in_ctr + 2, sizeof(ssl->cur_out_ctr) - 2); + /* Check for record replay and then update the window. This replicates what + * is done in `ssl_get_next_record()` when the record is not fetched through + * `mbedtls_ssl_read_record()`. */ #if defined(MBEDTLS_SSL_DTLS_ANTI_REPLAY) if (mbedtls_ssl_dtls_replay_check(ssl) != 0) { MBEDTLS_SSL_DEBUG_MSG(1, ("replayed record, discarding")); From 06abef23072c174c8cbf1ef145ca6865116617b6 Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Wed, 14 Jan 2026 13:48:52 +0100 Subject: [PATCH 31/61] ssl_tls12_server.c: Use mbedtls_ssl_read_record() only to read the ClientHello In ssl_tls12_server.c:ssl_parse_client_hello(), remove the code that directly reads the received data to read the record expected to contain the ClientHello message. The function already supported handling a ClientHello read via mbedtls_ssl_read_record() in the following cases: - when the ClientHello was read as a post-handshake message (renegotiation). - when the ClientHello was read by ssl_tls13_process_client_hello() during TLS 1.3 or TLS 1.2 version negotiation. Signed-off-by: Ronald Cron --- library/ssl_msg.c | 5 ++ library/ssl_tls12_server.c | 120 ++++++------------------------------- 2 files changed, 22 insertions(+), 103 deletions(-) diff --git a/library/ssl_msg.c b/library/ssl_msg.c index 38f81bd099..b353345a35 100644 --- a/library/ssl_msg.c +++ b/library/ssl_msg.c @@ -5830,6 +5830,11 @@ static int ssl_tls12_handle_hs_message_post_handshake(mbedtls_ssl_context *ssl) ssl->renego_status = MBEDTLS_SSL_RENEGOTIATION_PENDING; } #endif + + /* Keep the ClientHello message for ssl_parse_client_hello() */ + if (ssl->conf->endpoint == MBEDTLS_SSL_IS_SERVER) { + ssl->keep_current_message = 1; + } ret = mbedtls_ssl_start_renegotiation(ssl); if (ret != MBEDTLS_ERR_SSL_WAITING_SERVER_HELLO_RENEGO && ret != 0) { diff --git a/library/ssl_tls12_server.c b/library/ssl_tls12_server.c index b2703c8cd2..d34d810f10 100644 --- a/library/ssl_tls12_server.c +++ b/library/ssl_tls12_server.c @@ -912,29 +912,22 @@ static int ssl_parse_client_hello(mbedtls_ssl_context *ssl) MBEDTLS_SSL_DEBUG_MSG(2, ("=> parse client hello")); - int renegotiating; - -#if defined(MBEDTLS_SSL_DTLS_ANTI_REPLAY) -read_record_header: -#endif /* - * If renegotiating, then the input was read with mbedtls_ssl_read_record(), - * otherwise read it ourselves manually in order to support SSLv2 - * ClientHello, which doesn't use the same record layer format. - * Otherwise in a scenario of TLS 1.3/TLS 1.2 version negotiation, the - * ClientHello has been already fully fetched by the TLS 1.3 code and the - * flag ssl->keep_current_message is raised. + * Fetch the expected ClientHello handshake message. Do not ask + * mbedtls_ssl_read_record() to update the handshake digest, to align + * with cases where the ClientHello may already have been fetched in + * ssl_tls13_process_client_hello() or as a post-handshake message + * (renegotiation). */ - renegotiating = 0; -#if defined(MBEDTLS_SSL_RENEGOTIATION) - renegotiating = (ssl->renego_status != MBEDTLS_SSL_INITIAL_HANDSHAKE); -#endif - if (!renegotiating && !ssl->keep_current_message) { - if ((ret = mbedtls_ssl_fetch_input(ssl, 5)) != 0) { - /* No alert on a read error. */ - MBEDTLS_SSL_DEBUG_RET(1, "mbedtls_ssl_fetch_input", ret); - return ret; - } + if ((ret = mbedtls_ssl_read_record(ssl, 0)) != 0) { + MBEDTLS_SSL_DEBUG_RET(1, "mbedtls_ssl_read_record ", ret); + return ret; + } + + ret = mbedtls_ssl_update_handshake_status(ssl); + if (0 != ret) { + MBEDTLS_SSL_DEBUG_RET(1, ("mbedtls_ssl_update_handshake_status"), ret); + return ret; } buf = ssl->in_hdr; @@ -953,7 +946,8 @@ read_record_header: MBEDTLS_SSL_DEBUG_MSG(3, ("client hello, message type: %d", buf[0])); - if (buf[0] != MBEDTLS_SSL_MSG_HANDSHAKE) { + if ((ssl->in_msgtype != MBEDTLS_SSL_MSG_HANDSHAKE) || + (buf[0] != MBEDTLS_SSL_MSG_HANDSHAKE)) { MBEDTLS_SSL_DEBUG_MSG(1, ("bad client hello message")); return MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE; } @@ -980,66 +974,11 @@ read_record_header: memcpy(&ssl->cur_out_ctr[2], ssl->in_ctr + 2, sizeof(ssl->cur_out_ctr) - 2); - - /* Check for record replay and then update the window. This replicates what - * is done in `ssl_get_next_record()` when the record is not fetched through - * `mbedtls_ssl_read_record()`. */ -#if defined(MBEDTLS_SSL_DTLS_ANTI_REPLAY) - if (mbedtls_ssl_dtls_replay_check(ssl) != 0) { - MBEDTLS_SSL_DEBUG_MSG(1, ("replayed record, discarding")); - ssl->next_record_offset = 0; - ssl->in_left = 0; - goto read_record_header; - } - - /* No MAC to check yet, so we can update right now */ - mbedtls_ssl_dtls_replay_update(ssl); -#endif } #endif /* MBEDTLS_SSL_PROTO_DTLS */ - msg_len = MBEDTLS_GET_UINT16_BE(ssl->in_len, 0); - -#if defined(MBEDTLS_SSL_RENEGOTIATION) - if (ssl->renego_status != MBEDTLS_SSL_INITIAL_HANDSHAKE) { - /* Set by mbedtls_ssl_read_record() */ - msg_len = ssl->in_hslen; - } else -#endif - { - if (ssl->keep_current_message) { - ssl->keep_current_message = 0; - } else { - if (msg_len > MBEDTLS_SSL_IN_CONTENT_LEN) { - MBEDTLS_SSL_DEBUG_MSG(1, ("bad client hello message")); - return MBEDTLS_ERR_SSL_ILLEGAL_PARAMETER; - } - - if ((ret = mbedtls_ssl_fetch_input(ssl, - mbedtls_ssl_in_hdr_len(ssl) + msg_len)) != 0) { - MBEDTLS_SSL_DEBUG_RET(1, "mbedtls_ssl_fetch_input", ret); - return ret; - } - - /* Done reading this record, get ready for the next one */ -#if defined(MBEDTLS_SSL_PROTO_DTLS) - if (ssl->conf->transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM) { - ssl->next_record_offset = msg_len + mbedtls_ssl_in_hdr_len(ssl); - } else -#endif - ssl->in_left = 0; - } - } - buf = ssl->in_msg; - - MBEDTLS_SSL_DEBUG_BUF(4, "record contents", buf, msg_len); - - ret = ssl->handshake->update_checksum(ssl, buf, msg_len); - if (0 != ret) { - MBEDTLS_SSL_DEBUG_RET(1, ("update_checksum"), ret); - return ret; - } + msg_len = ssl->in_hslen; /* * Handshake layer: @@ -1049,13 +988,6 @@ read_record_header: * 6 . 8 DTLS only: fragment offset * 9 . 11 DTLS only: fragment length */ - if (msg_len < mbedtls_ssl_hs_hdr_len(ssl)) { - MBEDTLS_SSL_DEBUG_MSG(1, ("bad client hello message")); - return MBEDTLS_ERR_SSL_DECODE_ERROR; - } - - MBEDTLS_SSL_DEBUG_MSG(3, ("client hello v3, handshake type: %d", buf[0])); - if (buf[0] != MBEDTLS_SSL_HS_CLIENT_HELLO) { MBEDTLS_SSL_DEBUG_MSG(1, ("bad client hello message")); return MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE; @@ -1086,24 +1018,6 @@ read_record_header: ssl->handshake->out_msg_seq = cli_msg_seq; ssl->handshake->in_msg_seq = cli_msg_seq + 1; } - { - /* - * For now we don't support fragmentation, so make sure - * fragment_offset == 0 and fragment_length == length - */ - size_t fragment_offset, fragment_length, length; - fragment_offset = MBEDTLS_GET_UINT24_BE(ssl->in_msg, 6); - fragment_length = MBEDTLS_GET_UINT24_BE(ssl->in_msg, 9); - length = MBEDTLS_GET_UINT24_BE(ssl->in_msg, 1); - MBEDTLS_SSL_DEBUG_MSG( - 4, ("fragment_offset=%u fragment_length=%u length=%u", - (unsigned) fragment_offset, (unsigned) fragment_length, - (unsigned) length)); - if (fragment_offset != 0 || length != fragment_length) { - MBEDTLS_SSL_DEBUG_MSG(1, ("ClientHello fragmentation not supported")); - return MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE; - } - } } #endif /* MBEDTLS_SSL_PROTO_DTLS */ From 5a744e8d34c232e4b22a22f305372ded8fc1fb11 Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Thu, 8 Jan 2026 09:15:40 +0100 Subject: [PATCH 32/61] ssl_tls12_server.c: Move ClientHello message_seq adjustment Move ClientHello message_seq adjustment to the record layer. Signed-off-by: Ronald Cron --- library/ssl_msg.c | 21 +++++++++++++++++++++ library/ssl_tls12_server.c | 28 ---------------------------- 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/library/ssl_msg.c b/library/ssl_msg.c index b353345a35..6f2ac8826a 100644 --- a/library/ssl_msg.c +++ b/library/ssl_msg.c @@ -3261,6 +3261,27 @@ int mbedtls_ssl_prepare_handshake_record(mbedtls_ssl_context *ssl) return MBEDTLS_ERR_SSL_INVALID_RECORD; } + /* + * When establishing the connection, the client may go through a series + * of ClientHello and HelloVerifyRequest requests and responses. The + * server does not keep any trace of these initial round trips as + * intended: minimum allocated ressources as long as the reachability + * of the client has not been confirmed. When receiving the "first + * ClientHello" from server perspective, we may thus need to adapt + * the next expected `message_seq` for the incoming and outgoing + * handshake messages. + */ + if (ssl->in_msg[0] == MBEDTLS_SSL_HS_CLIENT_HELLO && + ssl->conf->endpoint == MBEDTLS_SSL_IS_SERVER && + ssl->state == MBEDTLS_SSL_CLIENT_HELLO +#if defined(MBEDTLS_SSL_RENEGOTIATION) + && ssl->renego_status == MBEDTLS_SSL_INITIAL_HANDSHAKE +#endif + ) { + ssl->handshake->in_msg_seq = recv_msg_seq; + ssl->handshake->out_msg_seq = recv_msg_seq; + } + if (ssl->handshake != NULL && ((mbedtls_ssl_is_handshake_over(ssl) == 0 && recv_msg_seq != ssl->handshake->in_msg_seq) || diff --git a/library/ssl_tls12_server.c b/library/ssl_tls12_server.c index d34d810f10..c5db5536b9 100644 --- a/library/ssl_tls12_server.c +++ b/library/ssl_tls12_server.c @@ -993,34 +993,6 @@ static int ssl_parse_client_hello(mbedtls_ssl_context *ssl) return MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE; } -#if defined(MBEDTLS_SSL_PROTO_DTLS) - if (ssl->conf->transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM) { - /* - * Copy the client's handshake message_seq on initial handshakes, - * check sequence number on renego. - */ -#if defined(MBEDTLS_SSL_RENEGOTIATION) - if (ssl->renego_status == MBEDTLS_SSL_RENEGOTIATION_IN_PROGRESS) { - /* This couldn't be done in ssl_prepare_handshake_record() */ - unsigned int cli_msg_seq = (unsigned int) MBEDTLS_GET_UINT16_BE(ssl->in_msg, 4); - if (cli_msg_seq != ssl->handshake->in_msg_seq) { - MBEDTLS_SSL_DEBUG_MSG(1, ("bad client hello message_seq: " - "%u (expected %u)", cli_msg_seq, - ssl->handshake->in_msg_seq)); - return MBEDTLS_ERR_SSL_DECODE_ERROR; - } - - ssl->handshake->in_msg_seq++; - } else -#endif - { - unsigned int cli_msg_seq = (unsigned int) MBEDTLS_GET_UINT16_BE(ssl->in_msg, 4); - ssl->handshake->out_msg_seq = cli_msg_seq; - ssl->handshake->in_msg_seq = cli_msg_seq + 1; - } - } -#endif /* MBEDTLS_SSL_PROTO_DTLS */ - buf += mbedtls_ssl_hs_hdr_len(ssl); msg_len -= mbedtls_ssl_hs_hdr_len(ssl); From 6a9fc0ce77446b6053a5b31e57b49f3a3864af0a Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Thu, 22 Jan 2026 18:43:57 +0100 Subject: [PATCH 33/61] ssl_tls12_server.c: Move ClientHello record sequence_number init Signed-off-by: Ronald Cron --- library/ssl_msg.c | 9 +++++++++ library/ssl_tls12_server.c | 19 ------------------- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/library/ssl_msg.c b/library/ssl_msg.c index 6f2ac8826a..cd933f24f7 100644 --- a/library/ssl_msg.c +++ b/library/ssl_msg.c @@ -3280,6 +3280,15 @@ int mbedtls_ssl_prepare_handshake_record(mbedtls_ssl_context *ssl) ) { ssl->handshake->in_msg_seq = recv_msg_seq; ssl->handshake->out_msg_seq = recv_msg_seq; + + /* Epoch should be 0 for initial handshakes */ + if (ssl->in_ctr[0] != 0 || ssl->in_ctr[1] != 0) { + MBEDTLS_SSL_DEBUG_MSG(1, ("bad client hello message")); + return MBEDTLS_ERR_SSL_ILLEGAL_PARAMETER; + } + + memcpy(&ssl->cur_out_ctr[2], ssl->in_ctr + 2, + sizeof(ssl->cur_out_ctr) - 2); } if (ssl->handshake != NULL && diff --git a/library/ssl_tls12_server.c b/library/ssl_tls12_server.c index c5db5536b9..16bbf7eb57 100644 --- a/library/ssl_tls12_server.c +++ b/library/ssl_tls12_server.c @@ -958,25 +958,6 @@ static int ssl_parse_client_hello(mbedtls_ssl_context *ssl) MBEDTLS_SSL_DEBUG_MSG(3, ("client hello, protocol version: [%d:%d]", buf[1], buf[2])); - /* For DTLS if this is the initial handshake, remember the client sequence - * number to use it in our next message (RFC 6347 4.2.1) */ -#if defined(MBEDTLS_SSL_PROTO_DTLS) - if (ssl->conf->transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM -#if defined(MBEDTLS_SSL_RENEGOTIATION) - && ssl->renego_status == MBEDTLS_SSL_INITIAL_HANDSHAKE -#endif - ) { - /* Epoch should be 0 for initial handshakes */ - if (ssl->in_ctr[0] != 0 || ssl->in_ctr[1] != 0) { - MBEDTLS_SSL_DEBUG_MSG(1, ("bad client hello message")); - return MBEDTLS_ERR_SSL_ILLEGAL_PARAMETER; - } - - memcpy(&ssl->cur_out_ctr[2], ssl->in_ctr + 2, - sizeof(ssl->cur_out_ctr) - 2); - } -#endif /* MBEDTLS_SSL_PROTO_DTLS */ - buf = ssl->in_msg; msg_len = ssl->in_hslen; From 2086b20f28b7f9ad90c8c6fbca4461cb22ab34f4 Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Thu, 22 Jan 2026 18:46:53 +0100 Subject: [PATCH 34/61] ssl_tls12_server.c: parse_client_hello: Remove remaining record level code Signed-off-by: Ronald Cron --- library/ssl_tls12_server.c | 31 ++----------------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/library/ssl_tls12_server.c b/library/ssl_tls12_server.c index 16bbf7eb57..47803dc85c 100644 --- a/library/ssl_tls12_server.c +++ b/library/ssl_tls12_server.c @@ -930,34 +930,6 @@ static int ssl_parse_client_hello(mbedtls_ssl_context *ssl) return ret; } - buf = ssl->in_hdr; - - MBEDTLS_SSL_DEBUG_BUF(4, "record header", buf, mbedtls_ssl_in_hdr_len(ssl)); - - /* - * TLS Client Hello - * - * Record layer: - * 0 . 0 message type - * 1 . 2 protocol version - * 3 . 11 DTLS: epoch + record sequence number - * 3 . 4 message length - */ - MBEDTLS_SSL_DEBUG_MSG(3, ("client hello, message type: %d", - buf[0])); - - if ((ssl->in_msgtype != MBEDTLS_SSL_MSG_HANDSHAKE) || - (buf[0] != MBEDTLS_SSL_MSG_HANDSHAKE)) { - MBEDTLS_SSL_DEBUG_MSG(1, ("bad client hello message")); - return MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE; - } - - MBEDTLS_SSL_DEBUG_MSG(3, ("client hello, message len.: %d", - MBEDTLS_GET_UINT16_BE(ssl->in_len, 0))); - - MBEDTLS_SSL_DEBUG_MSG(3, ("client hello, protocol version: [%d:%d]", - buf[1], buf[2])); - buf = ssl->in_msg; msg_len = ssl->in_hslen; @@ -969,7 +941,8 @@ static int ssl_parse_client_hello(mbedtls_ssl_context *ssl) * 6 . 8 DTLS only: fragment offset * 9 . 11 DTLS only: fragment length */ - if (buf[0] != MBEDTLS_SSL_HS_CLIENT_HELLO) { + if ((ssl->in_msgtype != MBEDTLS_SSL_MSG_HANDSHAKE) || + (buf[0] != MBEDTLS_SSL_HS_CLIENT_HELLO)) { MBEDTLS_SSL_DEBUG_MSG(1, ("bad client hello message")); return MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE; } From 09546ee1204388d94c0ae8f865657468132bd25c Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Fri, 23 Jan 2026 15:36:49 +0100 Subject: [PATCH 35/61] ssl_msg.c: Remove some now unnecessary code Signed-off-by: Ronald Cron --- library/ssl_msg.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/library/ssl_msg.c b/library/ssl_msg.c index cd933f24f7..b284e6dcda 100644 --- a/library/ssl_msg.c +++ b/library/ssl_msg.c @@ -5167,14 +5167,9 @@ static int ssl_get_next_record(mbedtls_ssl_context *ssl) /* The record content type may change during decryption, * so re-read it. */ ssl->in_msgtype = rec.type; - /* Also update the input buffer, because unfortunately - * the server-side ssl_parse_client_hello() reparses the - * record header when receiving a ClientHello initiating - * a renegotiation. */ - ssl->in_hdr[0] = rec.type; + ssl->in_msg = rec.buf + rec.data_offset; ssl->in_msglen = rec.data_len; - MBEDTLS_PUT_UINT16_BE(rec.data_len, ssl->in_len, 0); return 0; } From f20d48f71918b1a875703772b4de93ebc75597e5 Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Wed, 14 Jan 2026 09:42:27 +0100 Subject: [PATCH 36/61] ssl-opt.sh: Add interop test of DTLS defragmentation on server side Signed-off-by: Ronald Cron --- tests/ssl-opt.sh | 151 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/tests/ssl-opt.sh b/tests/ssl-opt.sh index ef84feb3b4..4641811d35 100755 --- a/tests/ssl-opt.sh +++ b/tests/ssl-opt.sh @@ -10982,6 +10982,51 @@ run_test "DTLS reassembly: fragmentation, nbio, renego (gnutls server)" \ -C "error" \ -s "Extra-header:" +requires_gnutls +requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 +run_test "DTLS reassembly: no fragmentation (gnutls client)" \ + "$P_SRV debug_level=2 dtls=1" \ + "$G_NEXT_CLI -u --mtu 2048 --insecure 127.0.0.1" \ + 0 \ + -S "found fragmented DTLS handshake message" \ + -S "error" + +requires_gnutls +requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 +run_test "DTLS reassembly: some fragmentation (gnutls client)" \ + "$P_SRV debug_level=2 dtls=1 auth_mode=required" \ + "$G_NEXT_CLI -u --mtu 256 --insecure 127.0.0.1 --x509certfile $DATA_FILES_PATH/server5.crt --x509keyfile $DATA_FILES_PATH/server5.key" \ + 0 \ + -s "found fragmented DTLS handshake message" \ + -s "Certificate handshake message has been buffered and reassembled" \ + -S "error" + +# Set the MTU to 128 bytes. The minimum size of a DTLS 1.2 record +# containing a ClientHello handshake message is 69 bytes, without any cookie, +# ciphersuite, or extension. With an MTU of 128 bytes, the ClientHello handshake +# message is therefore very likely to be fragmented in most library +# configurations. +requires_gnutls +requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 +run_test "DTLS reassembly: more fragmentation (gnutls client)" \ + "$P_SRV debug_level=2 dtls=1" \ + "$G_NEXT_CLI -u --mtu 128 --insecure 127.0.0.1" \ + 0 \ + -s "ClientHello handshake message has been buffered and reassembled" \ + -S "error" + +requires_gnutls +requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 +run_test "DTLS reassembly: more fragmentation, nbio (gnutls client)" \ + "$P_SRV debug_level=2 dtls=1 nbio=2" \ + "$G_NEXT_CLI -u --mtu 128 --insecure 127.0.0.1" \ + 0 \ + -s "ClientHello handshake message has been buffered and reassembled" \ + -S "error" + +# No fragmentation and renegotiation tests with GnuTLS client as the feature +# does not work properly. + requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 run_test "DTLS reassembly: no fragmentation (openssl server)" \ "$O_SRV -dtls -mtu 2048" \ @@ -11012,6 +11057,37 @@ run_test "DTLS reassembly: fragmentation, nbio (openssl server)" \ -c "Certificate handshake message has been buffered and reassembled" \ -C "error" +requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 +run_test "DTLS reassembly: no fragmentation (openssl client)" \ + "$P_SRV debug_level=2 dtls=1 auth_mode=required" \ + "$O_NEXT_CLI -dtls -mtu 2048 -cert $DATA_FILES_PATH/server5.crt -key $DATA_FILES_PATH/server5.key" \ + 0 \ + -S "found fragmented DTLS handshake message" \ + -S "error" + +# Minimum possible MTU for OpenSSL server: 256 bytes. +# We expect the server Certificate handshake to be fragmented and verify that +# this is the case. Depending on the configuration, other handshake messages may +# also be fragmented like the ClientHello, ClientKeyExchange or +# CertificateVerify messages. +requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 +run_test "DTLS reassembly: some fragmentation (openssl client)" \ + "$P_SRV debug_level=2 dtls=1 auth_mode=required" \ + "$O_NEXT_CLI -dtls -mtu 256 -cert $DATA_FILES_PATH/server5.crt -key $DATA_FILES_PATH/server5.key" \ + 0 \ + -s "found fragmented DTLS handshake message" \ + -s "Certificate handshake message has been buffered and reassembled" \ + -S "error" + +requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 +run_test "DTLS reassembly: fragmentation, nbio (openssl client)" \ + "$P_SRV debug_level=2 dtls=1 auth_mode=required nbio=2" \ + "$O_NEXT_CLI -dtls -mtu 256 -cert $DATA_FILES_PATH/server5.crt -key $DATA_FILES_PATH/server5.key" \ + 0 \ + -s "found fragmented DTLS handshake message" \ + -s "Certificate handshake message has been buffered and reassembled" \ + -S "error" + # Tests for sending fragmented handshake messages with DTLS # # Use client auth when we need the client to send large messages, @@ -12892,6 +12968,43 @@ run_test "DTLS proxy: 3d, openssl server, fragmentation, nbio" \ -c "HTTP/1.0 200 OK" \ -c "Certificate handshake message has been buffered and reassembled" +requires_openssl_next +client_needs_more_time 6 +not_with_valgrind # risk of non-mbedtls peer timing out +requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 +run_test "DTLS proxy: 3d, openssl client" \ + -p "$P_PXY drop=5 delay=5 duplicate=5" \ + "$P_SRV dgram_packing=0 dtls=1 hs_timeout=500-60000 tickets=0" \ + "$O_NEXT_CLI -dtls1_2 -mtu 2048" \ + 0 \ + -s "HTTP/1.0 200 OK" + +requires_openssl_next +client_needs_more_time 8 +not_with_valgrind # risk of non-mbedtls peer timing out +requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 +run_test "DTLS proxy: 3d, openssl client, fragmentation" \ + -p "$P_PXY drop=5 delay=5 duplicate=5" \ + "$P_SRV debug_level=2 dgram_packing=0 auth_mode=required dtls=1 hs_timeout=500-60000 tickets=0" \ + "$O_NEXT_CLI -dtls1_2 -mtu 256 -cert $DATA_FILES_PATH/server5.crt -key $DATA_FILES_PATH/server5.key" \ + 0 \ + -s "HTTP/1.0 200 OK" \ + -s "found fragmented DTLS handshake message" \ + -s "Certificate handshake message has been buffered and reassembled" + +requires_openssl_next +client_needs_more_time 8 +not_with_valgrind # risk of non-mbedtls peer timing out +requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 +run_test "DTLS proxy: 3d, openssl client, fragmentation, nbio" \ + -p "$P_PXY drop=5 delay=5 duplicate=5" \ + "$P_SRV debug_level=2 dgram_packing=0 auth_mode=required dtls=1 hs_timeout=500-60000 nbio=2 tickets=0" \ + "$O_NEXT_CLI -dtls1_2 -mtu 256 -cert $DATA_FILES_PATH/server5.crt -key $DATA_FILES_PATH/server5.key" \ + 0 \ + -s "HTTP/1.0 200 OK" \ + -s "found fragmented DTLS handshake message" \ + -s "Certificate handshake message has been buffered and reassembled" + requires_gnutls client_needs_more_time 6 not_with_valgrind # risk of non-mbedtls peer timing out @@ -12930,6 +13043,44 @@ run_test "DTLS proxy: 3d, gnutls server, fragmentation, nbio" \ -c "Extra-header:" \ -c "Certificate handshake message has been buffered and reassembled" +requires_gnutls +client_needs_more_time 6 +not_with_valgrind # risk of non-mbedtls peer timing out +requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 +run_test "DTLS proxy: 3d, gnutls client" \ + -p "$P_PXY drop=5 delay=5 duplicate=5" \ + "$P_SRV dgram_packing=0 dtls=1" \ + "$G_NEXT_CLI -u --mtu 2048 --insecure 127.0.0.1" \ + 0 \ + -s "HTTP/1.0 200 OK" + +# Set the MTU to 128 bytes. The ClientHello is not surely fragmented but very +# likely. Do not set it to 56 bytes where we would be sure that the ClientHello +# is fragmented as then experimentally the handshake fails too often. +requires_gnutls +client_needs_more_time 8 +not_with_valgrind # risk of non-mbedtls peer timing out +requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 +run_test "DTLS proxy: 3d, gnutls client, fragmentation" \ + -p "$P_PXY drop=5 delay=5 duplicate=5" \ + "$P_SRV dgram_packing=0 dtls=1 debug_level=2" \ + "$G_NEXT_CLI -u --mtu 128 --insecure 127.0.0.1" \ + 0 \ + -s "HTTP/1.0 200 OK" \ + -s "ClientHello handshake message has been buffered and reassembled" + +requires_gnutls +client_needs_more_time 8 +not_with_valgrind # risk of non-mbedtls peer timing out +requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 +run_test "DTLS proxy: 3d, gnutls client, fragmentation, nbio=2" \ + -p "$P_PXY drop=5 delay=5 duplicate=5" \ + "$P_SRV dgram_packing=0 dtls=1 debug_level=2 nbio=2" \ + "$G_NEXT_CLI -u --mtu 128 --insecure 127.0.0.1" \ + 0 \ + -s "HTTP/1.0 200 OK" \ + -s "ClientHello handshake message has been buffered and reassembled" + requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 run_test "export keys functionality" \ "$P_SRV eap_tls=1 debug_level=3" \ From f44b6545fef93cc2f758aedcdaea267c12f8ad9d Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Fri, 16 Jan 2026 16:50:39 +0100 Subject: [PATCH 37/61] ssl-opt.sh: Add tests with CH fragmented with DTLS in default config Signed-off-by: Ronald Cron --- tests/scripts/analyze_outcomes.py | 2 +- tests/ssl-opt.sh | 120 ++++++++++++++++++++++++++++-- 2 files changed, 113 insertions(+), 9 deletions(-) diff --git a/tests/scripts/analyze_outcomes.py b/tests/scripts/analyze_outcomes.py index 7b43145151..0266f1457c 100755 --- a/tests/scripts/analyze_outcomes.py +++ b/tests/scripts/analyze_outcomes.py @@ -43,7 +43,7 @@ class CoverageTask(outcome_analysis.CoverageTask): 'DTLS cookie: enabled, IPv6', # Disabled due to OpenSSL bug. # https://github.com/openssl/openssl/issues/18887 - 'DTLS fragmenting: 3d, openssl client, DTLS 1.2', + 'DTLS fragmenting: 3d, MTU=512, openssl client, DTLS 1.2', # We don't run ssl-opt.sh with Valgrind on the CI because # it's extremely slow. We don't intend to change this. 'DTLS fragmenting: proxy MTU: auto-reduction (with valgrind)', diff --git a/tests/ssl-opt.sh b/tests/ssl-opt.sh index 4641811d35..4423b1a285 100755 --- a/tests/ssl-opt.sh +++ b/tests/ssl-opt.sh @@ -11371,6 +11371,37 @@ run_test "DTLS fragmenting: both (MTU=512)" \ -c "found fragmented DTLS handshake message" \ -C "error" +# Depending on the ciphersuite selected to encrypt the application data, the +# maximum application data payload per record may be small with an MTU of 128. +# For example, with TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384, this maximum is +# 35 bytes. We therefore reduce the size of the client request and the server +# response in this test. +requires_config_enabled MBEDTLS_SSL_PROTO_DTLS +requires_max_content_len 2048 +run_test "DTLS fragmenting: both (MTU=128)" \ + -p "$P_PXY mtu=128" \ + "$P_SRV dtls=1 debug_level=5 auth_mode=required \ + crt_file=$DATA_FILES_PATH/server7_int-ca.crt \ + key_file=$DATA_FILES_PATH/server7.key \ + response_size=8 \ + hs_timeout=2500-60000 \ + mtu=128" \ + "$P_CLI dtls=1 debug_level=2 \ + crt_file=$DATA_FILES_PATH/server8_int-ca2.crt \ + key_file=$DATA_FILES_PATH/server8.key \ + request_size=8 \ + hs_timeout=2500-60000 \ + mtu=128" \ + 0 \ + -s "found fragmented DTLS handshake message" \ + -s "fragmenting Certificate handshake message" \ + -s "fragmenting ServerKeyExchange handshake message" \ + -c "found fragmented DTLS handshake message" \ + -c "fragmenting ClientHello handshake message" \ + -c "fragmenting Certificate handshake message" \ + -c "fragmenting CertificateVerify handshake message" \ + -C "error" + # Test for automatic MTU reduction on repeated resend. # Forcing ciphersuite for this test to fit the MTU of 508 with full config. # The ratio of max/min timeout should ideally equal 4 to accept two @@ -11747,7 +11778,7 @@ requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C requires_gnutls requires_max_content_len 2048 -run_test "DTLS fragmenting: gnutls server, DTLS 1.2" \ +run_test "DTLS fragmenting: MTU=512, gnutls server, DTLS 1.2" \ "$G_SRV -u" \ "$P_CLI dtls=1 debug_level=2 \ crt_file=$DATA_FILES_PATH/server8_int-ca2.crt \ @@ -11757,6 +11788,21 @@ run_test "DTLS fragmenting: gnutls server, DTLS 1.2" \ -c "fragmenting Certificate handshake message" \ -C "error" +requires_config_enabled MBEDTLS_SSL_PROTO_DTLS +requires_config_enabled MBEDTLS_RSA_C +requires_gnutls +requires_max_content_len 2048 +run_test "DTLS fragmenting: MTU=128, gnutls server, DTLS 1.2" \ + "$G_NEXT_SRV -u" \ + "$P_CLI dtls=1 debug_level=2 \ + crt_file=$DATA_FILES_PATH/server8_int-ca2.crt \ + key_file=$DATA_FILES_PATH/server8.key \ + mtu=128 force_version=dtls12" \ + 0 \ + -c "fragmenting ClientHello handshake message" \ + -c "fragmenting Certificate handshake message" \ + -C "error" + # We use --insecure for the GnuTLS client because it expects # the hostname / IP it connects to to be the name used in the # certificate obtained from the server. Here, however, it @@ -11769,7 +11815,7 @@ requires_config_enabled MBEDTLS_RSA_C requires_gnutls requires_not_i686 requires_max_content_len 2048 -run_test "DTLS fragmenting: gnutls client, DTLS 1.2" \ +run_test "DTLS fragmenting: MTU=512, gnutls client, DTLS 1.2" \ "$P_SRV dtls=1 debug_level=2 \ crt_file=$DATA_FILES_PATH/server7_int-ca.crt \ key_file=$DATA_FILES_PATH/server7.key \ @@ -11781,7 +11827,7 @@ run_test "DTLS fragmenting: gnutls client, DTLS 1.2" \ requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C requires_max_content_len 2048 -run_test "DTLS fragmenting: openssl server, DTLS 1.2" \ +run_test "DTLS fragmenting: MTU=512, openssl server, DTLS 1.2" \ "$O_SRV -dtls1_2 -verify 10" \ "$P_CLI dtls=1 debug_level=2 \ crt_file=$DATA_FILES_PATH/server8_int-ca2.crt \ @@ -11791,10 +11837,29 @@ run_test "DTLS fragmenting: openssl server, DTLS 1.2" \ -c "fragmenting Certificate handshake message" \ -C "error" +# Depending on the ciphersuite selected to encrypt the application data, the +# maximum application data payload per record may be small with an MTU of 128. +# For example, with TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384, this maximum is +# 35 bytes. We therefore reduce the size of the client request in this test. requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C requires_max_content_len 2048 -run_test "DTLS fragmenting: openssl client, DTLS 1.2" \ +run_test "DTLS fragmenting: MTU=128, openssl server, DTLS 1.2" \ + "$O_NEXT_SRV -dtls1_2 -verify 10" \ + "$P_CLI dtls=1 debug_level=2 \ + crt_file=$DATA_FILES_PATH/server8_int-ca2.crt \ + key_file=$DATA_FILES_PATH/server8.key \ + request_size=8 \ + mtu=128 force_version=dtls12" \ + 0 \ + -c "fragmenting ClientHello handshake message" \ + -c "fragmenting Certificate handshake message" \ + -C "error" + +requires_config_enabled MBEDTLS_SSL_PROTO_DTLS +requires_config_enabled MBEDTLS_RSA_C +requires_max_content_len 2048 +run_test "DTLS fragmenting: MTU=512, openssl client, DTLS 1.2" \ "$P_SRV dtls=1 debug_level=2 \ crt_file=$DATA_FILES_PATH/server7_int-ca.crt \ key_file=$DATA_FILES_PATH/server7.key \ @@ -11812,7 +11877,7 @@ requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C client_needs_more_time 4 requires_max_content_len 2048 -run_test "DTLS fragmenting: 3d, gnutls server, DTLS 1.2" \ +run_test "DTLS fragmenting: 3d, MTU=512, gnutls server, DTLS 1.2" \ -p "$P_PXY drop=8 delay=8 duplicate=8" \ "$G_NEXT_SRV -u" \ "$P_CLI dgram_packing=0 dtls=1 debug_level=2 \ @@ -11823,12 +11888,29 @@ run_test "DTLS fragmenting: 3d, gnutls server, DTLS 1.2" \ -c "fragmenting Certificate handshake message" \ -C "error" +requires_gnutls_next +requires_config_enabled MBEDTLS_SSL_PROTO_DTLS +requires_config_enabled MBEDTLS_RSA_C +client_needs_more_time 6 +requires_max_content_len 2048 +run_test "DTLS fragmenting: 3d, MTU=128, gnutls server, DTLS 1.2" \ + -p "$P_PXY drop=8 delay=8 duplicate=8" \ + "$G_NEXT_SRV -u" \ + "$P_CLI dgram_packing=0 dtls=1 debug_level=2 \ + crt_file=$DATA_FILES_PATH/server8_int-ca2.crt \ + key_file=$DATA_FILES_PATH/server8.key \ + hs_timeout=250-60000 mtu=128 force_version=dtls12" \ + 0 \ + -c "fragmenting ClientHello handshake message" \ + -c "fragmenting Certificate handshake message" \ + -C "error" + requires_gnutls_next requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C client_needs_more_time 4 requires_max_content_len 2048 -run_test "DTLS fragmenting: 3d, gnutls client, DTLS 1.2" \ +run_test "DTLS fragmenting: 3d, MTU=512, gnutls client, DTLS 1.2" \ -p "$P_PXY drop=8 delay=8 duplicate=8" \ "$P_SRV dtls=1 debug_level=2 \ crt_file=$DATA_FILES_PATH/server7_int-ca.crt \ @@ -11845,7 +11927,7 @@ requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C client_needs_more_time 4 requires_max_content_len 2048 -run_test "DTLS fragmenting: 3d, openssl server, DTLS 1.2" \ +run_test "DTLS fragmenting: 3d, MTU=512, openssl server, DTLS 1.2" \ -p "$P_PXY drop=8 delay=8 duplicate=8" \ "$O_NEXT_SRV -dtls1_2 -verify 10" \ "$P_CLI dtls=1 debug_level=2 \ @@ -11856,6 +11938,28 @@ run_test "DTLS fragmenting: 3d, openssl server, DTLS 1.2" \ -c "fragmenting Certificate handshake message" \ -C "error" +# Depending on the ciphersuite selected to encrypt the application data, the +# maximum application data payload per record may be small with an MTU of 128. +# For example, with TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384, this maximum is +# 35 bytes. We therefore reduce the size of the client request in this test. +requires_openssl_next +requires_config_enabled MBEDTLS_SSL_PROTO_DTLS +requires_config_enabled MBEDTLS_RSA_C +client_needs_more_time 4 +requires_max_content_len 2048 +run_test "DTLS fragmenting: 3d, MTU=128, openssl server, DTLS 1.2" \ + -p "$P_PXY drop=8 delay=8 duplicate=8" \ + "$O_NEXT_SRV -dtls1_2 -verify 10" \ + "$P_CLI dtls=1 debug_level=2 \ + crt_file=$DATA_FILES_PATH/server8_int-ca2.crt \ + key_file=$DATA_FILES_PATH/server8.key \ + request_size=8 \ + hs_timeout=250-60000 mtu=128 force_version=dtls12" \ + 0 \ + -c "fragmenting ClientHello handshake message" \ + -c "fragmenting Certificate handshake message" \ + -C "error" + ## the test below will time out with certain seed. ## The cause is an openssl bug (https://github.com/openssl/openssl/issues/18887) skip_next_test @@ -11863,7 +11967,7 @@ requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C client_needs_more_time 4 requires_max_content_len 2048 -run_test "DTLS fragmenting: 3d, openssl client, DTLS 1.2" \ +run_test "DTLS fragmenting: 3d, MTU=512, openssl client, DTLS 1.2" \ -p "$P_PXY drop=8 delay=8 duplicate=8" \ "$P_SRV dtls=1 debug_level=2 \ crt_file=$DATA_FILES_PATH/server7_int-ca.crt \ From 50d1a74ad8474918276b16b4e7fb304fd92c170a Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Tue, 10 Feb 2026 19:12:21 +0100 Subject: [PATCH 38/61] ssl-opt.sh: Fix/improve comments Signed-off-by: Ronald Cron --- tests/ssl-opt.sh | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/tests/ssl-opt.sh b/tests/ssl-opt.sh index 4423b1a285..8dcb8ea3d6 100755 --- a/tests/ssl-opt.sh +++ b/tests/ssl-opt.sh @@ -11004,8 +11004,9 @@ run_test "DTLS reassembly: some fragmentation (gnutls client)" \ # Set the MTU to 128 bytes. The minimum size of a DTLS 1.2 record # containing a ClientHello handshake message is 69 bytes, without any cookie, # ciphersuite, or extension. With an MTU of 128 bytes, the ClientHello handshake -# message is therefore very likely to be fragmented in most library -# configurations. +# message is therefore very likely to be fragmented, regardless of the +# GnuTLS client version. For example, the ClientHello sent by the GnuTLS 3.7.2 +# client is 206 bytes in this test. requires_gnutls requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 run_test "DTLS reassembly: more fragmentation (gnutls client)" \ @@ -11066,10 +11067,11 @@ run_test "DTLS reassembly: no fragmentation (openssl client)" \ -S "error" # Minimum possible MTU for OpenSSL server: 256 bytes. -# We expect the server Certificate handshake to be fragmented and verify that -# this is the case. Depending on the configuration, other handshake messages may -# also be fragmented like the ClientHello, ClientKeyExchange or -# CertificateVerify messages. +# We expect the client Certificate handshake message to be fragmented and +# verify that this is the case. With OpenSSL 3.0.13, the ClientHello handshake +# message is 224 bytes and also fragmented. However, it may not hold across +# OpenSSL version updates. Therefore, we do not verify that the ClientHello is +# reassembled by the server. requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 run_test "DTLS reassembly: some fragmentation (openssl client)" \ "$P_SRV debug_level=2 dtls=1 auth_mode=required" \ @@ -13158,9 +13160,12 @@ run_test "DTLS proxy: 3d, gnutls client" \ 0 \ -s "HTTP/1.0 200 OK" -# Set the MTU to 128 bytes. The ClientHello is not surely fragmented but very -# likely. Do not set it to 56 bytes where we would be sure that the ClientHello -# is fragmented as then experimentally the handshake fails too often. +# Set the MTU to 128 bytes. The ClientHello is not guaranteed to be surely +# fragmented but it is very likely. For example, the ClientHello sent by the +# GnuTLS 3.7.2 client is 206 bytes in this test. We expect ClientHello +# fragmentation to remain the case across GnuTLS version updates. Avoid using a +# smaller MTU, as the smaller the MTU, the more likely the handshake is to fail +# in this very unreliable connection emulation. requires_gnutls client_needs_more_time 8 not_with_valgrind # risk of non-mbedtls peer timing out From 404daf5f6d311c4e02af0598dca022b4a945b324 Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Wed, 11 Feb 2026 09:19:18 +0100 Subject: [PATCH 39/61] ssl-opt.sh: DTLS reassembly: Improve max_content_len requirements Signed-off-by: Ronald Cron --- tests/ssl-opt.sh | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/ssl-opt.sh b/tests/ssl-opt.sh index 8dcb8ea3d6..3ddecf6e09 100755 --- a/tests/ssl-opt.sh +++ b/tests/ssl-opt.sh @@ -11331,7 +11331,7 @@ run_test "DTLS fragmenting: server (MTU)" \ requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C -requires_max_content_len 2048 +requires_max_content_len 1024 requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 run_test "DTLS fragmenting: both (MTU=1024)" \ -p "$P_PXY mtu=1024" \ @@ -11354,7 +11354,7 @@ run_test "DTLS fragmenting: both (MTU=1024)" \ requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C requires_hash_alg SHA_256 -requires_max_content_len 2048 +requires_max_content_len 512 run_test "DTLS fragmenting: both (MTU=512)" \ -p "$P_PXY mtu=512" \ "$P_SRV dtls=1 debug_level=2 auth_mode=required \ @@ -11379,7 +11379,7 @@ run_test "DTLS fragmenting: both (MTU=512)" \ # 35 bytes. We therefore reduce the size of the client request and the server # response in this test. requires_config_enabled MBEDTLS_SSL_PROTO_DTLS -requires_max_content_len 2048 +requires_max_content_len 128 run_test "DTLS fragmenting: both (MTU=128)" \ -p "$P_PXY mtu=128" \ "$P_SRV dtls=1 debug_level=5 auth_mode=required \ @@ -11457,7 +11457,7 @@ run_test "DTLS fragmenting: proxy MTU: auto-reduction (with valgrind)" \ not_with_valgrind # spurious autoreduction due to timeout requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C -requires_max_content_len 2048 +requires_max_content_len 1024 requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 run_test "DTLS fragmenting: proxy MTU, simple handshake (MTU=1024)" \ -p "$P_PXY mtu=1024" \ @@ -11484,7 +11484,7 @@ run_test "DTLS fragmenting: proxy MTU, simple handshake (MTU=1024)" \ not_with_valgrind # spurious autoreduction due to timeout requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C -requires_max_content_len 2048 +requires_max_content_len 512 run_test "DTLS fragmenting: proxy MTU, simple handshake (MTU=512)" \ -p "$P_PXY mtu=512" \ "$P_SRV dtls=1 debug_level=2 auth_mode=required \ @@ -11507,7 +11507,7 @@ run_test "DTLS fragmenting: proxy MTU, simple handshake (MTU=512)" \ not_with_valgrind # spurious autoreduction due to timeout requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C -requires_max_content_len 2048 +requires_max_content_len 1024 requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 run_test "DTLS fragmenting: proxy MTU, simple handshake, nbio (MTU=1024)" \ -p "$P_PXY mtu=1024" \ @@ -11531,7 +11531,7 @@ run_test "DTLS fragmenting: proxy MTU, simple handshake, nbio (MTU=1024)" \ not_with_valgrind # spurious autoreduction due to timeout requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C -requires_max_content_len 2048 +requires_max_content_len 512 run_test "DTLS fragmenting: proxy MTU, simple handshake, nbio (MTU=512)" \ -p "$P_PXY mtu=512" \ "$P_SRV dtls=1 debug_level=2 auth_mode=required \ @@ -11564,7 +11564,7 @@ run_test "DTLS fragmenting: proxy MTU, simple handshake, nbio (MTU=512)" \ not_with_valgrind # spurious autoreduction due to timeout requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C -requires_max_content_len 2048 +requires_max_content_len 1450 run_test "DTLS fragmenting: proxy MTU, resumed handshake" \ -p "$P_PXY mtu=1450" \ "$P_SRV dtls=1 debug_level=2 auth_mode=required \ @@ -11591,7 +11591,7 @@ requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C requires_hash_alg SHA_256 requires_config_enabled MBEDTLS_SSL_RENEGOTIATION -requires_max_content_len 2048 +requires_max_content_len 512 run_test "DTLS fragmenting: proxy MTU, ChachaPoly renego" \ -p "$P_PXY mtu=512" \ "$P_SRV dtls=1 debug_level=2 auth_mode=required \ @@ -11620,7 +11620,7 @@ requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C requires_hash_alg SHA_256 requires_config_enabled MBEDTLS_SSL_RENEGOTIATION -requires_max_content_len 2048 +requires_max_content_len 512 run_test "DTLS fragmenting: proxy MTU, AES-GCM renego" \ -p "$P_PXY mtu=512" \ "$P_SRV dtls=1 debug_level=2 auth_mode=required \ @@ -11649,7 +11649,7 @@ requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C requires_hash_alg SHA_256 requires_config_enabled MBEDTLS_SSL_RENEGOTIATION -requires_max_content_len 2048 +requires_max_content_len 1024 run_test "DTLS fragmenting: proxy MTU, AES-CCM renego" \ -p "$P_PXY mtu=1024" \ "$P_SRV dtls=1 debug_level=2 auth_mode=required \ @@ -11679,7 +11679,7 @@ requires_config_enabled MBEDTLS_RSA_C requires_hash_alg SHA_256 requires_config_enabled MBEDTLS_SSL_RENEGOTIATION requires_config_enabled MBEDTLS_SSL_ENCRYPT_THEN_MAC -requires_max_content_len 2048 +requires_max_content_len 1024 run_test "DTLS fragmenting: proxy MTU, AES-CBC EtM renego" \ -p "$P_PXY mtu=1024" \ "$P_SRV dtls=1 debug_level=2 auth_mode=required \ @@ -11708,7 +11708,7 @@ requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C requires_hash_alg SHA_256 requires_config_enabled MBEDTLS_SSL_RENEGOTIATION -requires_max_content_len 2048 +requires_max_content_len 1024 run_test "DTLS fragmenting: proxy MTU, AES-CBC non-EtM renego" \ -p "$P_PXY mtu=1024" \ "$P_SRV dtls=1 debug_level=2 auth_mode=required \ @@ -11734,7 +11734,7 @@ run_test "DTLS fragmenting: proxy MTU, AES-CBC non-EtM renego" \ requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C client_needs_more_time 2 -requires_max_content_len 2048 +requires_max_content_len 512 run_test "DTLS fragmenting: proxy MTU + 3d" \ -p "$P_PXY mtu=512 drop=8 delay=8 duplicate=8" \ "$P_SRV dgram_packing=0 dtls=1 debug_level=2 auth_mode=required \ @@ -11755,7 +11755,7 @@ run_test "DTLS fragmenting: proxy MTU + 3d" \ requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C client_needs_more_time 2 -requires_max_content_len 2048 +requires_max_content_len 512 run_test "DTLS fragmenting: proxy MTU + 3d, nbio" \ -p "$P_PXY mtu=512 drop=8 delay=8 duplicate=8" \ "$P_SRV dtls=1 debug_level=2 auth_mode=required \ From 82d549a297df54fd018f6966e49e8360e410cc09 Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Wed, 11 Feb 2026 09:08:03 +0100 Subject: [PATCH 40/61] ssl-opt.sh: Use more diverse MTUs Do not use only power of 2 MTUs. Use diverse MTUs in DTLS reassembly/ fragmenting/proxy tests. Signed-off-by: Ronald Cron --- tests/ssl-opt.sh | 189 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 142 insertions(+), 47 deletions(-) diff --git a/tests/ssl-opt.sh b/tests/ssl-opt.sh index 3ddecf6e09..c9c9ec3929 100755 --- a/tests/ssl-opt.sh +++ b/tests/ssl-opt.sh @@ -10942,7 +10942,7 @@ run_test "DTLS reassembly: more fragmentation (gnutls server)" \ requires_gnutls requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 run_test "DTLS reassembly: more fragmentation, nbio (gnutls server)" \ - "$G_SRV -u --mtu 128" \ + "$G_SRV -u --mtu 109" \ "$P_CLI dtls=1 nbio=2 debug_level=2" \ 0 \ -c "found fragmented DTLS handshake message" \ @@ -10954,7 +10954,7 @@ requires_gnutls requires_config_enabled MBEDTLS_SSL_RENEGOTIATION requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 run_test "DTLS reassembly: fragmentation, renego (gnutls server)" \ - "$G_SRV -u --mtu 256" \ + "$G_SRV -u --mtu 241" \ "$P_CLI debug_level=3 dtls=1 renegotiation=1 renegotiate=1" \ 0 \ -c "found fragmented DTLS handshake message" \ @@ -10995,7 +10995,7 @@ requires_gnutls requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 run_test "DTLS reassembly: some fragmentation (gnutls client)" \ "$P_SRV debug_level=2 dtls=1 auth_mode=required" \ - "$G_NEXT_CLI -u --mtu 256 --insecure 127.0.0.1 --x509certfile $DATA_FILES_PATH/server5.crt --x509keyfile $DATA_FILES_PATH/server5.key" \ + "$G_NEXT_CLI -u --mtu 211 --insecure 127.0.0.1 --x509certfile $DATA_FILES_PATH/server5.crt --x509keyfile $DATA_FILES_PATH/server5.key" \ 0 \ -s "found fragmented DTLS handshake message" \ -s "Certificate handshake message has been buffered and reassembled" \ @@ -11011,7 +11011,7 @@ requires_gnutls requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 run_test "DTLS reassembly: more fragmentation (gnutls client)" \ "$P_SRV debug_level=2 dtls=1" \ - "$G_NEXT_CLI -u --mtu 128 --insecure 127.0.0.1" \ + "$G_NEXT_CLI -u --mtu 103 --insecure 127.0.0.1" \ 0 \ -s "ClientHello handshake message has been buffered and reassembled" \ -S "error" @@ -11020,7 +11020,7 @@ requires_gnutls requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 run_test "DTLS reassembly: more fragmentation, nbio (gnutls client)" \ "$P_SRV debug_level=2 dtls=1 nbio=2" \ - "$G_NEXT_CLI -u --mtu 128 --insecure 127.0.0.1" \ + "$G_NEXT_CLI -u --mtu 103 --insecure 127.0.0.1" \ 0 \ -s "ClientHello handshake message has been buffered and reassembled" \ -S "error" @@ -11051,7 +11051,7 @@ run_test "DTLS reassembly: fragmentation (openssl server)" \ requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 run_test "DTLS reassembly: fragmentation, nbio (openssl server)" \ - "$O_SRV -dtls -mtu 256" \ + "$O_SRV -dtls -mtu 273" \ "$P_CLI dtls=1 nbio=2 debug_level=2" \ 0 \ -c "found fragmented DTLS handshake message" \ @@ -11084,7 +11084,7 @@ run_test "DTLS reassembly: some fragmentation (openssl client)" \ requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 run_test "DTLS reassembly: fragmentation, nbio (openssl client)" \ "$P_SRV debug_level=2 dtls=1 auth_mode=required nbio=2" \ - "$O_NEXT_CLI -dtls -mtu 256 -cert $DATA_FILES_PATH/server5.crt -key $DATA_FILES_PATH/server5.key" \ + "$O_NEXT_CLI -dtls -mtu 269 -cert $DATA_FILES_PATH/server5.crt -key $DATA_FILES_PATH/server5.key" \ 0 \ -s "found fragmented DTLS handshake message" \ -s "Certificate handshake message has been buffered and reassembled" \ @@ -11331,20 +11331,20 @@ run_test "DTLS fragmenting: server (MTU)" \ requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C -requires_max_content_len 1024 +requires_max_content_len 1038 requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 -run_test "DTLS fragmenting: both (MTU=1024)" \ - -p "$P_PXY mtu=1024" \ +run_test "DTLS fragmenting: both (MTU=1038)" \ + -p "$P_PXY mtu=1038" \ "$P_SRV dtls=1 debug_level=2 auth_mode=required \ crt_file=$DATA_FILES_PATH/server7_int-ca.crt \ key_file=$DATA_FILES_PATH/server7.key \ hs_timeout=2500-60000 \ - mtu=1024" \ + mtu=1038" \ "$P_CLI dtls=1 debug_level=2 \ crt_file=$DATA_FILES_PATH/server8_int-ca2.crt \ key_file=$DATA_FILES_PATH/server8.key \ hs_timeout=2500-60000 \ - mtu=1024" \ + mtu=1038" \ 0 \ -s "found fragmented DTLS handshake message" \ -c "found fragmented DTLS handshake message" \ @@ -11354,20 +11354,20 @@ run_test "DTLS fragmenting: both (MTU=1024)" \ requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C requires_hash_alg SHA_256 -requires_max_content_len 512 -run_test "DTLS fragmenting: both (MTU=512)" \ - -p "$P_PXY mtu=512" \ +requires_max_content_len 509 +run_test "DTLS fragmenting: both (MTU=509)" \ + -p "$P_PXY mtu=509" \ "$P_SRV dtls=1 debug_level=2 auth_mode=required \ crt_file=$DATA_FILES_PATH/server7_int-ca.crt \ key_file=$DATA_FILES_PATH/server7.key \ hs_timeout=2500-60000 \ - mtu=512" \ + mtu=509" \ "$P_CLI dtls=1 debug_level=2 \ crt_file=$DATA_FILES_PATH/server8_int-ca2.crt \ key_file=$DATA_FILES_PATH/server8.key \ force_ciphersuite=TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256 \ hs_timeout=2500-60000 \ - mtu=512" \ + mtu=509" \ 0 \ -s "found fragmented DTLS handshake message" \ -c "found fragmented DTLS handshake message" \ @@ -11377,7 +11377,7 @@ run_test "DTLS fragmenting: both (MTU=512)" \ # maximum application data payload per record may be small with an MTU of 128. # For example, with TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384, this maximum is # 35 bytes. We therefore reduce the size of the client request and the server -# response in this test. +# response in this test and the two following tests. requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_max_content_len 128 run_test "DTLS fragmenting: both (MTU=128)" \ @@ -11404,6 +11404,58 @@ run_test "DTLS fragmenting: both (MTU=128)" \ -c "fragmenting CertificateVerify handshake message" \ -C "error" +requires_config_enabled MBEDTLS_SSL_PROTO_DTLS +requires_max_content_len 2048 +run_test "DTLS fragmenting: both (MTU=107)" \ + -p "$P_PXY mtu=107" \ + "$P_SRV dtls=1 debug_level=5 auth_mode=required \ + crt_file=$DATA_FILES_PATH/server7_int-ca.crt \ + key_file=$DATA_FILES_PATH/server7.key \ + response_size=8 \ + hs_timeout=2500-60000 \ + mtu=107" \ + "$P_CLI dtls=1 debug_level=2 \ + crt_file=$DATA_FILES_PATH/server8_int-ca2.crt \ + key_file=$DATA_FILES_PATH/server8.key \ + request_size=8 \ + hs_timeout=2500-60000 \ + mtu=107" \ + 0 \ + -s "found fragmented DTLS handshake message" \ + -s "fragmenting Certificate handshake message" \ + -s "fragmenting ServerKeyExchange handshake message" \ + -c "found fragmented DTLS handshake message" \ + -c "fragmenting ClientHello handshake message" \ + -c "fragmenting Certificate handshake message" \ + -c "fragmenting CertificateVerify handshake message" \ + -C "error" + +requires_config_enabled MBEDTLS_SSL_PROTO_DTLS +requires_max_content_len 2048 +run_test "DTLS fragmenting: both (MTU=133)" \ + -p "$P_PXY mtu=133" \ + "$P_SRV dtls=1 debug_level=5 auth_mode=required \ + crt_file=$DATA_FILES_PATH/server7_int-ca.crt \ + key_file=$DATA_FILES_PATH/server7.key \ + response_size=8 \ + hs_timeout=2500-60000 \ + mtu=133" \ + "$P_CLI dtls=1 debug_level=2 \ + crt_file=$DATA_FILES_PATH/server8_int-ca2.crt \ + key_file=$DATA_FILES_PATH/server8.key \ + request_size=8 \ + hs_timeout=2500-60000 \ + mtu=133" \ + 0 \ + -s "found fragmented DTLS handshake message" \ + -s "fragmenting Certificate handshake message" \ + -s "fragmenting ServerKeyExchange handshake message" \ + -c "found fragmented DTLS handshake message" \ + -c "fragmenting ClientHello handshake message" \ + -c "fragmenting Certificate handshake message" \ + -c "fragmenting CertificateVerify handshake message" \ + -C "error" + # Test for automatic MTU reduction on repeated resend. # Forcing ciphersuite for this test to fit the MTU of 508 with full config. # The ratio of max/min timeout should ideally equal 4 to accept two @@ -11780,12 +11832,12 @@ requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C requires_gnutls requires_max_content_len 2048 -run_test "DTLS fragmenting: MTU=512, gnutls server, DTLS 1.2" \ +run_test "DTLS fragmenting: MTU=501, gnutls server, DTLS 1.2" \ "$G_SRV -u" \ "$P_CLI dtls=1 debug_level=2 \ crt_file=$DATA_FILES_PATH/server8_int-ca2.crt \ key_file=$DATA_FILES_PATH/server8.key \ - mtu=512 force_version=dtls12" \ + mtu=501 force_version=dtls12" \ 0 \ -c "fragmenting Certificate handshake message" \ -C "error" @@ -11794,12 +11846,13 @@ requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C requires_gnutls requires_max_content_len 2048 -run_test "DTLS fragmenting: MTU=128, gnutls server, DTLS 1.2" \ +run_test "DTLS fragmenting: MTU=110, gnutls server, DTLS 1.2" \ "$G_NEXT_SRV -u" \ "$P_CLI dtls=1 debug_level=2 \ crt_file=$DATA_FILES_PATH/server8_int-ca2.crt \ key_file=$DATA_FILES_PATH/server8.key \ - mtu=128 force_version=dtls12" \ + request_size=35 \ + mtu=110 force_version=dtls12" \ 0 \ -c "fragmenting ClientHello handshake message" \ -c "fragmenting Certificate handshake message" \ @@ -11817,11 +11870,25 @@ requires_config_enabled MBEDTLS_RSA_C requires_gnutls requires_not_i686 requires_max_content_len 2048 -run_test "DTLS fragmenting: MTU=512, gnutls client, DTLS 1.2" \ +run_test "DTLS fragmenting: MTU=536, gnutls client, DTLS 1.2" \ "$P_SRV dtls=1 debug_level=2 \ crt_file=$DATA_FILES_PATH/server7_int-ca.crt \ key_file=$DATA_FILES_PATH/server7.key \ - mtu=512 force_version=dtls12" \ + mtu=536 force_version=dtls12" \ + "$G_CLI -u --insecure 127.0.0.1" \ + 0 \ + -s "fragmenting Certificate handshake message" + +requires_config_enabled MBEDTLS_SSL_PROTO_DTLS +requires_config_enabled MBEDTLS_RSA_C +requires_gnutls +requires_not_i686 +requires_max_content_len 2048 +run_test "DTLS fragmenting: MTU=149, gnutls client, DTLS 1.2" \ + "$P_SRV dtls=1 debug_level=2 \ + crt_file=$DATA_FILES_PATH/server7_int-ca.crt \ + key_file=$DATA_FILES_PATH/server7.key \ + mtu=149 force_version=dtls12" \ "$G_CLI -u --insecure 127.0.0.1" \ 0 \ -s "fragmenting Certificate handshake message" @@ -11829,12 +11896,12 @@ run_test "DTLS fragmenting: MTU=512, gnutls client, DTLS 1.2" \ requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C requires_max_content_len 2048 -run_test "DTLS fragmenting: MTU=512, openssl server, DTLS 1.2" \ +run_test "DTLS fragmenting: MTU=525, openssl server, DTLS 1.2" \ "$O_SRV -dtls1_2 -verify 10" \ "$P_CLI dtls=1 debug_level=2 \ crt_file=$DATA_FILES_PATH/server8_int-ca2.crt \ key_file=$DATA_FILES_PATH/server8.key \ - mtu=512 force_version=dtls12" \ + mtu=525 force_version=dtls12" \ 0 \ -c "fragmenting Certificate handshake message" \ -C "error" @@ -11846,13 +11913,13 @@ run_test "DTLS fragmenting: MTU=512, openssl server, DTLS 1.2" \ requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C requires_max_content_len 2048 -run_test "DTLS fragmenting: MTU=128, openssl server, DTLS 1.2" \ +run_test "DTLS fragmenting: MTU=130, openssl server, DTLS 1.2" \ "$O_NEXT_SRV -dtls1_2 -verify 10" \ "$P_CLI dtls=1 debug_level=2 \ crt_file=$DATA_FILES_PATH/server8_int-ca2.crt \ key_file=$DATA_FILES_PATH/server8.key \ request_size=8 \ - mtu=128 force_version=dtls12" \ + mtu=130 force_version=dtls12" \ 0 \ -c "fragmenting ClientHello handshake message" \ -c "fragmenting Certificate handshake message" \ @@ -11870,6 +11937,18 @@ run_test "DTLS fragmenting: MTU=512, openssl client, DTLS 1.2" \ 0 \ -s "fragmenting Certificate handshake message" +requires_config_enabled MBEDTLS_SSL_PROTO_DTLS +requires_config_enabled MBEDTLS_RSA_C +requires_max_content_len 2048 +run_test "DTLS fragmenting: MTU=131, openssl client, DTLS 1.2" \ + "$P_SRV dtls=1 debug_level=2 \ + crt_file=$DATA_FILES_PATH/server7_int-ca.crt \ + key_file=$DATA_FILES_PATH/server7.key \ + mtu=131 force_version=dtls12" \ + "$O_CLI -dtls1_2" \ + 0 \ + -s "fragmenting Certificate handshake message" + # interop tests for DTLS fragmentating with unreliable connection # # again we just want to test that the we fragment in a way that @@ -11879,13 +11958,13 @@ requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C client_needs_more_time 4 requires_max_content_len 2048 -run_test "DTLS fragmenting: 3d, MTU=512, gnutls server, DTLS 1.2" \ +run_test "DTLS fragmenting: 3d, MTU=434, gnutls server, DTLS 1.2" \ -p "$P_PXY drop=8 delay=8 duplicate=8" \ "$G_NEXT_SRV -u" \ "$P_CLI dgram_packing=0 dtls=1 debug_level=2 \ crt_file=$DATA_FILES_PATH/server8_int-ca2.crt \ key_file=$DATA_FILES_PATH/server8.key \ - hs_timeout=250-60000 mtu=512 force_version=dtls12" \ + hs_timeout=250-60000 mtu=434 force_version=dtls12" \ 0 \ -c "fragmenting Certificate handshake message" \ -C "error" @@ -11895,13 +11974,14 @@ requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C client_needs_more_time 6 requires_max_content_len 2048 -run_test "DTLS fragmenting: 3d, MTU=128, gnutls server, DTLS 1.2" \ +run_test "DTLS fragmenting: 3d, MTU=103, gnutls server, DTLS 1.2" \ -p "$P_PXY drop=8 delay=8 duplicate=8" \ "$G_NEXT_SRV -u" \ "$P_CLI dgram_packing=0 dtls=1 debug_level=2 \ crt_file=$DATA_FILES_PATH/server8_int-ca2.crt \ key_file=$DATA_FILES_PATH/server8.key \ - hs_timeout=250-60000 mtu=128 force_version=dtls12" \ + request_size=35 \ + hs_timeout=250-60000 mtu=103 force_version=dtls12" \ 0 \ -c "fragmenting ClientHello handshake message" \ -c "fragmenting Certificate handshake message" \ @@ -11912,12 +11992,27 @@ requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C client_needs_more_time 4 requires_max_content_len 2048 -run_test "DTLS fragmenting: 3d, MTU=512, gnutls client, DTLS 1.2" \ +run_test "DTLS fragmenting: 3d, MTU=614, gnutls client, DTLS 1.2" \ -p "$P_PXY drop=8 delay=8 duplicate=8" \ "$P_SRV dtls=1 debug_level=2 \ crt_file=$DATA_FILES_PATH/server7_int-ca.crt \ key_file=$DATA_FILES_PATH/server7.key \ - hs_timeout=250-60000 mtu=512 force_version=dtls12" \ + hs_timeout=250-60000 mtu=614 force_version=dtls12" \ + "$G_NEXT_CLI -u --insecure 127.0.0.1" \ + 0 \ + -s "fragmenting Certificate handshake message" + +requires_gnutls_next +requires_config_enabled MBEDTLS_SSL_PROTO_DTLS +requires_config_enabled MBEDTLS_RSA_C +client_needs_more_time 4 +requires_max_content_len 2048 +run_test "DTLS fragmenting: 3d, MTU=116, gnutls client, DTLS 1.2" \ + -p "$P_PXY drop=8 delay=8 duplicate=8" \ + "$P_SRV dtls=1 debug_level=2 \ + crt_file=$DATA_FILES_PATH/server7_int-ca.crt \ + key_file=$DATA_FILES_PATH/server7.key \ + hs_timeout=250-60000 mtu=116 force_version=dtls12" \ "$G_NEXT_CLI -u --insecure 127.0.0.1" \ 0 \ -s "fragmenting Certificate handshake message" @@ -11929,13 +12024,13 @@ requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C client_needs_more_time 4 requires_max_content_len 2048 -run_test "DTLS fragmenting: 3d, MTU=512, openssl server, DTLS 1.2" \ +run_test "DTLS fragmenting: 3d, MTU=541, openssl server, DTLS 1.2" \ -p "$P_PXY drop=8 delay=8 duplicate=8" \ "$O_NEXT_SRV -dtls1_2 -verify 10" \ "$P_CLI dtls=1 debug_level=2 \ crt_file=$DATA_FILES_PATH/server8_int-ca2.crt \ key_file=$DATA_FILES_PATH/server8.key \ - hs_timeout=250-60000 mtu=512 force_version=dtls12" \ + hs_timeout=250-60000 mtu=541 force_version=dtls12" \ 0 \ -c "fragmenting Certificate handshake message" \ -C "error" @@ -11949,14 +12044,14 @@ requires_config_enabled MBEDTLS_SSL_PROTO_DTLS requires_config_enabled MBEDTLS_RSA_C client_needs_more_time 4 requires_max_content_len 2048 -run_test "DTLS fragmenting: 3d, MTU=128, openssl server, DTLS 1.2" \ +run_test "DTLS fragmenting: 3d, MTU=108, openssl server, DTLS 1.2" \ -p "$P_PXY drop=8 delay=8 duplicate=8" \ "$O_NEXT_SRV -dtls1_2 -verify 10" \ "$P_CLI dtls=1 debug_level=2 \ crt_file=$DATA_FILES_PATH/server8_int-ca2.crt \ key_file=$DATA_FILES_PATH/server8.key \ request_size=8 \ - hs_timeout=250-60000 mtu=128 force_version=dtls12" \ + hs_timeout=250-60000 mtu=108 force_version=dtls12" \ 0 \ -c "fragmenting ClientHello handshake message" \ -c "fragmenting Certificate handshake message" \ @@ -13056,7 +13151,7 @@ not_with_valgrind # risk of non-mbedtls peer timing out requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 run_test "DTLS proxy: 3d, openssl server, fragmentation" \ -p "$P_PXY drop=5 delay=5 duplicate=5 protect_hvr=1" \ - "$O_NEXT_SRV -dtls1_2 -mtu 256" \ + "$O_NEXT_SRV -dtls1_2 -mtu 277" \ "$P_CLI dgram_packing=0 dtls=1 debug_level=2 hs_timeout=500-60000 tickets=0" \ 0 \ -c "HTTP/1.0 200 OK" \ @@ -13068,7 +13163,7 @@ not_with_valgrind # risk of non-mbedtls peer timing out requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 run_test "DTLS proxy: 3d, openssl server, fragmentation, nbio" \ -p "$P_PXY drop=5 delay=5 duplicate=5 protect_hvr=1" \ - "$O_NEXT_SRV -dtls1_2 -mtu 256" \ + "$O_NEXT_SRV -dtls1_2 -mtu 268" \ "$P_CLI dgram_packing=0 dtls=1 debug_level=2 hs_timeout=500-60000 nbio=2 tickets=0" \ 0 \ -c "HTTP/1.0 200 OK" \ @@ -13092,7 +13187,7 @@ requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 run_test "DTLS proxy: 3d, openssl client, fragmentation" \ -p "$P_PXY drop=5 delay=5 duplicate=5" \ "$P_SRV debug_level=2 dgram_packing=0 auth_mode=required dtls=1 hs_timeout=500-60000 tickets=0" \ - "$O_NEXT_CLI -dtls1_2 -mtu 256 -cert $DATA_FILES_PATH/server5.crt -key $DATA_FILES_PATH/server5.key" \ + "$O_NEXT_CLI -dtls1_2 -mtu 260 -cert $DATA_FILES_PATH/server5.crt -key $DATA_FILES_PATH/server5.key" \ 0 \ -s "HTTP/1.0 200 OK" \ -s "found fragmented DTLS handshake message" \ @@ -13105,7 +13200,7 @@ requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 run_test "DTLS proxy: 3d, openssl client, fragmentation, nbio" \ -p "$P_PXY drop=5 delay=5 duplicate=5" \ "$P_SRV debug_level=2 dgram_packing=0 auth_mode=required dtls=1 hs_timeout=500-60000 nbio=2 tickets=0" \ - "$O_NEXT_CLI -dtls1_2 -mtu 256 -cert $DATA_FILES_PATH/server5.crt -key $DATA_FILES_PATH/server5.key" \ + "$O_NEXT_CLI -dtls1_2 -mtu 259 -cert $DATA_FILES_PATH/server5.crt -key $DATA_FILES_PATH/server5.key" \ 0 \ -s "HTTP/1.0 200 OK" \ -s "found fragmented DTLS handshake message" \ @@ -13129,7 +13224,7 @@ not_with_valgrind # risk of non-mbedtls peer timing out requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 run_test "DTLS proxy: 3d, gnutls server, fragmentation" \ -p "$P_PXY drop=5 delay=5 duplicate=5" \ - "$G_NEXT_SRV -u --mtu 512" \ + "$G_NEXT_SRV -u --mtu 499" \ "$P_CLI dgram_packing=0 dtls=1 debug_level=2 hs_timeout=500-60000" \ 0 \ -s "Extra-header:" \ @@ -13142,7 +13237,7 @@ not_with_valgrind # risk of non-mbedtls peer timing out requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 run_test "DTLS proxy: 3d, gnutls server, fragmentation, nbio" \ -p "$P_PXY drop=5 delay=5 duplicate=5" \ - "$G_NEXT_SRV -u --mtu 512" \ + "$G_NEXT_SRV -u --mtu 528" \ "$P_CLI dgram_packing=0 dtls=1 debug_level=2 hs_timeout=500-60000 nbio=2" \ 0 \ -s "Extra-header:" \ @@ -13160,7 +13255,7 @@ run_test "DTLS proxy: 3d, gnutls client" \ 0 \ -s "HTTP/1.0 200 OK" -# Set the MTU to 128 bytes. The ClientHello is not guaranteed to be surely +# Set the MTU to 131 bytes. The ClientHello is not guaranteed to be surely # fragmented but it is very likely. For example, the ClientHello sent by the # GnuTLS 3.7.2 client is 206 bytes in this test. We expect ClientHello # fragmentation to remain the case across GnuTLS version updates. Avoid using a @@ -13173,7 +13268,7 @@ requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 run_test "DTLS proxy: 3d, gnutls client, fragmentation" \ -p "$P_PXY drop=5 delay=5 duplicate=5" \ "$P_SRV dgram_packing=0 dtls=1 debug_level=2" \ - "$G_NEXT_CLI -u --mtu 128 --insecure 127.0.0.1" \ + "$G_NEXT_CLI -u --mtu 131 --insecure 127.0.0.1" \ 0 \ -s "HTTP/1.0 200 OK" \ -s "ClientHello handshake message has been buffered and reassembled" @@ -13185,7 +13280,7 @@ requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 run_test "DTLS proxy: 3d, gnutls client, fragmentation, nbio=2" \ -p "$P_PXY drop=5 delay=5 duplicate=5" \ "$P_SRV dgram_packing=0 dtls=1 debug_level=2 nbio=2" \ - "$G_NEXT_CLI -u --mtu 128 --insecure 127.0.0.1" \ + "$G_NEXT_CLI -u --mtu 135 --insecure 127.0.0.1" \ 0 \ -s "HTTP/1.0 200 OK" \ -s "ClientHello handshake message has been buffered and reassembled" From 1bdb0901c90dfbffc29e674f70219137860f1398 Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Fri, 13 Feb 2026 10:52:28 +0100 Subject: [PATCH 41/61] Update mbedtls_ssl_handshake() documentation Signed-off-by: Ronald Cron --- include/mbedtls/ssl.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/include/mbedtls/ssl.h b/include/mbedtls/ssl.h index 3cdddf7d72..a81bb686e3 100644 --- a/include/mbedtls/ssl.h +++ b/include/mbedtls/ssl.h @@ -5084,13 +5084,6 @@ int mbedtls_ssl_get_session(const mbedtls_ssl_context *ssl, * 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 From 38213a785666d65cf787c3f2a6cd9c4943aa1fa9 Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Fri, 13 Feb 2026 11:03:13 +0100 Subject: [PATCH 42/61] Add change log Signed-off-by: Ronald Cron --- ChangeLog.d/dtls-client-hello-defragmentation.txt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 ChangeLog.d/dtls-client-hello-defragmentation.txt diff --git a/ChangeLog.d/dtls-client-hello-defragmentation.txt b/ChangeLog.d/dtls-client-hello-defragmentation.txt new file mode 100644 index 0000000000..f5ff0b754c --- /dev/null +++ b/ChangeLog.d/dtls-client-hello-defragmentation.txt @@ -0,0 +1,5 @@ +Bugfix + * Support re-assembly of fragmented DTLS 1.2 ClientHello in Mbed TLS server. + * Support re-assembly of fragmented TLS 1.2 ClientHello in Mbed TLS server + even if TLS 1.3 support is disabled. This removes the main limitation on + support for re-assembly of fragmented handshake messages in TLS 1.2. From 97a25e4c68120dc3231362da745edc1373686e28 Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Mon, 23 Feb 2026 14:09:13 +0100 Subject: [PATCH 43/61] ssl_tls12_server.c: Update hs status after some validations of the ClientHello Signed-off-by: Ronald Cron --- library/ssl_tls12_server.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/library/ssl_tls12_server.c b/library/ssl_tls12_server.c index 47803dc85c..eae4996fc8 100644 --- a/library/ssl_tls12_server.c +++ b/library/ssl_tls12_server.c @@ -924,12 +924,6 @@ static int ssl_parse_client_hello(mbedtls_ssl_context *ssl) return ret; } - ret = mbedtls_ssl_update_handshake_status(ssl); - if (0 != ret) { - MBEDTLS_SSL_DEBUG_RET(1, ("mbedtls_ssl_update_handshake_status"), ret); - return ret; - } - buf = ssl->in_msg; msg_len = ssl->in_hslen; @@ -1135,6 +1129,21 @@ static int ssl_parse_client_hello(mbedtls_ssl_context *ssl) ext_len = 0; } + /* + * Update the handshake checksum after performing preliminary + * validation of the ClientHello and before parsing its extensions. + * + * The checksum must be updated before parsing the extensions because + * ssl_parse_session_ticket_ext() may decrypt the ticket in place and + * therefore modify the ClientHello message. This occurs when using + * the Mbed TLS ssl_ticket.c implementation. + */ + ret = mbedtls_ssl_update_handshake_status(ssl); + if (0 != ret) { + MBEDTLS_SSL_DEBUG_RET(1, ("mbedtls_ssl_update_handshake_status"), ret); + return ret; + } + ext = buf + ext_offset + 2; MBEDTLS_SSL_DEBUG_BUF(3, "client hello extensions", ext, ext_len); @@ -1276,7 +1285,11 @@ static int ssl_parse_client_hello(mbedtls_ssl_context *ssl) #if defined(MBEDTLS_SSL_SESSION_TICKETS) case MBEDTLS_TLS_EXT_SESSION_TICKET: MBEDTLS_SSL_DEBUG_MSG(3, ("found session ticket extension")); - + /* + * If the Mbed TLS ssl_ticket.c implementation is used, the + * ticket is decrypted in place. This modifies the ClientHello + * message in the input buffer. + */ ret = ssl_parse_session_ticket_ext(ssl, ext + 4, ext_size); if (ret != 0) { return ret; From 6b529ff50de49f3f7e770480093a4d08ab50460f Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Wed, 25 Feb 2026 17:49:35 +0100 Subject: [PATCH 44/61] component_test_tls1_2_ccm_psk_dtls_psa: Enable MBEDTLS_HAVE_TIME As done in component_test_tls1_2_ccm_psk_dtls_legacy enable MBEDTLS_HAVE_TIME in component_test_tls1_2_ccm_psk_dtls_psa. Signed-off-by: Ronald Cron --- tests/scripts/components-configuration-tls.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/scripts/components-configuration-tls.sh b/tests/scripts/components-configuration-tls.sh index d2a6049777..731f695bd5 100644 --- a/tests/scripts/components-configuration-tls.sh +++ b/tests/scripts/components-configuration-tls.sh @@ -420,6 +420,7 @@ component_test_tls1_2_ccm_psk_dtls_psa () { cp configs/config-ccm-psk-dtls1_2.h "$CONFIG_H" scripts/config.py set MBEDTLS_PSA_CRYPTO_C scripts/config.py set MBEDTLS_USE_PSA_CRYPTO + scripts/config.py set MBEDTLS_HAVE_TIME # test-ref-configs works by overwriting mbedtls_config.h; this makes cmake # want to re-generate generated files that depend on it, quite correctly. # However this doesn't work as the generation script expects a specific From 7d022d3275453b605ad258cefa58b951c7067020 Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Wed, 25 Feb 2026 17:52:44 +0100 Subject: [PATCH 45/61] ssl-opt.sh: DTLS fragmenting: Do not check for ServerKeyExchange fragmentation In DTLS fragmenting tests, do not check for ServerKeyExchange message fragmentation as depending on the configuration and consequently the chosen ciphersuite the server may not send the message. Signed-off-by: Ronald Cron --- tests/ssl-opt.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/ssl-opt.sh b/tests/ssl-opt.sh index c9c9ec3929..6e91581cc3 100755 --- a/tests/ssl-opt.sh +++ b/tests/ssl-opt.sh @@ -11397,7 +11397,6 @@ run_test "DTLS fragmenting: both (MTU=128)" \ 0 \ -s "found fragmented DTLS handshake message" \ -s "fragmenting Certificate handshake message" \ - -s "fragmenting ServerKeyExchange handshake message" \ -c "found fragmented DTLS handshake message" \ -c "fragmenting ClientHello handshake message" \ -c "fragmenting Certificate handshake message" \ @@ -11423,7 +11422,6 @@ run_test "DTLS fragmenting: both (MTU=107)" \ 0 \ -s "found fragmented DTLS handshake message" \ -s "fragmenting Certificate handshake message" \ - -s "fragmenting ServerKeyExchange handshake message" \ -c "found fragmented DTLS handshake message" \ -c "fragmenting ClientHello handshake message" \ -c "fragmenting Certificate handshake message" \ @@ -11449,7 +11447,6 @@ run_test "DTLS fragmenting: both (MTU=133)" \ 0 \ -s "found fragmented DTLS handshake message" \ -s "fragmenting Certificate handshake message" \ - -s "fragmenting ServerKeyExchange handshake message" \ -c "found fragmented DTLS handshake message" \ -c "fragmenting ClientHello handshake message" \ -c "fragmenting Certificate handshake message" \ From bc69abd2cbc29842a91d64b248d4f43cfeff4870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Fri, 27 Feb 2026 10:13:04 +0100 Subject: [PATCH 46/61] PK: use existing macros and functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/pk.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/library/pk.c b/library/pk.c index 88eee3c515..e4a3ac0375 100644 --- a/library/pk.c +++ b/library/pk.c @@ -47,8 +47,7 @@ PSA_KEY_EXPORT_RSA_PUBLIC_KEY_MAX_SIZE(PSA_VENDOR_RSA_MAX_KEY_BITS) #define MBEDTLS_PK_MAX_PUBKEY_RAW_LEN 0 -#if (defined(MBEDTLS_ECP_C) || \ - (defined(MBEDTLS_USE_PSA_CRYPTO) && defined(PSA_WANT_KEY_TYPE_ECC_PUBLIC_KEY))) && \ +#if defined(MBEDTLS_PK_HAVE_ECC_KEYS) && \ MBEDTLS_PK_MAX_EC_PUBKEY_RAW_LEN > MBEDTLS_PK_MAX_PUBKEY_RAW_LEN #undef MBEDTLS_PK_MAX_PUBKEY_RAW_LEN #define MBEDTLS_PK_MAX_PUBKEY_RAW_LEN MBEDTLS_PK_MAX_EC_PUBKEY_RAW_LEN @@ -720,8 +719,7 @@ static int import_pair_into_psa(const mbedtls_pk_context *pk, key_data, key_length, key_id)); cleanup_rsa: - mbedtls_platform_zeroize(key_buffer, key_buffer_size); - mbedtls_free(key_buffer); + mbedtls_zeroize_and_free(key_buffer, key_buffer_size); return ret; } #endif /* MBEDTLS_RSA_C */ @@ -924,8 +922,7 @@ static int is_valid_for_pk(psa_key_type_t key_type) } #endif #if defined(MBEDTLS_RSA_C) - if (key_type == PSA_KEY_TYPE_RSA_PUBLIC_KEY || - key_type == PSA_KEY_TYPE_RSA_KEY_PAIR) { + if (PSA_KEY_TYPE_IS_RSA(key_type)) { return 1; } #endif From 81ecc37372863e1478d7bb93f794a6261924286e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Fri, 27 Feb 2026 10:16:20 +0100 Subject: [PATCH 47/61] PK: clarify comment about key export MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/pk_internal.h | 1 + 1 file changed, 1 insertion(+) diff --git a/library/pk_internal.h b/library/pk_internal.h index 202f011ea3..ab42a0b624 100644 --- a/library/pk_internal.h +++ b/library/pk_internal.h @@ -53,6 +53,7 @@ * then we export keys on the stack, and otherwise we use the heap. * * RSA can either be used directly or indirectly via opaque keys if enabled. + * (RSA_ALT is not relevant here as we can't export from such contexts.) */ #if !defined(MBEDTLS_RSA_C) && \ !(defined(MBEDTLS_USE_PSA_CRYPTO) && defined(PSA_WANT_KEY_TYPE_RSA_PUBLIC_KEY)) From 571d78361ac8bdca44d15a96935c613588719f79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Fri, 27 Feb 2026 10:17:28 +0100 Subject: [PATCH 48/61] PK: zeroize dummy signature just to be sure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The signature of a dummy hash for which no pre-image is know is probably not sensitive, but zeroize it anyway. Signed-off-by: Manuel Pégourié-Gonnard --- library/pk_wrap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/pk_wrap.c b/library/pk_wrap.c index c174ce3b42..cafcb87d0d 100644 --- a/library/pk_wrap.c +++ b/library/pk_wrap.c @@ -1365,7 +1365,7 @@ static int rsa_alt_check_pair(mbedtls_pk_context *pub, mbedtls_pk_context *prv, } cleanup: - mbedtls_free(sig); + mbedtls_zeroize_and_free(sig, sig_size); return ret; } #endif /* MBEDTLS_RSA_C */ From 127b0352b3eee25fa62a3f6ab447c3d1cd8242bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Fri, 27 Feb 2026 10:29:15 +0100 Subject: [PATCH 49/61] PK: move another large buffer to the heap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Was previously missed as it only exists when USE_PSA_CRYPTO is enabled. Signed-off-by: Manuel Pégourié-Gonnard --- library/pk_wrap.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/library/pk_wrap.c b/library/pk_wrap.c index cafcb87d0d..8c038e7db6 100644 --- a/library/pk_wrap.c +++ b/library/pk_wrap.c @@ -288,9 +288,6 @@ static int rsa_decrypt_wrap(mbedtls_pk_context *pk, psa_algorithm_t psa_md_alg, decrypt_alg; psa_status_t status; int key_len; - unsigned char buf[MBEDTLS_PK_RSA_PRV_DER_MAX_BYTES]; - unsigned char *p = buf + sizeof(buf); - ((void) f_rng); ((void) p_rng); @@ -298,6 +295,13 @@ static int rsa_decrypt_wrap(mbedtls_pk_context *pk, return MBEDTLS_ERR_RSA_BAD_INPUT_DATA; } + const size_t key_bits = mbedtls_pk_get_bitlen(pk); + /* mbedtls_rsa_write_key() uses the same format as PSA export, which + * actually calls it under the hood, so we can use the PSA size macro. */ + const size_t buf_size = PSA_KEY_EXPORT_RSA_KEY_PAIR_MAX_SIZE(key_bits); + unsigned char *buf = mbedtls_calloc(1, buf_size); + + unsigned char *p = buf + buf_size; key_len = mbedtls_rsa_write_key(rsa, buf, &p); if (key_len <= 0) { return MBEDTLS_ERR_PK_BAD_INPUT_DATA; @@ -314,7 +318,7 @@ static int rsa_decrypt_wrap(mbedtls_pk_context *pk, psa_set_key_algorithm(&attributes, decrypt_alg); status = psa_import_key(&attributes, - buf + sizeof(buf) - key_len, key_len, + buf + buf_size - key_len, key_len, &key_id); if (status != PSA_SUCCESS) { ret = PSA_PK_TO_MBEDTLS_ERR(status); @@ -333,7 +337,7 @@ static int rsa_decrypt_wrap(mbedtls_pk_context *pk, ret = 0; cleanup: - mbedtls_platform_zeroize(buf, sizeof(buf)); + mbedtls_zeroize_and_free(buf, buf_size); status = psa_destroy_key(key_id); if (ret == 0 && status != PSA_SUCCESS) { ret = PSA_PK_TO_MBEDTLS_ERR(status); From 023c51b28227741389e3c7434b421da8a3e832a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Fri, 27 Feb 2026 10:37:34 +0100 Subject: [PATCH 50/61] PK: adjust size macro in case PSA is disabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/pk_internal.h | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/library/pk_internal.h b/library/pk_internal.h index ab42a0b624..d1c26421d4 100644 --- a/library/pk_internal.h +++ b/library/pk_internal.h @@ -61,12 +61,21 @@ #endif #if defined(PK_EXPORT_KEYS_ON_THE_STACK) -/* We know for ECC, pubkey are longer than privkeys, but double check */ +/* We know for ECC, pubkey are longer than privkeys, but double check. + * Also, take the maximum size of legacy and PSA, as PSA might be disabled. */ #define PK_EXPORT_KEY_STACK_BUFFER_SIZE MBEDTLS_PSA_MAX_EC_PUBKEY_LENGTH -#if MBEDTLS_PSA_MAX_EC_KEY_PAIR_LENGTH > PK_EXPORT_KEY_STACK_BUFFER_SIZE +#if PK_EXPORT_KEY_STACK_BUFFER_SIZE < MBEDTLS_PSA_MAX_EC_KEY_PAIR_LENGTH #undef PK_EXPORT_KEY_STACK_BUFFER_SIZE #define PK_EXPORT_KEY_STACK_BUFFER_SIZE MBEDTLS_PSA_MAX_EC_KEY_PAIR_LENGTH #endif +#if PK_EXPORT_KEY_STACK_BUFFER_SIZE < MBEDTLS_ECP_MAX_BYTES +#undef PK_EXPORT_KEY_STACK_BUFFER_SIZE +#define PK_EXPORT_KEY_STACK_BUFFER_SIZE MBEDTLS_ECP_MAX_BYTES +#endif +#if PK_EXPORT_KEY_STACK_BUFFER_SIZE < MBEDTLS_ECP_MAX_PT_LEN +#undef PK_EXPORT_KEY_STACK_BUFFER_SIZE +#define PK_EXPORT_KEY_STACK_BUFFER_SIZE MBEDTLS_ECP_MAX_PT_LEN +#endif #endif #if defined(MBEDTLS_PK_HAVE_ECC_KEYS) && !defined(MBEDTLS_PK_USE_PSA_EC_DATA) From 1fee3da79aa529bd0221823829ecc8826f9edaf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Fri, 27 Feb 2026 10:48:48 +0100 Subject: [PATCH 51/61] PK: require more option combinations in tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- .../test_suite_config.crypto_combinations.data | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/suites/test_suite_config.crypto_combinations.data b/tests/suites/test_suite_config.crypto_combinations.data index 0f8585a519..fe3899f83c 100644 --- a/tests/suites/test_suite_config.crypto_combinations.data +++ b/tests/suites/test_suite_config.crypto_combinations.data @@ -13,10 +13,18 @@ Config: ECC: Montgomery curves only depends_on:!MBEDTLS_ECP_SHORT_WEIERSTRASS_ENABLED:MBEDTLS_ECP_MONTGOMERY_ENABLED pass: -Config: PK export keys on the stack (ECC is the only PK key type) -depends_on:PK_EXPORT_KEYS_ON_THE_STACK +Config: PK export keys on the stack (PK is ECC-only) (USE_PSA) +depends_on:PK_EXPORT_KEYS_ON_THE_STACK:MBEDTLS_USE_PSA_CRYPTO pass: -Config: PK export keys on the heap (ECC is not the only PK key type) -depends_on:!PK_EXPORT_KEYS_ON_THE_STACK +Config: PK export keys on the stack (PK is ECC-only) (!USE_PSA) +depends_on:PK_EXPORT_KEYS_ON_THE_STACK:!MBEDTLS_USE_PSA_CRYPTO +pass: + +Config: PK export keys on the heap (PK is not ECC-only) (USE_PSA) +depends_on:!PK_EXPORT_KEYS_ON_THE_STACK:MBEDTLS_USE_PSA_CRYPTO +pass: + +Config: PK export keys on the heap (PK is not ECC-only) (!USE_PSA) +depends_on:!PK_EXPORT_KEYS_ON_THE_STACK:!MBEDTLS_USE_PSA_CRYPTO pass: From 4c4cfe98de970359ddb44a1a2a2ceb2e1b6498d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Tue, 3 Mar 2026 09:50:53 +0100 Subject: [PATCH 52/61] PK: use PSA size macros when exporting to PSA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/pk.c | 2 +- library/pk_wrap.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library/pk.c b/library/pk.c index e4a3ac0375..b5d76dbd91 100644 --- a/library/pk.c +++ b/library/pk.c @@ -1245,7 +1245,7 @@ int mbedtls_pk_verify_ext(mbedtls_pk_type_t type, const void *options, #if defined(MBEDTLS_USE_PSA_CRYPTO) if (pss_opts->mgf1_hash_id == md_alg) { - unsigned char buf[MBEDTLS_PK_RSA_PUB_DER_MAX_BYTES]; + unsigned char buf[PSA_KEY_EXPORT_RSA_PUBLIC_KEY_MAX_SIZE(PSA_VENDOR_RSA_MAX_KEY_BITS)]; unsigned char *p; int key_len; size_t signature_length; diff --git a/library/pk_wrap.c b/library/pk_wrap.c index 8c038e7db6..5265469349 100644 --- a/library/pk_wrap.c +++ b/library/pk_wrap.c @@ -72,7 +72,7 @@ static int rsa_verify_wrap(mbedtls_pk_context *pk, mbedtls_md_type_t md_alg, mbedtls_svc_key_id_t key_id = MBEDTLS_SVC_KEY_ID_INIT; psa_status_t status; int key_len; - unsigned char buf[MBEDTLS_PK_RSA_PUB_DER_MAX_BYTES]; + unsigned char buf[PSA_KEY_EXPORT_RSA_PUBLIC_KEY_MAX_SIZE(PSA_VENDOR_RSA_MAX_KEY_BITS)]; unsigned char *p = buf + sizeof(buf); psa_algorithm_t psa_alg_md; size_t rsa_len = mbedtls_rsa_get_len(rsa); @@ -375,7 +375,7 @@ static int rsa_encrypt_wrap(mbedtls_pk_context *pk, psa_algorithm_t psa_md_alg, psa_encrypt_alg; psa_status_t status; int key_len; - unsigned char buf[MBEDTLS_PK_RSA_PUB_DER_MAX_BYTES]; + unsigned char buf[PSA_KEY_EXPORT_RSA_PUBLIC_KEY_MAX_SIZE(PSA_VENDOR_RSA_MAX_KEY_BITS)]; unsigned char *p = buf + sizeof(buf); ((void) f_rng); From c763c2e83717d276108c9c28c5c43d9d69e7ce7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Tue, 3 Mar 2026 09:53:08 +0100 Subject: [PATCH 53/61] PK: use EC macro for EC key size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Pégourié-Gonnard --- library/pkwrite.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/pkwrite.c b/library/pkwrite.c index ff6c0bfb44..d264abfbd9 100644 --- a/library/pkwrite.c +++ b/library/pkwrite.c @@ -45,10 +45,10 @@ /* Helpers for properly sizing buffers aimed at holding public keys or * key-pairs based on build symbols. */ #if defined(MBEDTLS_PK_USE_PSA_EC_DATA) -#define PK_MAX_EC_PUBLIC_KEY_SIZE PSA_EXPORT_PUBLIC_KEY_MAX_SIZE +#define PK_MAX_EC_PUBLIC_KEY_SIZE MBEDTLS_PSA_MAX_EC_PUBKEY_LENGTH #define PK_MAX_EC_KEY_PAIR_SIZE MBEDTLS_PSA_MAX_EC_KEY_PAIR_LENGTH #elif defined(MBEDTLS_USE_PSA_CRYPTO) -#define PK_MAX_EC_PUBLIC_KEY_SIZE PSA_EXPORT_PUBLIC_KEY_MAX_SIZE +#define PK_MAX_EC_PUBLIC_KEY_SIZE MBEDTLS_PSA_MAX_EC_PUBKEY_LENGTH #define PK_MAX_EC_KEY_PAIR_SIZE MBEDTLS_PSA_MAX_EC_KEY_PAIR_LENGTH #else #define PK_MAX_EC_PUBLIC_KEY_SIZE MBEDTLS_ECP_MAX_PT_LEN From 34bbc72190048d21ba59c4281bbdb0189cd3a6cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bence=20Sz=C3=A9pk=C3=BAti?= Date: Mon, 17 Nov 2025 16:53:01 +0100 Subject: [PATCH 54/61] Move abi_check.py into the framework MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Bence Szépkúti --- scripts/abi_check.py | 699 ------------------------------------------- 1 file changed, 699 deletions(-) delete mode 100755 scripts/abi_check.py diff --git a/scripts/abi_check.py b/scripts/abi_check.py deleted file mode 100755 index 7882681f12..0000000000 --- a/scripts/abi_check.py +++ /dev/null @@ -1,699 +0,0 @@ -#!/usr/bin/env python3 -"""This script compares the interfaces of two versions of Mbed TLS, looking -for backward incompatibilities between two different Git revisions within -an Mbed TLS repository. It must be run from the root of a Git working tree. - -### How the script works ### - -For the source (API) and runtime (ABI) interface compatibility, this script -is a small wrapper around the abi-compliance-checker and abi-dumper tools, -applying them to compare the header and library files. - -For the storage format, this script compares the automatically generated -storage tests and the manual read tests, and complains if there is a -reduction in coverage. A change in test data will be signaled as a -coverage reduction since the old test data is no longer present. A change in -how test data is presented will be signaled as well; this would be a false -positive. - -The results of the API/ABI comparison are either formatted as HTML and stored -at a configurable location, or are given as a brief list of problems. -Returns 0 on success, 1 on non-compliance, and 2 if there is an error -while running the script. - -### How to interpret non-compliance ### - -This script has relatively common false positives. In many scenarios, it only -reports a pass if there is a strict textual match between the old version and -the new version, and it reports problems where there is a sufficient semantic -match but not a textual match. This section lists some common false positives. -This is not an exhaustive list: in the end what matters is whether we are -breaking a backward compatibility goal. - -**API**: the goal is that if an application works with the old version of the -library, it can be recompiled against the new version and will still work. -This is normally validated by comparing the declarations in `include/*/*.h`. -A failure is a declaration that has disappeared or that now has a different -type. - - * It's ok to change or remove macros and functions that are documented as - for internal use only or as experimental. - * It's ok to rename function or macro parameters as long as the semantics - has not changed. - * It's ok to change or remove structure fields that are documented as - private. - * It's ok to add fields to a structure that already had private fields - or was documented as extensible. - -**ABI**: the goal is that if an application was built against the old version -of the library, the same binary will work when linked against the new version. -This is normally validated by comparing the symbols exported by `libmbed*.so`. -A failure is a symbol that is no longer exported by the same library or that -now has a different type. - - * All ABI changes are acceptable if the library version is bumped - (see `scripts/bump_version.sh`). - * ABI changes that concern functions which are declared only inside the - library directory, and not in `include/*/*.h`, are acceptable only if - the function was only ever used inside the same library (libmbedcrypto, - libmbedx509, libmbedtls). As a counter example, if the old version - of libmbedtls calls mbedtls_foo() from libmbedcrypto, and the new version - of libmbedcrypto no longer has a compatible mbedtls_foo(), this does - require a version bump for libmbedcrypto. - -**Storage format**: the goal is to check that persistent keys stored by the -old version can be read by the new version. This is normally validated by -comparing the `*read*` test cases in `test_suite*storage_format*.data`. -A failure is a storage read test case that is no longer present with the same -function name and parameter list. - - * It's ok if the same test data is present, but its presentation has changed, - for example if a test function is renamed or has different parameters. - * It's ok if redundant tests are removed. - -**Generated test coverage**: the goal is to check that automatically -generated tests have as much coverage as before. This is normally validated -by comparing the test cases that are automatically generated by a script. -A failure is a generated test case that is no longer present with the same -function name and parameter list. - - * It's ok if the same test data is present, but its presentation has changed, - for example if a test function is renamed or has different parameters. - * It's ok if redundant tests are removed. - -""" - -# Copyright The Mbed TLS Contributors -# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later - -import glob -import os -import re -import sys -import traceback -import shutil -import subprocess -import argparse -import logging -import tempfile -import fnmatch -from types import SimpleNamespace - -import xml.etree.ElementTree as ET - -import framework_scripts_path # pylint: disable=unused-import -from mbedtls_framework import build_tree - - -class AbiChecker: - """API and ABI checker.""" - - def __init__(self, old_version, new_version, configuration): - """Instantiate the API/ABI checker. - - old_version: RepoVersion containing details to compare against - new_version: RepoVersion containing details to check - configuration.report_dir: directory for output files - configuration.keep_all_reports: if false, delete old reports - configuration.brief: if true, output shorter report to stdout - configuration.check_abi: if true, compare ABIs - configuration.check_api: if true, compare APIs - configuration.check_storage: if true, compare storage format tests - configuration.skip_file: path to file containing symbols and types to skip - """ - self.repo_path = "." - self.log = None - self.verbose = configuration.verbose - self._setup_logger() - self.report_dir = os.path.abspath(configuration.report_dir) - self.keep_all_reports = configuration.keep_all_reports - self.can_remove_report_dir = not (os.path.exists(self.report_dir) or - self.keep_all_reports) - self.old_version = old_version - self.new_version = new_version - self.skip_file = configuration.skip_file - self.check_abi = configuration.check_abi - self.check_api = configuration.check_api - if self.check_abi != self.check_api: - raise Exception('Checking API without ABI or vice versa is not supported') - self.check_storage_tests = configuration.check_storage - self.brief = configuration.brief - self.git_command = "git" - self.make_command = "make" - - def _setup_logger(self): - self.log = logging.getLogger() - if self.verbose: - self.log.setLevel(logging.DEBUG) - else: - self.log.setLevel(logging.INFO) - self.log.addHandler(logging.StreamHandler()) - - @staticmethod - def check_abi_tools_are_installed(): - for command in ["abi-dumper", "abi-compliance-checker"]: - if not shutil.which(command): - raise Exception("{} not installed, aborting".format(command)) - - def _get_clean_worktree_for_git_revision(self, version): - """Make a separate worktree with version.revision checked out. - Do not modify the current worktree.""" - git_worktree_path = tempfile.mkdtemp() - if version.repository: - self.log.debug( - "Checking out git worktree for revision {} from {}".format( - version.revision, version.repository - ) - ) - fetch_output = subprocess.check_output( - [self.git_command, "fetch", - version.repository, version.revision], - cwd=self.repo_path, - stderr=subprocess.STDOUT - ) - self.log.debug(fetch_output.decode("utf-8")) - worktree_rev = "FETCH_HEAD" - else: - self.log.debug("Checking out git worktree for revision {}".format( - version.revision - )) - worktree_rev = version.revision - worktree_output = subprocess.check_output( - [self.git_command, "worktree", "add", "--detach", - git_worktree_path, worktree_rev], - cwd=self.repo_path, - stderr=subprocess.STDOUT - ) - self.log.debug(worktree_output.decode("utf-8")) - version.commit = subprocess.check_output( - [self.git_command, "rev-parse", "HEAD"], - cwd=git_worktree_path, - stderr=subprocess.STDOUT - ).decode("ascii").rstrip() - self.log.debug("Commit is {}".format(version.commit)) - return git_worktree_path - - def _update_git_submodules(self, git_worktree_path, version): - """If the crypto submodule is present, initialize it. - if version.crypto_revision exists, update it to that revision, - otherwise update it to the default revision""" - submodule_output = subprocess.check_output( - [self.git_command, "submodule", "foreach", "--recursive", - f'git worktree add --detach "{git_worktree_path}/$displaypath" HEAD'], - cwd=self.repo_path, - stderr=subprocess.STDOUT - ) - self.log.debug(submodule_output.decode("utf-8")) - - try: - # Try to update the submodules using local commits - # (Git will sometimes insist on fetching the remote without --no-fetch - # if the submodules are shallow clones) - update_output = subprocess.check_output( - [self.git_command, "submodule", "update", "--init", '--recursive', '--no-fetch'], - cwd=git_worktree_path, - stderr=subprocess.STDOUT - ) - except subprocess.CalledProcessError as err: - self.log.debug(err.stdout.decode("utf-8")) - - # Checkout with --no-fetch failed, falling back to fetching from origin - update_output = subprocess.check_output( - [self.git_command, "submodule", "update", "--init", '--recursive'], - cwd=git_worktree_path, - stderr=subprocess.STDOUT - ) - self.log.debug(update_output.decode("utf-8")) - if not (os.path.exists(os.path.join(git_worktree_path, "crypto")) - and version.crypto_revision): - return - - if version.crypto_repository: - fetch_output = subprocess.check_output( - [self.git_command, "fetch", version.crypto_repository, - version.crypto_revision], - cwd=os.path.join(git_worktree_path, "crypto"), - stderr=subprocess.STDOUT - ) - self.log.debug(fetch_output.decode("utf-8")) - crypto_rev = "FETCH_HEAD" - else: - crypto_rev = version.crypto_revision - - checkout_output = subprocess.check_output( - [self.git_command, "checkout", crypto_rev], - cwd=os.path.join(git_worktree_path, "crypto"), - stderr=subprocess.STDOUT - ) - self.log.debug(checkout_output.decode("utf-8")) - - def _build_shared_libraries(self, git_worktree_path, version): - """Build the shared libraries in the specified worktree.""" - my_environment = os.environ.copy() - my_environment["CFLAGS"] = "-g -Og" - my_environment["SHARED"] = "1" - if os.path.exists(os.path.join(git_worktree_path, "crypto")): - my_environment["USE_CRYPTO_SUBMODULE"] = "1" - make_output = subprocess.check_output( - [self.make_command, "lib"], - env=my_environment, - cwd=git_worktree_path, - stderr=subprocess.STDOUT - ) - self.log.debug(make_output.decode("utf-8")) - for root, _dirs, files in os.walk(git_worktree_path): - for file in fnmatch.filter(files, "*.so"): - version.modules[os.path.splitext(file)[0]] = ( - os.path.join(root, file) - ) - - @staticmethod - def _pretty_revision(version): - if version.revision == version.commit: - return version.revision - else: - return "{} ({})".format(version.revision, version.commit) - - def _get_abi_dumps_from_shared_libraries(self, version): - """Generate the ABI dumps for the specified git revision. - The shared libraries must have been built and the module paths - present in version.modules.""" - for mbed_module, module_path in version.modules.items(): - output_path = os.path.join( - self.report_dir, "{}-{}-{}.dump".format( - mbed_module, version.revision, version.version - ) - ) - abi_dump_command = [ - "abi-dumper", - module_path, - "-o", output_path, - "-lver", self._pretty_revision(version), - ] - abi_dump_output = subprocess.check_output( - abi_dump_command, - stderr=subprocess.STDOUT - ) - self.log.debug(abi_dump_output.decode("utf-8")) - version.abi_dumps[mbed_module] = output_path - - @staticmethod - def _normalize_storage_test_case_data(line): - """Eliminate cosmetic or irrelevant details in storage format test cases.""" - line = re.sub(r'\s+', r'', line) - return line - - def _read_storage_tests(self, - directory, - filename, - is_generated, - storage_tests): - """Record storage tests from the given file. - - Populate the storage_tests dictionary with test cases read from - filename under directory. - """ - at_paragraph_start = True - description = None - full_path = os.path.join(directory, filename) - with open(full_path) as fd: - for line_number, line in enumerate(fd, 1): - line = line.strip() - if not line: - at_paragraph_start = True - continue - if line.startswith('#'): - continue - if at_paragraph_start: - description = line.strip() - at_paragraph_start = False - continue - if line.startswith('depends_on:'): - continue - # We've reached a test case data line - test_case_data = self._normalize_storage_test_case_data(line) - if not is_generated: - # In manual test data, only look at read tests. - function_name = test_case_data.split(':', 1)[0] - if 'read' not in function_name.split('_'): - continue - metadata = SimpleNamespace( - filename=filename, - line_number=line_number, - description=description - ) - storage_tests[test_case_data] = metadata - - @staticmethod - def _list_generated_test_data_files(git_worktree_path): - """List the generated test data files.""" - generate_psa_tests = 'framework/scripts/generate_psa_tests.py' - if not os.path.isfile(git_worktree_path + '/' + generate_psa_tests): - # The checked-out revision is from before generate_psa_tests.py - # was moved to the framework submodule. Use the old location. - generate_psa_tests = 'tests/scripts/generate_psa_tests.py' - - output = subprocess.check_output( - [generate_psa_tests, '--list'], - cwd=git_worktree_path, - ).decode('ascii') - return [line for line in output.split('\n') if line] - - def _get_storage_format_tests(self, version, git_worktree_path): - """Record the storage format tests for the specified git version. - - The storage format tests are the test suite data files whose name - contains "storage_format". - - The version must be checked out at git_worktree_path. - - This function creates or updates the generated data files. - """ - # Existing test data files. This may be missing some automatically - # generated files if they haven't been generated yet. - storage_data_files = set(glob.glob( - 'tests/suites/test_suite_*storage_format*.data' - )) - # Discover and (re)generate automatically generated data files. - to_be_generated = set() - for filename in self._list_generated_test_data_files(git_worktree_path): - if 'storage_format' in filename: - storage_data_files.add(filename) - to_be_generated.add(filename) - - generate_psa_tests = 'framework/scripts/generate_psa_tests.py' - if not os.path.isfile(git_worktree_path + '/' + generate_psa_tests): - # The checked-out revision is from before generate_psa_tests.py - # was moved to the framework submodule. Use the old location. - generate_psa_tests = 'tests/scripts/generate_psa_tests.py' - subprocess.check_call( - [generate_psa_tests] + sorted(to_be_generated), - cwd=git_worktree_path, - ) - for test_file in sorted(storage_data_files): - self._read_storage_tests(git_worktree_path, - test_file, - test_file in to_be_generated, - version.storage_tests) - - def _cleanup_worktree(self, git_worktree_path): - """Remove the specified git worktree.""" - shutil.rmtree(git_worktree_path) - submodule_output = subprocess.check_output( - [self.git_command, "submodule", "foreach", "--recursive", - f'git worktree remove "{git_worktree_path}/$displaypath"'], - cwd=self.repo_path, - stderr=subprocess.STDOUT - ) - self.log.debug(submodule_output.decode("utf-8")) - worktree_output = subprocess.check_output( - [self.git_command, "worktree", "remove", git_worktree_path], - cwd=self.repo_path, - stderr=subprocess.STDOUT - ) - self.log.debug(worktree_output.decode("utf-8")) - - def _get_abi_dump_for_ref(self, version): - """Generate the interface information for the specified git revision.""" - git_worktree_path = self._get_clean_worktree_for_git_revision(version) - self._update_git_submodules(git_worktree_path, version) - if self.check_abi: - self._build_shared_libraries(git_worktree_path, version) - self._get_abi_dumps_from_shared_libraries(version) - if self.check_storage_tests: - self._get_storage_format_tests(version, git_worktree_path) - self._cleanup_worktree(git_worktree_path) - - def _remove_children_with_tag(self, parent, tag): - children = parent.getchildren() - for child in children: - if child.tag == tag: - parent.remove(child) - else: - self._remove_children_with_tag(child, tag) - - def _remove_extra_detail_from_report(self, report_root): - for tag in ['test_info', 'test_results', 'problem_summary', - 'added_symbols', 'affected']: - self._remove_children_with_tag(report_root, tag) - - for report in report_root: - for problems in report.getchildren()[:]: - if not problems.getchildren(): - report.remove(problems) - - def _abi_compliance_command(self, mbed_module, output_path): - """Build the command to run to analyze the library mbed_module. - The report will be placed in output_path.""" - abi_compliance_command = [ - "abi-compliance-checker", - "-l", mbed_module, - "-old", self.old_version.abi_dumps[mbed_module], - "-new", self.new_version.abi_dumps[mbed_module], - "-strict", - "-report-path", output_path, - ] - if self.skip_file: - abi_compliance_command += ["-skip-symbols", self.skip_file, - "-skip-types", self.skip_file] - if self.brief: - abi_compliance_command += ["-report-format", "xml", - "-stdout"] - return abi_compliance_command - - def _is_library_compatible(self, mbed_module, compatibility_report): - """Test if the library mbed_module has remained compatible. - Append a message regarding compatibility to compatibility_report.""" - output_path = os.path.join( - self.report_dir, "{}-{}-{}.html".format( - mbed_module, self.old_version.revision, - self.new_version.revision - ) - ) - try: - subprocess.check_output( - self._abi_compliance_command(mbed_module, output_path), - stderr=subprocess.STDOUT - ) - except subprocess.CalledProcessError as err: - if err.returncode != 1: - raise err - if self.brief: - self.log.info( - "Compatibility issues found for {}".format(mbed_module) - ) - report_root = ET.fromstring(err.output.decode("utf-8")) - self._remove_extra_detail_from_report(report_root) - self.log.info(ET.tostring(report_root).decode("utf-8")) - else: - self.can_remove_report_dir = False - compatibility_report.append( - "Compatibility issues found for {}, " - "for details see {}".format(mbed_module, output_path) - ) - return False - compatibility_report.append( - "No compatibility issues for {}".format(mbed_module) - ) - if not (self.keep_all_reports or self.brief): - os.remove(output_path) - return True - - @staticmethod - def _is_storage_format_compatible(old_tests, new_tests, - compatibility_report): - """Check whether all tests present in old_tests are also in new_tests. - - Append a message regarding compatibility to compatibility_report. - """ - missing = frozenset(old_tests.keys()).difference(new_tests.keys()) - for test_data in sorted(missing): - metadata = old_tests[test_data] - compatibility_report.append( - 'Test case from {} line {} "{}" has disappeared: {}'.format( - metadata.filename, metadata.line_number, - metadata.description, test_data - ) - ) - compatibility_report.append( - 'FAIL: {}/{} storage format test cases have changed or disappeared.'.format( - len(missing), len(old_tests) - ) if missing else - 'PASS: All {} storage format test cases are preserved.'.format( - len(old_tests) - ) - ) - compatibility_report.append( - 'Info: number of storage format tests cases: {} -> {}.'.format( - len(old_tests), len(new_tests) - ) - ) - return not missing - - def get_abi_compatibility_report(self): - """Generate a report of the differences between the reference ABI - and the new ABI. ABI dumps from self.old_version and self.new_version - must be available.""" - compatibility_report = ["Checking evolution from {} to {}".format( - self._pretty_revision(self.old_version), - self._pretty_revision(self.new_version) - )] - compliance_return_code = 0 - - if self.check_abi: - shared_modules = list(set(self.old_version.modules.keys()) & - set(self.new_version.modules.keys())) - for mbed_module in shared_modules: - if not self._is_library_compatible(mbed_module, - compatibility_report): - compliance_return_code = 1 - - if self.check_storage_tests: - if not self._is_storage_format_compatible( - self.old_version.storage_tests, - self.new_version.storage_tests, - compatibility_report): - compliance_return_code = 1 - - for version in [self.old_version, self.new_version]: - for mbed_module, mbed_module_dump in version.abi_dumps.items(): - os.remove(mbed_module_dump) - if self.can_remove_report_dir: - os.rmdir(self.report_dir) - self.log.info("\n".join(compatibility_report)) - return compliance_return_code - - def check_for_abi_changes(self): - """Generate a report of ABI differences - between self.old_rev and self.new_rev.""" - build_tree.check_repo_path() - if self.check_api or self.check_abi: - self.check_abi_tools_are_installed() - self._get_abi_dump_for_ref(self.old_version) - self._get_abi_dump_for_ref(self.new_version) - return self.get_abi_compatibility_report() - - -def run_main(): - try: - parser = argparse.ArgumentParser( - description=__doc__ - ) - parser.add_argument( - "-v", "--verbose", action="store_true", - help="set verbosity level", - ) - parser.add_argument( - "-r", "--report-dir", type=str, default="reports", - help="directory where reports are stored, default is reports", - ) - parser.add_argument( - "-k", "--keep-all-reports", action="store_true", - help="keep all reports, even if there are no compatibility issues", - ) - parser.add_argument( - "-o", "--old-rev", type=str, help="revision for old version.", - required=True, - ) - parser.add_argument( - "-or", "--old-repo", type=str, help="repository for old version." - ) - parser.add_argument( - "-oc", "--old-crypto-rev", type=str, - help="revision for old crypto submodule." - ) - parser.add_argument( - "-ocr", "--old-crypto-repo", type=str, - help="repository for old crypto submodule." - ) - parser.add_argument( - "-n", "--new-rev", type=str, help="revision for new version", - required=True, - ) - parser.add_argument( - "-nr", "--new-repo", type=str, help="repository for new version." - ) - parser.add_argument( - "-nc", "--new-crypto-rev", type=str, - help="revision for new crypto version" - ) - parser.add_argument( - "-ncr", "--new-crypto-repo", type=str, - help="repository for new crypto submodule." - ) - parser.add_argument( - "-s", "--skip-file", type=str, - help=("path to file containing symbols and types to skip " - "(typically \"-s identifiers\" after running " - "\"tests/scripts/list-identifiers.sh --internal\")") - ) - parser.add_argument( - "--check-abi", - action='store_true', default=True, - help="Perform ABI comparison (default: yes)" - ) - parser.add_argument("--no-check-abi", action='store_false', dest='check_abi') - parser.add_argument( - "--check-api", - action='store_true', default=True, - help="Perform API comparison (default: yes)" - ) - parser.add_argument("--no-check-api", action='store_false', dest='check_api') - parser.add_argument( - "--check-storage", - action='store_true', default=True, - help="Perform storage tests comparison (default: yes)" - ) - parser.add_argument("--no-check-storage", action='store_false', dest='check_storage') - parser.add_argument( - "-b", "--brief", action="store_true", - help="output only the list of issues to stdout, instead of a full report", - ) - abi_args = parser.parse_args() - if os.path.isfile(abi_args.report_dir): - print("Error: {} is not a directory".format(abi_args.report_dir)) - parser.exit() - old_version = SimpleNamespace( - version="old", - repository=abi_args.old_repo, - revision=abi_args.old_rev, - commit=None, - crypto_repository=abi_args.old_crypto_repo, - crypto_revision=abi_args.old_crypto_rev, - abi_dumps={}, - storage_tests={}, - modules={} - ) - new_version = SimpleNamespace( - version="new", - repository=abi_args.new_repo, - revision=abi_args.new_rev, - commit=None, - crypto_repository=abi_args.new_crypto_repo, - crypto_revision=abi_args.new_crypto_rev, - abi_dumps={}, - storage_tests={}, - modules={} - ) - configuration = SimpleNamespace( - verbose=abi_args.verbose, - report_dir=abi_args.report_dir, - keep_all_reports=abi_args.keep_all_reports, - brief=abi_args.brief, - check_abi=abi_args.check_abi, - check_api=abi_args.check_api, - check_storage=abi_args.check_storage, - skip_file=abi_args.skip_file - ) - abi_check = AbiChecker(old_version, new_version, configuration) - return_code = abi_check.check_for_abi_changes() - sys.exit(return_code) - except Exception: # pylint: disable=broad-except - # Print the backtrace and exit explicitly so as to exit with - # status 2, not 1. - traceback.print_exc() - sys.exit(2) - - -if __name__ == "__main__": - run_main() From 5dcfb01b52b7fff5f39785814224fb7f0c4311a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bence=20Sz=C3=A9pk=C3=BAti?= Date: Wed, 22 Oct 2025 00:52:06 +0200 Subject: [PATCH 55/61] Add abi_check.py bridge script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bulk of the script that was moved to the framework is now a pure python module - bridge scripts like this one will remain in each individual branch, and continue to be the way to invoke the ABI checks. This way we can use the bridge scripts to encode branch-specific information in a more convenient way. Signed-off-by: Bence Szépkúti --- scripts/abi_check.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100755 scripts/abi_check.py diff --git a/scripts/abi_check.py b/scripts/abi_check.py new file mode 100755 index 0000000000..517d40e355 --- /dev/null +++ b/scripts/abi_check.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +"""Bridge script +See framework/scripts/mbedtls_framework/interface_checks.py for detailed documentation. + +This is a convenient place to encode any branch-specific information we might want to add +in the future. +""" + +# Copyright The Mbed TLS Contributors +# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +import framework_scripts_path # pylint: disable=unused-import +from mbedtls_framework import interface_checks + +if __name__ == "__main__": + interface_checks.run_main() From 1222d8cbf4862a89b7f5189debe4b2bb518a7ed5 Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Mon, 9 Mar 2026 23:28:50 +0100 Subject: [PATCH 56/61] Improve comments Signed-off-by: Ronald Cron --- library/ssl_msg.c | 4 ++-- library/ssl_tls12_server.c | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/library/ssl_msg.c b/library/ssl_msg.c index b284e6dcda..1288dcca0e 100644 --- a/library/ssl_msg.c +++ b/library/ssl_msg.c @@ -3264,8 +3264,8 @@ int mbedtls_ssl_prepare_handshake_record(mbedtls_ssl_context *ssl) /* * When establishing the connection, the client may go through a series * of ClientHello and HelloVerifyRequest requests and responses. The - * server does not keep any trace of these initial round trips as - * intended: minimum allocated ressources as long as the reachability + * server intentionally does not keep trace of these initial round + * trips: minimum allocated ressources as long as the reachability * of the client has not been confirmed. When receiving the "first * ClientHello" from server perspective, we may thus need to adapt * the next expected `message_seq` for the incoming and outgoing diff --git a/library/ssl_tls12_server.c b/library/ssl_tls12_server.c index eae4996fc8..a3f1c2afe4 100644 --- a/library/ssl_tls12_server.c +++ b/library/ssl_tls12_server.c @@ -914,10 +914,11 @@ static int ssl_parse_client_hello(mbedtls_ssl_context *ssl) /* * Fetch the expected ClientHello handshake message. Do not ask - * mbedtls_ssl_read_record() to update the handshake digest, to align - * with cases where the ClientHello may already have been fetched in - * ssl_tls13_process_client_hello() or as a post-handshake message - * (renegotiation). + * mbedtls_ssl_read_record() to update the handshake digest, because the + * ClientHello may already have been read in ssl_tls13_process_client_hello() + * or as a post-handshake message (renegotiation). In those cases we need + * to update the digest ourselves, and it is simpler to do so + * unconditionally than to track whether it is needed. */ if ((ret = mbedtls_ssl_read_record(ssl, 0)) != 0) { MBEDTLS_SSL_DEBUG_RET(1, "mbedtls_ssl_read_record ", ret); From d6977afbd82f261fc7bb112104764daf2918d38c Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Tue, 10 Mar 2026 08:25:14 +0100 Subject: [PATCH 57/61] ssl_tls12_server.c: Move back the digest update Move back the digest update just after the call to mbedtls_ssl_read_record(). It fits well here as we explain in the comment associated to the call to mbedtls_ssl_read_record() that we update it manually. Signed-off-by: Ronald Cron --- library/ssl_tls12_server.c | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/library/ssl_tls12_server.c b/library/ssl_tls12_server.c index a3f1c2afe4..0fbe519ce1 100644 --- a/library/ssl_tls12_server.c +++ b/library/ssl_tls12_server.c @@ -925,6 +925,20 @@ static int ssl_parse_client_hello(mbedtls_ssl_context *ssl) return ret; } + /* + * Update the handshake checksum. + * + * Note that the checksum must be updated before parsing the extensions + * because ssl_parse_session_ticket_ext() may decrypt the ticket in place + * and therefore modify the ClientHello message. This occurs when using + * the Mbed TLS ssl_ticket.c implementation. + */ + ret = mbedtls_ssl_update_handshake_status(ssl); + if (0 != ret) { + MBEDTLS_SSL_DEBUG_RET(1, ("mbedtls_ssl_update_handshake_status"), ret); + return ret; + } + buf = ssl->in_msg; msg_len = ssl->in_hslen; @@ -1130,21 +1144,6 @@ static int ssl_parse_client_hello(mbedtls_ssl_context *ssl) ext_len = 0; } - /* - * Update the handshake checksum after performing preliminary - * validation of the ClientHello and before parsing its extensions. - * - * The checksum must be updated before parsing the extensions because - * ssl_parse_session_ticket_ext() may decrypt the ticket in place and - * therefore modify the ClientHello message. This occurs when using - * the Mbed TLS ssl_ticket.c implementation. - */ - ret = mbedtls_ssl_update_handshake_status(ssl); - if (0 != ret) { - MBEDTLS_SSL_DEBUG_RET(1, ("mbedtls_ssl_update_handshake_status"), ret); - return ret; - } - ext = buf + ext_offset + 2; MBEDTLS_SSL_DEBUG_BUF(3, "client hello extensions", ext, ext_len); From 7476e4067da1bd89ecab4b234cd2a81f44fc2f35 Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Tue, 10 Mar 2026 08:49:25 +0100 Subject: [PATCH 58/61] Restore seq number check of post-handshake ClientHello msg The check was wrongly removed by the commit "ssl_tls12_server.c: Move ClientHello message_seq adjustment". Signed-off-by: Ronald Cron --- library/ssl_msg.c | 59 +++++++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/library/ssl_msg.c b/library/ssl_msg.c index 1288dcca0e..7a7182c2be 100644 --- a/library/ssl_msg.c +++ b/library/ssl_msg.c @@ -3261,34 +3261,47 @@ int mbedtls_ssl_prepare_handshake_record(mbedtls_ssl_context *ssl) return MBEDTLS_ERR_SSL_INVALID_RECORD; } - /* - * When establishing the connection, the client may go through a series - * of ClientHello and HelloVerifyRequest requests and responses. The - * server intentionally does not keep trace of these initial round - * trips: minimum allocated ressources as long as the reachability - * of the client has not been confirmed. When receiving the "first - * ClientHello" from server perspective, we may thus need to adapt - * the next expected `message_seq` for the incoming and outgoing - * handshake messages. - */ if (ssl->in_msg[0] == MBEDTLS_SSL_HS_CLIENT_HELLO && - ssl->conf->endpoint == MBEDTLS_SSL_IS_SERVER && - ssl->state == MBEDTLS_SSL_CLIENT_HELLO + ssl->conf->endpoint == MBEDTLS_SSL_IS_SERVER) { + if (ssl->state == MBEDTLS_SSL_CLIENT_HELLO #if defined(MBEDTLS_SSL_RENEGOTIATION) - && ssl->renego_status == MBEDTLS_SSL_INITIAL_HANDSHAKE + && ssl->renego_status == MBEDTLS_SSL_INITIAL_HANDSHAKE #endif - ) { - ssl->handshake->in_msg_seq = recv_msg_seq; - ssl->handshake->out_msg_seq = recv_msg_seq; + ) { + /* + * When establishing the connection, the client may go through + * a series of ClientHello and HelloVerifyRequest requests and + * responses. The server intentionally does not keep trace of + * these initial round trips: minimum allocated ressources as + * long as the reachability of the client has not been + * confirmed. When receiving the "first ClientHello" from + * server perspective, we may thus need to adapt the next + * expected `message_seq` for the incoming and outgoing + * handshake messages. + */ + ssl->handshake->in_msg_seq = recv_msg_seq; + ssl->handshake->out_msg_seq = recv_msg_seq; - /* Epoch should be 0 for initial handshakes */ - if (ssl->in_ctr[0] != 0 || ssl->in_ctr[1] != 0) { - MBEDTLS_SSL_DEBUG_MSG(1, ("bad client hello message")); - return MBEDTLS_ERR_SSL_ILLEGAL_PARAMETER; + /* Epoch should be 0 for initial handshakes */ + if (ssl->in_ctr[0] != 0 || ssl->in_ctr[1] != 0) { + MBEDTLS_SSL_DEBUG_MSG(1, ("bad client hello message")); + return MBEDTLS_ERR_SSL_ILLEGAL_PARAMETER; + } + + memcpy(&ssl->cur_out_ctr[2], ssl->in_ctr + 2, + sizeof(ssl->cur_out_ctr) - 2); + } else if (mbedtls_ssl_is_handshake_over(ssl) == 1) { + /* In case of a post-handshake ClientHello that initiates a + * renegotiation check that the handshake message sequence + * number is zero. + */ + if (recv_msg_seq != 0) { + MBEDTLS_SSL_DEBUG_MSG(1, ("bad client hello message_seq: " + "%u (expected 0)", + recv_msg_seq)); + return MBEDTLS_ERR_SSL_DECODE_ERROR; + } } - - memcpy(&ssl->cur_out_ctr[2], ssl->in_ctr + 2, - sizeof(ssl->cur_out_ctr) - 2); } if (ssl->handshake != NULL && From 611f3fb072904082e154f681a697f4f51e0a7387 Mon Sep 17 00:00:00 2001 From: David Horstmann Date: Mon, 16 Mar 2026 11:15:01 +0000 Subject: [PATCH 59/61] Configure the RNG in 3.6 testcase In Mbed TLS 3.6 we still need to manually configure the RNG for TLS. Add this to the testcase for default verify_result. Signed-off-by: David Horstmann --- tests/suites/test_suite_ssl.function | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/suites/test_suite_ssl.function b/tests/suites/test_suite_ssl.function index 552c06a7b9..38837f2ead 100644 --- a/tests/suites/test_suite_ssl.function +++ b/tests/suites/test_suite_ssl.function @@ -6019,6 +6019,7 @@ void verify_result_without_handshake(void) mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_OPTIONAL); mbedtls_ssl_conf_ca_chain(&conf, NULL, NULL); + mbedtls_ssl_conf_rng(&conf, mbedtls_test_random, NULL); TEST_EQUAL(mbedtls_ssl_setup(&ssl, &conf), 0); From 05012095075c63532bd4c87b1860b4fec9ebd8ae Mon Sep 17 00:00:00 2001 From: David Horstmann Date: Mon, 16 Mar 2026 17:19:30 +0000 Subject: [PATCH 60/61] Move TLS 1.3 verify-result setting for PSK When we are doing PSK, we'd like to set verify_result to 0 to indicate success. Previously this was done in mbedtls_ssl_set_hs_psk() but this is inadequate since this function may be called for early data (where certificate verification happens later in the handshake). Instead, set this value after writing / processing the encrypted extensions on the server / client respectively, so that we know whether we are doing certificate verification or not for sure. This change is effective only for TLS 1.3 as TLS 1.2 sets verify_result for PSK in ssl_parse_certificate_coordinate(). Signed-off-by: David Horstmann --- library/ssl_tls.c | 3 --- library/ssl_tls13_client.c | 3 +++ library/ssl_tls13_server.c | 3 +++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/library/ssl_tls.c b/library/ssl_tls.c index ca3be8b50b..7fc3b10072 100644 --- a/library/ssl_tls.c +++ b/library/ssl_tls.c @@ -2288,9 +2288,6 @@ int mbedtls_ssl_set_hs_psk(mbedtls_ssl_context *ssl, return MBEDTLS_ERR_SSL_HW_ACCEL_FAILED; } - /* Since we're not using a certificate, set verify_result to success */ - ssl->session_negotiate->verify_result = 0; - /* Allow calling psa_destroy_key() on psk remove */ ssl->handshake->psk_opaque_is_internal = 1; return mbedtls_ssl_set_hs_psk_opaque(ssl, key); diff --git a/library/ssl_tls13_client.c b/library/ssl_tls13_client.c index 78a069f792..752bc033fe 100644 --- a/library/ssl_tls13_client.c +++ b/library/ssl_tls13_client.c @@ -2270,6 +2270,9 @@ static int ssl_tls13_process_encrypted_extensions(mbedtls_ssl_context *ssl) #if defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED) if (mbedtls_ssl_tls13_key_exchange_mode_with_psk(ssl)) { mbedtls_ssl_handshake_set_state(ssl, MBEDTLS_SSL_SERVER_FINISHED); + + /* Since we're not using a certificate, set verify_result to success */ + ssl->session_negotiate->verify_result = 0; } else { mbedtls_ssl_handshake_set_state(ssl, MBEDTLS_SSL_CERTIFICATE_REQUEST); } diff --git a/library/ssl_tls13_server.c b/library/ssl_tls13_server.c index 5757d20180..e370a6b3f3 100644 --- a/library/ssl_tls13_server.c +++ b/library/ssl_tls13_server.c @@ -2637,6 +2637,9 @@ static int ssl_tls13_write_encrypted_extensions(mbedtls_ssl_context *ssl) #if defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED) if (mbedtls_ssl_tls13_key_exchange_mode_with_psk(ssl)) { mbedtls_ssl_handshake_set_state(ssl, MBEDTLS_SSL_SERVER_FINISHED); + + /* Since we're not using a certificate, set verify_result to success */ + ssl->session_negotiate->verify_result = 0; } else { mbedtls_ssl_handshake_set_state(ssl, MBEDTLS_SSL_CERTIFICATE_REQUEST); } From b19eec649583e69f635ab5bbba38c4d192218c7b Mon Sep 17 00:00:00 2001 From: Minos Galanakis Date: Tue, 17 Mar 2026 16:55:40 +0000 Subject: [PATCH 61/61] Updated framework pointer Signed-off-by: Minos Galanakis --- framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework b/framework index bd6dfd6d8a..9b92164c47 160000 --- a/framework +++ b/framework @@ -1 +1 @@ -Subproject commit bd6dfd6d8af59ccd6ea24fcc37565809bd2c8cc7 +Subproject commit 9b92164c47fdaecb2600b417733507e2a105c3a5