From 3e6c4577850ba15a25b29d64bce6aa76808e6593 Mon Sep 17 00:00:00 2001 From: Ewerton Scaboro da Silva Date: Sun, 23 Aug 2026 17:28:40 +0000 Subject: [PATCH 1/4] Add mbedTLS 4.x support to the mbedTLS adapters mbedTLS 4.0 moved all cryptography into TF-PSA-Crypto and removed several APIs the SDK relied on: - mbedtls/entropy.h and mbedtls/ctr_drbg.h are no longer public; all randomness comes from the PSA Crypto RNG, which requires psa_crypto_init(). - mbedtls_ssl_conf_rng() was removed. - mbedtls_ssl_conf_min_version() and MBEDTLS_SSL_{MAJOR,MINOR}_VERSION_3 were removed in favor of mbedtls_ssl_conf_min_tls_version()/MBEDTLS_SSL_VERSION_TLS1_2. - mbedtls_pk_parse_key() no longer takes f_rng/p_rng. - mbedtls_pk_get_type()/MBEDTLS_PK_NONE became private. tlsio_mbedtls.c and httpapi_curl.c now guard those call sites on MBEDTLS_VERSION_NUMBER, so 2.16, 2.28, 3.x and 4.x all build from the same source. Whether a private key has been parsed is now tracked by the adapter itself instead of via mbedtls_pk_get_type(). Also fixes the non-MBEDTLS_SSL_MAX_FRAGMENT_LENGTH fallback, which still referenced MBEDTLS_SSL_MAX_CONTENT_LEN (removed in mbedTLS 3.0). Adds a CI job that builds and unit tests against mbedTLS 4.2.0. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- adapters/httpapi_curl.c | 28 +++++++++- adapters/tlsio_mbedtls.c | 68 ++++++++++++++++++++++- build/.vsts-ci.yml | 53 ++++++++++++++++++ tests/tlsio_mbedtls_ut/tlsio_mbedtls_ut.c | 63 ++++++++++++++++++--- 4 files changed, 198 insertions(+), 14 deletions(-) diff --git a/adapters/httpapi_curl.c b/adapters/httpapi_curl.c index 86af2d1b0..6da7940a3 100644 --- a/adapters/httpapi_curl.c +++ b/adapters/httpapi_curl.c @@ -23,11 +23,19 @@ #include "wolfssl/ssl.h" #include "wolfssl/error-ssl.h" #elif USE_MBEDTLS +#include "mbedtls/version.h" #include "mbedtls/x509_crt.h" #include "mbedtls/ssl.h" +#define TLSIO_MBEDTLS_VERSION_3_0_0 0x03000000 +#define TLSIO_MBEDTLS_VERSION_4_0_0 0x04000000 +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= TLSIO_MBEDTLS_VERSION_4_0_0 +// mbedTLS 4.x removed the public entropy and CTR_DRBG modules in favor of the +// PSA Crypto random generator. +#include "psa/crypto.h" +#else #include "mbedtls/entropy.h" #include "mbedtls/ctr_drbg.h" -#define TLSIO_MBEDTLS_VERSION_3_0_0 0x03000000 +#endif #endif #include "azure_c_shared_utility/shared_util_options.h" #include "azure_c_shared_utility/safe_math.h" @@ -337,7 +345,23 @@ static int parse_key(const char* key, mbedtls_pk_context* out_parsed_key) { int result; -#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= TLSIO_MBEDTLS_VERSION_3_0_0 +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= TLSIO_MBEDTLS_VERSION_4_0_0 + // mbedTLS 4.x takes its randomness from PSA Crypto, which must be + // initialized before parsing a key. psa_crypto_init() is idempotent. + psa_status_t psa_status = psa_crypto_init(); + + if (psa_status != PSA_SUCCESS) + { + LogError("psa_crypto_init failed (%d)", (int)psa_status); + result = MU_FAILURE; + } + else if ((result = mbedtls_pk_parse_key(out_parsed_key, + (const unsigned char *)key, (int)(strlen(key) + 1), + NULL, 0)) != 0) + { + LogError("mbedtls_pk_parse_key failed (%d)", result); + } +#elif defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= TLSIO_MBEDTLS_VERSION_3_0_0 const char *pers = "httpapi_curl"; mbedtls_entropy_context entropy; mbedtls_ctr_drbg_context ctr_drbg; diff --git a/adapters/tlsio_mbedtls.c b/adapters/tlsio_mbedtls.c index eaf0b126b..ffd22607c 100644 --- a/adapters/tlsio_mbedtls.c +++ b/adapters/tlsio_mbedtls.c @@ -9,18 +9,27 @@ #define TLSIO_MBEDTLS_VERSION_2_16_0 0x02160000 #define TLSIO_MBEDTLS_VERSION_3_0_0 0x03000000 +#define TLSIO_MBEDTLS_VERSION_4_0_0 0x04000000 #include "mbedtls/version.h" #include "mbedtls/debug.h" #include "mbedtls/ssl.h" +#include "mbedtls/error.h" +#if !defined(MBEDTLS_VERSION_NUMBER) || MBEDTLS_VERSION_NUMBER < TLSIO_MBEDTLS_VERSION_4_0_0 +// mbedTLS 4.x removed the public entropy and CTR_DRBG modules. Everything that +// needs randomness now goes through the PSA Crypto random generator, which is +// enabled by psa_crypto_init(). #include "mbedtls/entropy.h" #include "mbedtls/ctr_drbg.h" -#include "mbedtls/error.h" +#endif // MBEDTLS_VERSION_NUMBER #if !defined(MBEDTLS_VERSION_NUMBER) || MBEDTLS_VERSION_NUMBER < TLSIO_MBEDTLS_VERSION_3_0_0 #include "mbedtls/certs.h" #include "mbedtls/entropy_poll.h" #endif // MBEDTLS_VERSION_NUMBER #include "mbedtls/pk.h" +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= TLSIO_MBEDTLS_VERSION_4_0_0 +#include "psa/crypto.h" +#endif // MBEDTLS_VERSION_NUMBER #include "azure_c_shared_utility/gballoc.h" #include "azure_c_shared_utility/optimize_size.h" @@ -71,8 +80,10 @@ typedef struct TLS_IO_INSTANCE_TAG size_t socket_io_read_byte_count; SEND_COMPLETE_INFO send_complete_info; +#if !defined(MBEDTLS_VERSION_NUMBER) || MBEDTLS_VERSION_NUMBER < TLSIO_MBEDTLS_VERSION_4_0_0 mbedtls_entropy_context entropy; mbedtls_ctr_drbg_context ctr_drbg; +#endif // MBEDTLS_VERSION_NUMBER mbedtls_ssl_context ssl; mbedtls_ssl_config config; mbedtls_x509_crt trusted_certificates_parsed; @@ -83,6 +94,9 @@ typedef struct TLS_IO_INSTANCE_TAG char *hostname; mbedtls_x509_crt owncert; mbedtls_pk_context pKey; + // mbedTLS 4.x made mbedtls_pk_get_type() private, so the SDK tracks whether + // pKey holds a successfully parsed private key on its own. + bool pkey_parsed; char* x509_certificate; char* x509_private_key; @@ -146,8 +160,14 @@ static bool is_fragmented_send_request(TLS_IO_INSTANCE *tls_io_instance, size_t #else // MBEDTLS_VERSION_NUMBER size_t max_len = mbedtls_ssl_get_max_frag_len(&tls_io_instance->ssl); #endif // MBEDTLS_VERSION_NUMBER +#else +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= TLSIO_MBEDTLS_VERSION_3_0_0 + // MBEDTLS_SSL_MAX_CONTENT_LEN was removed in mbedTLS 3.0 in favor of + // separate incoming/outgoing limits. + size_t max_len = MBEDTLS_SSL_OUT_CONTENT_LEN; #else size_t max_len = MBEDTLS_SSL_MAX_CONTENT_LEN; +#endif // MBEDTLS_VERSION_NUMBER (void)tls_io_instance; #endif /* MBEDTLS_SSL_MAX_FRAGMENT_LENGTH */ bool result; @@ -456,6 +476,7 @@ static int on_io_send(void *context, const unsigned char *buf, size_t sz) return result; } +#if !defined(MBEDTLS_VERSION_NUMBER) || MBEDTLS_VERSION_NUMBER < TLSIO_MBEDTLS_VERSION_4_0_0 static int tlsio_entropy_poll(void *v, unsigned char *output, size_t len, size_t *olen) { (void)v; @@ -468,6 +489,7 @@ static int tlsio_entropy_poll(void *v, unsigned char *output, size_t len, size_t *olen = len; return result; } +#endif // MBEDTLS_VERSION_NUMBER // Un-initialize mbedTLS static void mbedtls_uninit(TLS_IO_INSTANCE *tls_io_instance) @@ -481,8 +503,14 @@ static void mbedtls_uninit(TLS_IO_INSTANCE *tls_io_instance) mbedtls_x509_crt_free(&tls_io_instance->trusted_certificates_parsed); mbedtls_x509_crt_free(&tls_io_instance->owncert); mbedtls_pk_free(&tls_io_instance->pKey); + tls_io_instance->pkey_parsed = false; +#if !defined(MBEDTLS_VERSION_NUMBER) || MBEDTLS_VERSION_NUMBER < TLSIO_MBEDTLS_VERSION_4_0_0 mbedtls_ctr_drbg_free(&tls_io_instance->ctr_drbg); mbedtls_entropy_free(&tls_io_instance->entropy); +#endif // MBEDTLS_VERSION_NUMBER + // Note: psa_crypto_free() is deliberately not called on mbedTLS 4.x. + // The PSA subsystem is global to the process and may still be in use by + // other tlsio instances or by the application itself. tls_io_instance->tls_status = TLS_STATE_NOT_INITIALIZED; } @@ -506,19 +534,42 @@ static void mbedtls_init(TLS_IO_INSTANCE *tls_io_instance) mbedtls_x509_crt_init(&tls_io_instance->trusted_certificates_parsed); mbedtls_x509_crt_init(&tls_io_instance->owncert); mbedtls_pk_init(&tls_io_instance->pKey); + tls_io_instance->pkey_parsed = false; +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= TLSIO_MBEDTLS_VERSION_4_0_0 + { + // mbedTLS 4.x routes every source of randomness (TLS, X.509 and key + // parsing included) through PSA Crypto, so psa_crypto_init() must + // succeed before anything else. It is idempotent. + psa_status_t psa_status = psa_crypto_init(); + + if (psa_status != PSA_SUCCESS) + { + LogError("psa_crypto_init failed (%d)", (int)psa_status); + } + } + (void)pers; +#else mbedtls_entropy_init(&tls_io_instance->entropy); // Add a weak entropy source here,avoid some platform doesn't have strong / hardware entropy mbedtls_entropy_add_source(&tls_io_instance->entropy, tlsio_entropy_poll, NULL, MBEDTLS_ENTROPY_MAX_GATHER, MBEDTLS_ENTROPY_SOURCE_WEAK); mbedtls_ctr_drbg_init(&tls_io_instance->ctr_drbg); mbedtls_ctr_drbg_seed(&tls_io_instance->ctr_drbg, mbedtls_entropy_func, &tls_io_instance->entropy, (const unsigned char *)pers, strlen(pers)); +#endif // MBEDTLS_VERSION_NUMBER mbedtls_ssl_config_init(&tls_io_instance->config); mbedtls_ssl_config_defaults(&tls_io_instance->config, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT); +#if !defined(MBEDTLS_VERSION_NUMBER) || MBEDTLS_VERSION_NUMBER < TLSIO_MBEDTLS_VERSION_4_0_0 + // mbedtls_ssl_conf_rng() was removed in mbedTLS 4.x. mbedtls_ssl_conf_rng(&tls_io_instance->config, mbedtls_ctr_drbg_random, &tls_io_instance->ctr_drbg); +#endif // MBEDTLS_VERSION_NUMBER mbedtls_ssl_conf_authmode(&tls_io_instance->config, MBEDTLS_SSL_VERIFY_REQUIRED); +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= TLSIO_MBEDTLS_VERSION_4_0_0 + mbedtls_ssl_conf_min_tls_version(&tls_io_instance->config, MBEDTLS_SSL_VERSION_TLS1_2); // v1.2 +#else mbedtls_ssl_conf_min_version(&tls_io_instance->config, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3); // v1.2 +#endif // MBEDTLS_VERSION_NUMBER mbedtls_ssl_init(&tls_io_instance->ssl); mbedtls_ssl_set_bio(&tls_io_instance->ssl, tls_io_instance, on_io_send, on_io_recv, NULL); @@ -934,7 +985,16 @@ static int parse_key(char* key, mbedtls_pk_context* out_parsed_key) { int result; -#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= TLSIO_MBEDTLS_VERSION_3_0_0 +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= TLSIO_MBEDTLS_VERSION_4_0_0 + // mbedTLS 4.x dropped the f_rng/p_rng arguments again: key parsing uses the + // PSA Crypto RNG, which mbedtls_init() has already brought up. + if ((result = mbedtls_pk_parse_key(out_parsed_key, + (const unsigned char *)key, (int)(strlen(key) + 1), + NULL, 0)) != 0) + { + LogError("mbedtls_pk_parse_key failed (%d)", result); + } +#elif defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= TLSIO_MBEDTLS_VERSION_3_0_0 const char *pers = "tlsio_mbedtls"; mbedtls_entropy_context entropy; mbedtls_ctr_drbg_context ctr_drbg; @@ -1023,7 +1083,7 @@ int tlsio_mbedtls_setoption(CONCRETE_IO_HANDLE tls_io, const char *optionName, c free(temp_cert); result = MU_FAILURE; } - else if (mbedtls_pk_get_type(&tls_io_instance->pKey) != MBEDTLS_PK_NONE && + else if (tls_io_instance->pkey_parsed && mbedtls_ssl_conf_own_cert(&tls_io_instance->config, &tls_io_instance->owncert, &tls_io_instance->pKey) != 0) { LogError("failure calling mbedtls_ssl_conf_own_cert"); @@ -1058,12 +1118,14 @@ int tlsio_mbedtls_setoption(CONCRETE_IO_HANDLE tls_io, const char *optionName, c } else if (tls_io_instance->owncert.version > 0 && mbedtls_ssl_conf_own_cert(&tls_io_instance->config, &tls_io_instance->owncert, &tls_io_instance->pKey)) { + tls_io_instance->pkey_parsed = true; LogError("failure calling mbedtls_ssl_conf_own_cert"); free(temp_key); result = MU_FAILURE; } else { + tls_io_instance->pkey_parsed = true; if (tls_io_instance->x509_private_key != NULL) { // Free the memory if it has been previously allocated diff --git a/build/.vsts-ci.yml b/build/.vsts-ci.yml index 37c5b3f6f..a09fc71d7 100644 --- a/build/.vsts-ci.yml +++ b/build/.vsts-ci.yml @@ -325,6 +325,59 @@ jobs: env: LD_LIBRARY_PATH: /usr/local/lib +- job: linux_mbed_4x + # mbedTLS 4.x moved all cryptography to TF-PSA-Crypto and removed the public + # entropy/CTR_DRBG modules, mbedtls_ssl_conf_rng() and + # mbedtls_ssl_conf_min_version(). adapters/tlsio_mbedtls.c handles that via + # MBEDTLS_VERSION_NUMBER guards; this job keeps that path compiling. + timeoutInMinutes: 60 + pool: + vmImage: 'ubuntu-24.04' + displayName: 'mbedTLS 4.x' + steps: + - checkout: self + submodules: true + - script: | + set -e + sudo apt-get update + # Purge the distro mbedTLS 3.x so the 4.x install under /usr/local is the + # only one CMake can find. + sudo apt-get purge -y 'libmbedtls*' 'libmbedcrypto*' 'libmbedx509*' || true + sudo apt-get install -y \ + clang \ + cmake \ + build-essential \ + pkg-config \ + curl \ + libcurl4-openssl-dev \ + uuid-dev \ + python3-pip + displayName: 'Host setup' + - script: | + set -e + git clone --depth 1 -b mbedtls-4.2.0 https://github.com/Mbed-TLS/mbedtls /tmp/mbedtls-4x + cd /tmp/mbedtls-4x + git submodule update --init --recursive --depth 1 + python3 -m pip install --break-system-packages -r scripts/basic.requirements.txt + mkdir build && cd build + cmake -DUSE_SHARED_MBEDTLS_LIBRARY=ON -DENABLE_TESTING=Off -DENABLE_PROGRAMS=Off -DCMAKE_INSTALL_PREFIX=/usr/local .. + make -j$(nproc) + sudo make install + sudo ldconfig + displayName: 'Install mbedTLS 4.2' + - script: | + chmod +x jenkins/linux_mbed_clang_buildonly.sh + ./jenkins/linux_mbed_clang_buildonly.sh + displayName: 'Clang Build' + env: + LD_LIBRARY_PATH: /usr/local/lib + - script: | + sudo chmod 755 jenkins/linux_mbed.sh + sudo -E ./jenkins/linux_mbed.sh + displayName: 'Build' + env: + LD_LIBRARY_PATH: /usr/local/lib + - job: wolfssl timeoutInMinutes: 60 pool: diff --git a/tests/tlsio_mbedtls_ut/tlsio_mbedtls_ut.c b/tests/tlsio_mbedtls_ut/tlsio_mbedtls_ut.c index 28a997e7e..370bc384b 100644 --- a/tests/tlsio_mbedtls_ut/tlsio_mbedtls_ut.c +++ b/tests/tlsio_mbedtls_ut/tlsio_mbedtls_ut.c @@ -52,15 +52,26 @@ static void my_gballoc_free(void* ptr) #include "mbedtls/config.h" #include "mbedtls/version.h" #include "mbedtls/debug.h" +#include "mbedtls/version.h" #include "mbedtls/ssl.h" +#include "mbedtls/error.h" + +#define TLSIO_MBEDTLS_VERSION_2_16_0 0x02160000 +#define TLSIO_MBEDTLS_VERSION_3_0_0 0x03000000 +#define TLSIO_MBEDTLS_VERSION_4_0_0 0x04000000 + +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= TLSIO_MBEDTLS_VERSION_4_0_0 +// mbedTLS 4.x removed the public entropy/CTR_DRBG modules; randomness comes +// from PSA Crypto instead. +#include "psa/crypto.h" +#else #include "mbedtls/entropy.h" #include "mbedtls/ctr_drbg.h" -#include "mbedtls/error.h" +#endif +#if !defined(MBEDTLS_VERSION_NUMBER) || MBEDTLS_VERSION_NUMBER < TLSIO_MBEDTLS_VERSION_3_0_0 #include "mbedtls/certs.h" #include "mbedtls/entropy_poll.h" - -#define TLSIO_MBEDTLS_VERSION_3_0_0 0x03000000 -#define TLSIO_MBEDTLS_VERSION_2_16_0 0x02160000 +#endif /** * Include the mockable headers here. @@ -87,18 +98,22 @@ MOCKABLE_FUNCTION(, int, mbedtls_x509_crt_parse, mbedtls_x509_crt*, crt, const u MOCKABLE_FUNCTION(, void, mbedtls_x509_crt_init, mbedtls_x509_crt*, crt); MOCKABLE_FUNCTION(, void, mbedtls_x509_crt_free, mbedtls_x509_crt*, crt); -#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= TLSIO_MBEDTLS_VERSION_3_0_0 +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= TLSIO_MBEDTLS_VERSION_3_0_0 && MBEDTLS_VERSION_NUMBER < TLSIO_MBEDTLS_VERSION_4_0_0 MOCKABLE_FUNCTION(, int, mbedtls_pk_parse_key, mbedtls_pk_context*, ctx, const unsigned char*, key, size_t, keylen, const unsigned char*, pwd, size_t, pwdlen, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng); #else MOCKABLE_FUNCTION(, int, mbedtls_pk_parse_key, mbedtls_pk_context*, ctx, const unsigned char*, key, size_t, keylen, const unsigned char*, pwd, size_t, pwdlen); #endif +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= TLSIO_MBEDTLS_VERSION_4_0_0 +MOCKABLE_FUNCTION(, psa_status_t, psa_crypto_init); +#else MOCKABLE_FUNCTION(, void, mbedtls_ctr_drbg_init, mbedtls_ctr_drbg_context*, ctx); MOCKABLE_FUNCTION(, void, mbedtls_ctr_drbg_free, mbedtls_ctr_drbg_context*, ctx) MOCKABLE_FUNCTION(, int, mbedtls_ctr_drbg_seed_entropy_len, mbedtls_ctr_drbg_context*, ctx, f_entropy, fe, void*, p_entropy, const unsigned char*, custom, size_t, len, size_t, entropy_len); MOCKABLE_FUNCTION(, int, mbedtls_ctr_drbg_random_with_add, void*, p_rng, unsigned char*, output, size_t, output_len, const unsigned char*, additional, size_t, add_len); MOCKABLE_FUNCTION(, int, mbedtls_ctr_drbg_seed, mbedtls_ctr_drbg_context*, ctx, f_entropy, fe, void*, p_entropy, const unsigned char*, custom, size_t, len); MOCKABLE_FUNCTION(, int, mbedtls_ctr_drbg_random, void*, p_rng, unsigned char*, output, size_t, output_len); +#endif MOCKABLE_FUNCTION(, void, mbedtls_ssl_init, mbedtls_ssl_context*, ssl) MOCKABLE_FUNCTION(, void, mbedtls_ssl_free, mbedtls_ssl_context*, ssl) @@ -114,11 +129,17 @@ MOCKABLE_FUNCTION(, size_t, mbedtls_ssl_get_max_frag_len, const mbedtls_ssl_cont MOCKABLE_FUNCTION(, int, mbedtls_ssl_get_max_out_record_payload, const mbedtls_ssl_context*, ssl) MOCKABLE_FUNCTION(, void, mbedtls_ssl_conf_authmode, mbedtls_ssl_config*, conf, int, authmode) +#if !defined(MBEDTLS_VERSION_NUMBER) || MBEDTLS_VERSION_NUMBER < TLSIO_MBEDTLS_VERSION_4_0_0 MOCKABLE_FUNCTION(, void, mbedtls_ssl_conf_rng, mbedtls_ssl_config*, conf, f_rng, fr, void*, p_rng); +#endif MOCKABLE_FUNCTION(, void, mbedtls_ssl_conf_dbg, mbedtls_ssl_config*, conf, f_dbg, fd, void*, p_dbg); MOCKABLE_FUNCTION(, void, mbedtls_ssl_set_bio, mbedtls_ssl_context*, ssl, void*, p_bio, mbedtls_ssl_send_t*, f_send, mbedtls_ssl_recv_t*, f_recv, mbedtls_ssl_recv_timeout_t*, f_recv_timeout); MOCKABLE_FUNCTION(, void, mbedtls_ssl_conf_ca_chain, mbedtls_ssl_config*, conf, mbedtls_x509_crt*, ca_chain, mbedtls_x509_crl*, ca_crl); +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= TLSIO_MBEDTLS_VERSION_4_0_0 +MOCKABLE_FUNCTION(, void, mbedtls_ssl_conf_min_tls_version, mbedtls_ssl_config*, conf, mbedtls_ssl_protocol_version, tls_version); +#else MOCKABLE_FUNCTION(, void, mbedtls_ssl_conf_min_version, mbedtls_ssl_config*, conf, int, major, int, minor); +#endif MOCKABLE_FUNCTION(, int, mbedtls_ssl_set_hostname, mbedtls_ssl_context*, ssl, const char*, hostname); MOCKABLE_FUNCTION(, int, mbedtls_ssl_handshake, mbedtls_ssl_context*, ssl); @@ -134,13 +155,14 @@ MOCKABLE_FUNCTION(, void, mbedtls_ssl_conf_renegotiation, mbedtls_ssl_config*, c MOCKABLE_FUNCTION(, void, mbedtls_debug_set_threshold, int, threshold); +#if !defined(MBEDTLS_VERSION_NUMBER) || MBEDTLS_VERSION_NUMBER < TLSIO_MBEDTLS_VERSION_4_0_0 MOCKABLE_FUNCTION(, void, mbedtls_entropy_init, mbedtls_entropy_context*, ctx); MOCKABLE_FUNCTION(, int, mbedtls_entropy_add_source, mbedtls_entropy_context*, ctx, mbedtls_entropy_f_source_ptr, f_source, void*, p_source, size_t, threshold, int, strong); MOCKABLE_FUNCTION(, int, mbedtls_entropy_func, void*, data, unsigned char*, output, size_t, len); MOCKABLE_FUNCTION(, void, mbedtls_entropy_free, mbedtls_entropy_context*, ctx) +#endif MOCKABLE_FUNCTION(, void, mbedtls_pk_init, mbedtls_pk_context*, ctx); -MOCKABLE_FUNCTION(, mbedtls_pk_type_t, mbedtls_pk_get_type, const mbedtls_pk_context*, ctx); MOCKABLE_FUNCTION(, void, mbedtls_pk_free, mbedtls_pk_context*, ctx); MOCKABLE_FUNCTION(, void, on_io_open_complete, void*, context, IO_OPEN_RESULT, open_result); @@ -176,7 +198,9 @@ static mbedtls_ssl_recv_t* mbed_f_recv = NULL; static mbedtls_ssl_recv_timeout_t* mbed_f_recv_timeout = NULL; static void* g_mbedtls_ctx = NULL; +#if !defined(MBEDTLS_VERSION_NUMBER) || MBEDTLS_VERSION_NUMBER < TLSIO_MBEDTLS_VERSION_4_0_0 static mbedtls_entropy_f_source_ptr g_entropy_f_source; +#endif #define MAX_RETRY 20 #define RECEIVE_BUFFER_SIZE 1024 @@ -258,6 +282,7 @@ static void my_mbedtls_ssl_set_bio(mbedtls_ssl_context* ssl, void* p_bio, mbedtl mbed_f_recv_timeout = f_recv_timeout; } +#if !defined(MBEDTLS_VERSION_NUMBER) || MBEDTLS_VERSION_NUMBER < TLSIO_MBEDTLS_VERSION_4_0_0 static int my_mbedtls_entropy_add_source(mbedtls_entropy_context* ctx, mbedtls_entropy_f_source_ptr f_source, void* p_source, size_t threshold, int strong) { (void)ctx; @@ -267,6 +292,7 @@ static int my_mbedtls_entropy_add_source(mbedtls_entropy_context* ctx, mbedtls_e g_entropy_f_source = f_source; return 0; } +#endif static int my_mbedtls_ssl_write(mbedtls_ssl_context* ssl, const unsigned char *buf, size_t len) { @@ -371,7 +397,12 @@ BEGIN_TEST_SUITE(tlsio_mbedtls_ut) result = umocktypes_charptr_register_types(); ASSERT_ARE_EQUAL(int, 0, result); +#if !defined(MBEDTLS_VERSION_NUMBER) || MBEDTLS_VERSION_NUMBER < TLSIO_MBEDTLS_VERSION_4_0_0 REGISTER_UMOCK_ALIAS_TYPE(mbedtls_entropy_f_source_ptr, void*); +#else + REGISTER_UMOCK_ALIAS_TYPE(psa_status_t, int); + REGISTER_UMOCK_ALIAS_TYPE(mbedtls_ssl_protocol_version, int); +#endif REGISTER_UMOCK_ALIAS_TYPE(f_entropy, void*); REGISTER_UMOCK_ALIAS_TYPE(f_rng, void*); REGISTER_UMOCK_ALIAS_TYPE(f_dbg, void*); @@ -381,7 +412,6 @@ BEGIN_TEST_SUITE(tlsio_mbedtls_ut) REGISTER_UMOCK_ALIAS_TYPE(ON_IO_ERROR, void*); REGISTER_UMOCK_ALIAS_TYPE(ON_IO_CLOSE_COMPLETE, void*); REGISTER_UMOCK_ALIAS_TYPE(ON_SEND_COMPLETE, void*); - REGISTER_UMOCK_ALIAS_TYPE(mbedtls_pk_type_t, int); REGISTER_TYPE(IO_SEND_RESULT, IO_SEND_RESULT); REGISTER_TYPE(IO_OPEN_RESULT, IO_OPEN_RESULT); @@ -409,7 +439,9 @@ BEGIN_TEST_SUITE(tlsio_mbedtls_ut) REGISTER_GLOBAL_MOCK_RETURN(mbedtls_ssl_read, 0); REGISTER_GLOBAL_MOCK_HOOK(mbedtls_ssl_set_bio, my_mbedtls_ssl_set_bio); +#if !defined(MBEDTLS_VERSION_NUMBER) || MBEDTLS_VERSION_NUMBER < TLSIO_MBEDTLS_VERSION_4_0_0 REGISTER_GLOBAL_MOCK_HOOK(mbedtls_entropy_add_source, my_mbedtls_entropy_add_source); +#endif REGISTER_GLOBAL_MOCK_HOOK(mbedtls_ssl_write, my_mbedtls_ssl_write); REGISTER_GLOBAL_MOCK_HOOK(on_io_open_complete, my_on_io_open_complete); @@ -479,6 +511,13 @@ BEGIN_TEST_SUITE(tlsio_mbedtls_ut) STRICT_EXPECTED_CALL(mbedtls_x509_crt_init(IGNORED_ARG)); STRICT_EXPECTED_CALL(mbedtls_x509_crt_init(IGNORED_ARG)); STRICT_EXPECTED_CALL(mbedtls_pk_init(IGNORED_ARG)); +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= TLSIO_MBEDTLS_VERSION_4_0_0 + STRICT_EXPECTED_CALL(psa_crypto_init()); + STRICT_EXPECTED_CALL(mbedtls_ssl_config_init(IGNORED_ARG)); + STRICT_EXPECTED_CALL(mbedtls_ssl_config_defaults(IGNORED_ARG, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)); + STRICT_EXPECTED_CALL(mbedtls_ssl_conf_authmode(IGNORED_ARG, MBEDTLS_SSL_VERIFY_REQUIRED)); + STRICT_EXPECTED_CALL(mbedtls_ssl_conf_min_tls_version(IGNORED_ARG, MBEDTLS_SSL_VERSION_TLS1_2)); +#else STRICT_EXPECTED_CALL(mbedtls_entropy_init(IGNORED_ARG)); STRICT_EXPECTED_CALL(mbedtls_entropy_add_source(IGNORED_ARG, IGNORED_ARG, NULL, IGNORED_ARG, IGNORED_ARG)); STRICT_EXPECTED_CALL(mbedtls_ctr_drbg_init(IGNORED_ARG)); @@ -488,6 +527,7 @@ BEGIN_TEST_SUITE(tlsio_mbedtls_ut) STRICT_EXPECTED_CALL(mbedtls_ssl_conf_rng(IGNORED_ARG, IGNORED_ARG, IGNORED_ARG)); STRICT_EXPECTED_CALL(mbedtls_ssl_conf_authmode(IGNORED_ARG, MBEDTLS_SSL_VERIFY_REQUIRED)); STRICT_EXPECTED_CALL(mbedtls_ssl_conf_min_version(IGNORED_ARG, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3)); +#endif STRICT_EXPECTED_CALL(mbedtls_ssl_init(IGNORED_ARG)); STRICT_EXPECTED_CALL(mbedtls_ssl_set_bio(IGNORED_ARG, IGNORED_ARG, IGNORED_ARG, IGNORED_ARG, NULL)); @@ -585,8 +625,10 @@ BEGIN_TEST_SUITE(tlsio_mbedtls_ut) STRICT_EXPECTED_CALL(mbedtls_x509_crt_free(IGNORED_ARG)); STRICT_EXPECTED_CALL(mbedtls_x509_crt_free(IGNORED_ARG)); STRICT_EXPECTED_CALL(mbedtls_pk_free(IGNORED_ARG)); +#if !defined(MBEDTLS_VERSION_NUMBER) || MBEDTLS_VERSION_NUMBER < TLSIO_MBEDTLS_VERSION_4_0_0 STRICT_EXPECTED_CALL(mbedtls_ctr_drbg_free(IGNORED_ARG)); STRICT_EXPECTED_CALL(mbedtls_entropy_free(IGNORED_ARG)); +#endif STRICT_EXPECTED_CALL(xio_destroy(IGNORED_ARG)); STRICT_EXPECTED_CALL(gballoc_free(IGNORED_ARG)); STRICT_EXPECTED_CALL(gballoc_free(IGNORED_ARG)); @@ -702,6 +744,9 @@ BEGIN_TEST_SUITE(tlsio_mbedtls_ut) tlsio_mbedtls_destroy(handle); } +#if !defined(MBEDTLS_VERSION_NUMBER) || MBEDTLS_VERSION_NUMBER < TLSIO_MBEDTLS_VERSION_4_0_0 + // mbedTLS 4.x uses the PSA Crypto RNG, so the adapter no longer registers + // its own weak entropy source. TEST_FUNCTION(tlsio_entropy_poll_success) { //arrange @@ -727,6 +772,7 @@ BEGIN_TEST_SUITE(tlsio_mbedtls_ut) //cleanup tlsio_mbedtls_destroy(handle); } +#endif TEST_FUNCTION(tlsio_mbedtls_close_handle_NULL_fail) { @@ -1187,7 +1233,6 @@ BEGIN_TEST_SUITE(tlsio_mbedtls_ut) STRICT_EXPECTED_CALL(mallocAndStrcpy_s(IGNORED_ARG, TEST_X509_CERTIFICATE)); STRICT_EXPECTED_CALL(mbedtls_x509_crt_parse(IGNORED_ARG, IGNORED_ARG, IGNORED_ARG)); - STRICT_EXPECTED_CALL(mbedtls_pk_get_type(IGNORED_ARG)).SetReturn(MBEDTLS_PK_NONE); //act tlsio_mbedtls_setoption(handle, SU_OPTION_X509_CERT, TEST_X509_CERTIFICATE); @@ -1217,7 +1262,7 @@ BEGIN_TEST_SUITE(tlsio_mbedtls_ut) STRICT_EXPECTED_CALL(mallocAndStrcpy_s(IGNORED_ARG, TEST_X509_KEY)); -#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= TLSIO_MBEDTLS_VERSION_3_0_0 +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= TLSIO_MBEDTLS_VERSION_3_0_0 && MBEDTLS_VERSION_NUMBER < TLSIO_MBEDTLS_VERSION_4_0_0 STRICT_EXPECTED_CALL(mbedtls_pk_parse_key(IGNORED_ARG, IGNORED_ARG, IGNORED_ARG, NULL, 0, IGNORED_ARG, IGNORED_ARG)) .CopyOutArgumentBuffer_ctx(&pk_info, sizeof(pk_info)); #else From 7a950dce81fd27f7ba1916ca7960afddc51c3916 Mon Sep 17 00:00:00 2001 From: Ewerton Scaboro da Silva Date: Sun, 23 Aug 2026 17:54:13 +0000 Subject: [PATCH 2/4] Fix unguarded mbedtls/config.h include in the tlsio_mbedtls unit test mbedtls/config.h was renamed to mbedtls/mbedtls_config.h in mbedTLS 3.0 and is pulled in automatically by mbedtls/build_info.h from 3.0 onwards, so the unconditional include broke the build on both 3.x and 4.x. Guard it to pre-3.0 and drop a duplicate mbedtls/version.h include, moving version.h first so MBEDTLS_VERSION_NUMBER is defined for the guards. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/tlsio_mbedtls_ut/tlsio_mbedtls_ut.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/tests/tlsio_mbedtls_ut/tlsio_mbedtls_ut.c b/tests/tlsio_mbedtls_ut/tlsio_mbedtls_ut.c index 370bc384b..8cca52488 100644 --- a/tests/tlsio_mbedtls_ut/tlsio_mbedtls_ut.c +++ b/tests/tlsio_mbedtls_ut/tlsio_mbedtls_ut.c @@ -49,17 +49,24 @@ static void my_gballoc_free(void* ptr) #include "umock_c/umock_c_negative_tests.h" #include "macro_utils/macro_utils.h" -#include "mbedtls/config.h" -#include "mbedtls/version.h" -#include "mbedtls/debug.h" +// mbedtls/version.h pulls in the configuration (via mbedtls/build_info.h on +// 3.x and later), so it has to come first for MBEDTLS_VERSION_NUMBER to be +// defined for the guards below. #include "mbedtls/version.h" -#include "mbedtls/ssl.h" -#include "mbedtls/error.h" #define TLSIO_MBEDTLS_VERSION_2_16_0 0x02160000 #define TLSIO_MBEDTLS_VERSION_3_0_0 0x03000000 #define TLSIO_MBEDTLS_VERSION_4_0_0 0x04000000 +#if !defined(MBEDTLS_VERSION_NUMBER) || MBEDTLS_VERSION_NUMBER < TLSIO_MBEDTLS_VERSION_3_0_0 +// mbedtls/config.h was renamed to mbedtls/mbedtls_config.h in mbedTLS 3.0 and +// is included automatically by mbedtls/build_info.h from 3.0 onwards. +#include "mbedtls/config.h" +#endif +#include "mbedtls/debug.h" +#include "mbedtls/ssl.h" +#include "mbedtls/error.h" + #if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= TLSIO_MBEDTLS_VERSION_4_0_0 // mbedTLS 4.x removed the public entropy/CTR_DRBG modules; randomness comes // from PSA Crypto instead. From 42327e2aa8fd0e0ee550199d6ddb5f84696b8dba Mon Sep 17 00:00:00 2001 From: Ewerton Scaboro da Silva Date: Sun, 23 Aug 2026 18:06:46 +0000 Subject: [PATCH 3/4] Fix tlsio_mbedtls unit test against mbedTLS 3.x/4.x Two problems the mbedTLS 4.x job surfaced: - mbedtls_ssl_conf_min_tls_version() is a static inline function in the mbedTLS headers, so declaring it with MOCKABLE_FUNCTION produced a redefinition error. It is no longer mocked; the real inline runs and the corresponding STRICT_EXPECTED_CALL is dropped. - The mbedtls_ssl_write test hook assigned ssl->out_msgtype directly, which has been ssl->MBEDTLS_PRIVATE(out_msgtype) since mbedTLS 3.0. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/tlsio_mbedtls_ut/tlsio_mbedtls_ut.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/tlsio_mbedtls_ut/tlsio_mbedtls_ut.c b/tests/tlsio_mbedtls_ut/tlsio_mbedtls_ut.c index 8cca52488..57a387fa3 100644 --- a/tests/tlsio_mbedtls_ut/tlsio_mbedtls_ut.c +++ b/tests/tlsio_mbedtls_ut/tlsio_mbedtls_ut.c @@ -142,9 +142,9 @@ MOCKABLE_FUNCTION(, void, mbedtls_ssl_conf_rng, mbedtls_ssl_config*, conf, f_rng MOCKABLE_FUNCTION(, void, mbedtls_ssl_conf_dbg, mbedtls_ssl_config*, conf, f_dbg, fd, void*, p_dbg); MOCKABLE_FUNCTION(, void, mbedtls_ssl_set_bio, mbedtls_ssl_context*, ssl, void*, p_bio, mbedtls_ssl_send_t*, f_send, mbedtls_ssl_recv_t*, f_recv, mbedtls_ssl_recv_timeout_t*, f_recv_timeout); MOCKABLE_FUNCTION(, void, mbedtls_ssl_conf_ca_chain, mbedtls_ssl_config*, conf, mbedtls_x509_crt*, ca_chain, mbedtls_x509_crl*, ca_crl); -#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= TLSIO_MBEDTLS_VERSION_4_0_0 -MOCKABLE_FUNCTION(, void, mbedtls_ssl_conf_min_tls_version, mbedtls_ssl_config*, conf, mbedtls_ssl_protocol_version, tls_version); -#else +#if !defined(MBEDTLS_VERSION_NUMBER) || MBEDTLS_VERSION_NUMBER < TLSIO_MBEDTLS_VERSION_4_0_0 +// Note: mbedtls_ssl_conf_min_tls_version() is a static inline function in +// mbedTLS 3.x/4.x, so it cannot be mocked - the real one runs on 4.x. MOCKABLE_FUNCTION(, void, mbedtls_ssl_conf_min_version, mbedtls_ssl_config*, conf, int, major, int, minor); #endif @@ -309,7 +309,11 @@ static int my_mbedtls_ssl_write(mbedtls_ssl_context* ssl, const unsigned char *b if (mbed_f_send != NULL) { // send tls app data +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= TLSIO_MBEDTLS_VERSION_3_0_0 + ssl->MBEDTLS_PRIVATE(out_msgtype) = MBEDTLS_SSL_MSG_APPLICATION_DATA; +#else ssl->out_msgtype = MBEDTLS_SSL_MSG_APPLICATION_DATA; +#endif mbed_f_send(g_mbedtls_ctx, buf, len); } @@ -408,7 +412,6 @@ BEGIN_TEST_SUITE(tlsio_mbedtls_ut) REGISTER_UMOCK_ALIAS_TYPE(mbedtls_entropy_f_source_ptr, void*); #else REGISTER_UMOCK_ALIAS_TYPE(psa_status_t, int); - REGISTER_UMOCK_ALIAS_TYPE(mbedtls_ssl_protocol_version, int); #endif REGISTER_UMOCK_ALIAS_TYPE(f_entropy, void*); REGISTER_UMOCK_ALIAS_TYPE(f_rng, void*); @@ -523,7 +526,8 @@ BEGIN_TEST_SUITE(tlsio_mbedtls_ut) STRICT_EXPECTED_CALL(mbedtls_ssl_config_init(IGNORED_ARG)); STRICT_EXPECTED_CALL(mbedtls_ssl_config_defaults(IGNORED_ARG, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)); STRICT_EXPECTED_CALL(mbedtls_ssl_conf_authmode(IGNORED_ARG, MBEDTLS_SSL_VERIFY_REQUIRED)); - STRICT_EXPECTED_CALL(mbedtls_ssl_conf_min_tls_version(IGNORED_ARG, MBEDTLS_SSL_VERSION_TLS1_2)); + // mbedtls_ssl_conf_min_tls_version() is static inline and therefore + // not mocked, so it produces no expected call here. #else STRICT_EXPECTED_CALL(mbedtls_entropy_init(IGNORED_ARG)); STRICT_EXPECTED_CALL(mbedtls_entropy_add_source(IGNORED_ARG, IGNORED_ARG, NULL, IGNORED_ARG, IGNORED_ARG)); From a14e5d569640d09552115901dcccd2e02e89dcc4 Mon Sep 17 00:00:00 2001 From: Ewerton Scaboro da Silva Date: Sun, 23 Aug 2026 18:46:16 +0000 Subject: [PATCH 4/4] Fail creation when PSA crypto initialization fails on mbedTLS 4.x Addresses review feedback on both adapters. httpapi_curl.c: initializing PSA inside parse_key() was too late and, for the trusted-CA-only path, never happened at all - ssl_ctx_callback() calls mbedtls_x509_crt_parse() before parse_key(), and skips parse_key() entirely when only a CA chain is configured. mbedTLS 4.x requires a successful psa_crypto_init() before parsing a certificate as well as a key. A new init_psa_crypto() helper is now called once at the top of the mbedTLS branch of ssl_ctx_callback(), before any X.509 operation, and returns CURLE_SSL_CERTPROBLEM on failure. parse_key() keeps an idempotent call as a safety net for any future caller. tlsio_mbedtls.c: a failed psa_crypto_init() was only logged, so creation still reported success and handed back a handle that could not safely parse credentials or handshake. mbedtls_init() now returns int, performs the PSA initialization before touching any mbedTLS context so nothing needs unwinding when it fails, and tlsio_mbedtls_create() releases the socket IO, hostname and instance and returns NULL. Adds tlsio_mbedtls_create_psa_crypto_init_fails to cover the new failure path, and reorders the psa_crypto_init expectation to match. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- adapters/httpapi_curl.c | 37 +++++++++++-- adapters/tlsio_mbedtls.c | 63 ++++++++++++++++------- tests/tlsio_mbedtls_ut/tlsio_mbedtls_ut.c | 39 +++++++++++++- 3 files changed, 115 insertions(+), 24 deletions(-) diff --git a/adapters/httpapi_curl.c b/adapters/httpapi_curl.c index 6da7940a3..366723488 100644 --- a/adapters/httpapi_curl.c +++ b/adapters/httpapi_curl.c @@ -341,13 +341,15 @@ static size_t ContentWriteFunction(void *ptr, size_t size, size_t nmemb, void *u } #ifdef USE_MBEDTLS -static int parse_key(const char* key, mbedtls_pk_context* out_parsed_key) +// mbedTLS 4.x requires psa_crypto_init() to have succeeded before ANY +// cryptographic operation, including indirect ones such as parsing a +// certificate or a private key. It is idempotent, and a no-op on earlier +// versions, which do not have a PSA subsystem to bring up. +static int init_psa_crypto(void) { - int result; + int result = 0; #if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= TLSIO_MBEDTLS_VERSION_4_0_0 - // mbedTLS 4.x takes its randomness from PSA Crypto, which must be - // initialized before parsing a key. psa_crypto_init() is idempotent. psa_status_t psa_status = psa_crypto_init(); if (psa_status != PSA_SUCCESS) @@ -355,6 +357,23 @@ static int parse_key(const char* key, mbedtls_pk_context* out_parsed_key) LogError("psa_crypto_init failed (%d)", (int)psa_status); result = MU_FAILURE; } +#endif // MBEDTLS_VERSION_NUMBER + + return result; +} + +static int parse_key(const char* key, mbedtls_pk_context* out_parsed_key) +{ + int result; + +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= TLSIO_MBEDTLS_VERSION_4_0_0 + // mbedTLS 4.x takes its randomness from PSA Crypto. The caller is expected + // to have brought PSA up already; this call is a cheap idempotent safety + // net in case parse_key() ever gains another caller. + if (init_psa_crypto() != 0) + { + result = MU_FAILURE; + } else if ((result = mbedtls_pk_parse_key(out_parsed_key, (const unsigned char *)key, (int)(strlen(key) + 1), NULL, 0)) != 0) @@ -477,8 +496,16 @@ static CURLcode ssl_ctx_callback(CURL *curl, void *ssl_ctx, void *userptr) result = CURLE_SSL_CERTPROBLEM; } #elif USE_MBEDTLS + // mbedTLS 4.x requires PSA to be up before ANY X.509 or key parsing. + // Both the client-certificate path and the trusted-CA-only path below + // call mbedtls_x509_crt_parse(), so this has to happen before either. + if (init_psa_crypto() != 0) + { + LogError("unable to initialize PSA crypto"); + result = CURLE_SSL_CERTPROBLEM; + } // set device cert and key - if ( + else if ( (httpHandleData->x509certificate != NULL) && (httpHandleData->x509privatekey != NULL) && !( (mbedtls_x509_crt_parse(&httpHandleData->cert, (const unsigned char *)httpHandleData->x509certificate, (int)(strlen(httpHandleData->x509certificate) + 1)) == 0) && diff --git a/adapters/tlsio_mbedtls.c b/adapters/tlsio_mbedtls.c index ffd22607c..9ceddbbb2 100644 --- a/adapters/tlsio_mbedtls.c +++ b/adapters/tlsio_mbedtls.c @@ -520,9 +520,14 @@ static void mbedtls_uninit(TLS_IO_INSTANCE *tls_io_instance) } } -static void mbedtls_init(TLS_IO_INSTANCE *tls_io_instance) +static int mbedtls_init(TLS_IO_INSTANCE *tls_io_instance) { const char* pers = "azure_iot_client"; + int result = 0; +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= TLSIO_MBEDTLS_VERSION_4_0_0 + psa_status_t psa_status; +#endif // MBEDTLS_VERSION_NUMBER + if (tls_io_instance->tls_status != TLS_STATE_INITIALIZED) { if (tls_io_instance->tls_status == TLS_STATE_CLOSING) @@ -530,26 +535,30 @@ static void mbedtls_init(TLS_IO_INSTANCE *tls_io_instance) // The underlying connection has been closed, so here un-initialize first mbedtls_uninit(tls_io_instance); } + +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= TLSIO_MBEDTLS_VERSION_4_0_0 + // mbedTLS 4.x routes every source of randomness (TLS, X.509 and key + // parsing included) through PSA Crypto, so psa_crypto_init() must + // succeed before any other mbedTLS call. It is idempotent. This is done + // first so that nothing has been initialized yet if it fails. + (void)pers; + psa_status = psa_crypto_init(); + + if (psa_status != PSA_SUCCESS) + { + LogError("psa_crypto_init failed (%d)", (int)psa_status); + result = MU_FAILURE; + } + else +#endif // MBEDTLS_VERSION_NUMBER + { // mbedTLS initialize... mbedtls_x509_crt_init(&tls_io_instance->trusted_certificates_parsed); mbedtls_x509_crt_init(&tls_io_instance->owncert); mbedtls_pk_init(&tls_io_instance->pKey); tls_io_instance->pkey_parsed = false; -#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= TLSIO_MBEDTLS_VERSION_4_0_0 - { - // mbedTLS 4.x routes every source of randomness (TLS, X.509 and key - // parsing included) through PSA Crypto, so psa_crypto_init() must - // succeed before anything else. It is idempotent. - psa_status_t psa_status = psa_crypto_init(); - - if (psa_status != PSA_SUCCESS) - { - LogError("psa_crypto_init failed (%d)", (int)psa_status); - } - } - (void)pers; -#else +#if !defined(MBEDTLS_VERSION_NUMBER) || MBEDTLS_VERSION_NUMBER < TLSIO_MBEDTLS_VERSION_4_0_0 mbedtls_entropy_init(&tls_io_instance->entropy); // Add a weak entropy source here,avoid some platform doesn't have strong / hardware entropy mbedtls_entropy_add_source(&tls_io_instance->entropy, tlsio_entropy_poll, NULL, MBEDTLS_ENTROPY_MAX_GATHER, MBEDTLS_ENTROPY_SOURCE_WEAK); @@ -581,7 +590,10 @@ static void mbedtls_init(TLS_IO_INSTANCE *tls_io_instance) mbedtls_ssl_setup(&tls_io_instance->ssl, &tls_io_instance->config); tls_io_instance->tls_status = TLS_STATE_INITIALIZED; + } } + + return result; } CONCRETE_IO_HANDLE tlsio_mbedtls_create(void *io_create_parameters) @@ -642,9 +654,24 @@ CONCRETE_IO_HANDLE tlsio_mbedtls_create(void *io_create_parameters) else { result->tls_status = TLS_STATE_NOT_INITIALIZED; - mbedtls_init((void*)result); - result->tlsio_state = TLSIO_STATE_NOT_OPEN; - result->invoke_on_send_complete_callback_for_fragments = tls_io_config->invoke_on_send_complete_callback_for_fragments; + + // Note: mbedtls_init() only fails before it has initialized + // any mbedTLS context, so there is nothing to unwind with + // mbedtls_uninit() here. Keep that true if it gains new + // failure points. + if (mbedtls_init((void*)result) != 0) + { + LogError("Failure initializing mbedTLS"); + xio_destroy(result->socket_io); + free(result->hostname); + free(result); + result = NULL; + } + else + { + result->tlsio_state = TLSIO_STATE_NOT_OPEN; + result->invoke_on_send_complete_callback_for_fragments = tls_io_config->invoke_on_send_complete_callback_for_fragments; + } } } } diff --git a/tests/tlsio_mbedtls_ut/tlsio_mbedtls_ut.c b/tests/tlsio_mbedtls_ut/tlsio_mbedtls_ut.c index 57a387fa3..85b30c61c 100644 --- a/tests/tlsio_mbedtls_ut/tlsio_mbedtls_ut.c +++ b/tests/tlsio_mbedtls_ut/tlsio_mbedtls_ut.c @@ -518,11 +518,15 @@ BEGIN_TEST_SUITE(tlsio_mbedtls_ut) } STRICT_EXPECTED_CALL(mallocAndStrcpy_s(IGNORED_ARG, IGNORED_ARG)); STRICT_EXPECTED_CALL(xio_create(IGNORED_ARG, IGNORED_ARG)); +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= TLSIO_MBEDTLS_VERSION_4_0_0 + // PSA must come up before anything else on 4.x, so that nothing has + // been initialized if it fails. + STRICT_EXPECTED_CALL(psa_crypto_init()); +#endif STRICT_EXPECTED_CALL(mbedtls_x509_crt_init(IGNORED_ARG)); STRICT_EXPECTED_CALL(mbedtls_x509_crt_init(IGNORED_ARG)); STRICT_EXPECTED_CALL(mbedtls_pk_init(IGNORED_ARG)); #if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= TLSIO_MBEDTLS_VERSION_4_0_0 - STRICT_EXPECTED_CALL(psa_crypto_init()); STRICT_EXPECTED_CALL(mbedtls_ssl_config_init(IGNORED_ARG)); STRICT_EXPECTED_CALL(mbedtls_ssl_config_defaults(IGNORED_ARG, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)); STRICT_EXPECTED_CALL(mbedtls_ssl_conf_authmode(IGNORED_ARG, MBEDTLS_SSL_VERIFY_REQUIRED)); @@ -619,6 +623,39 @@ BEGIN_TEST_SUITE(tlsio_mbedtls_ut) umock_c_negative_tests_deinit(); } +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= TLSIO_MBEDTLS_VERSION_4_0_0 + // On mbedTLS 4.x every cryptographic operation - including parsing a + // certificate or key and running a handshake - requires a successful + // psa_crypto_init(). If it fails, creation must fail rather than hand back + // a handle that cannot safely be used. + TEST_FUNCTION(tlsio_mbedtls_create_psa_crypto_init_fails) + { + //arrange + TLSIO_CONFIG tls_io_config; + tls_io_config.hostname = TEST_HOSTNAME; + tls_io_config.port = TEST_CONNECTION_PORT; + tls_io_config.underlying_io_interface = TEST_INTERFACE_DESC; + tls_io_config.underlying_io_parameters = NULL; + + STRICT_EXPECTED_CALL(gballoc_calloc(IGNORED_ARG, IGNORED_ARG)); + STRICT_EXPECTED_CALL(mallocAndStrcpy_s(IGNORED_ARG, IGNORED_ARG)); + STRICT_EXPECTED_CALL(xio_create(IGNORED_ARG, IGNORED_ARG)); + STRICT_EXPECTED_CALL(psa_crypto_init()).SetReturn(PSA_ERROR_INSUFFICIENT_MEMORY); + // Nothing else may be initialized, and everything already allocated + // has to be released. + STRICT_EXPECTED_CALL(xio_destroy(IGNORED_ARG)); + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_ARG)); + STRICT_EXPECTED_CALL(gballoc_free(IGNORED_ARG)); + + //act + CONCRETE_IO_HANDLE handle = tlsio_mbedtls_create(&tls_io_config); + + //assert + ASSERT_IS_NULL(handle); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + } +#endif // MBEDTLS_VERSION_NUMBER + TEST_FUNCTION(tlsio_mbedtls_destroy_succeed) { //arrange