From 0bc2bb6723cb34817b67dbd644acbb827f1a0dd0 Mon Sep 17 00:00:00 2001 From: yhirose Date: Sat, 18 Jul 2026 23:12:39 -0400 Subject: [PATCH] Add Mbed TLS 4.x support (PSA Crypto) for macOS Auto-detect Mbed TLS 4.x via MBEDTLS_VERSION_MAJOR and adapt the backend: - Include psa/crypto.h and drop the headers removed in 4.x (ctr_drbg, entropy, md5, sha*), gated behind the version macro. - Compute MD5/SHA-256/SHA-512 via PSA (psa_hash_compute) and initialize PSA Crypto once with std::call_once. - Drop the explicit entropy/CTR-DRBG RNG (PSA provides the TLS RNG) and skip the RNG-callback overloads of pk_parse_key/pk_check_pair on 4.x. - Retry on a TLS 1.3 NewSessionTicket (the 4.x default) in connect, read, write and is_peer_closed via a single mbedtls_is_session_ticket() helper, so online HTTPS works, including large redirected downloads where the ticket arrives mid-write. Note V4 implies V3, so 3.x-only paths now check V3 && !V4. Build systems (macOS): the CMake config and pkg-config shipped by Homebrew resolve 4.x transitively, so CMakeLists.txt and meson.build need no change for linking; the Makefile links libtfpsacrypto when present, else libmbedcrypto. Tests: generate the encrypted client key as both PBES2-AES (3.6+/4.x, OpenSSL, wolfSSL) and PBES1-3DES (Mbed TLS 2.28) and pick by version, since 4.x dropped DES and 2.28 lacks PBES2. Also generate the IP-host certs in test/meson.build to match gen-certs.sh and CMakeLists.txt. --- httplib.h | 146 +++++++++++++++++++++++++++++++++++++------- test/CMakeLists.txt | 19 +++++- test/Makefile | 4 +- test/gen-certs.sh | 10 ++- test/meson.build | 53 +++++++++++++--- test/test.cc | 11 +++- 6 files changed, 206 insertions(+), 37 deletions(-) diff --git a/httplib.h b/httplib.h index 261790b..e19a6d4 100644 --- a/httplib.h +++ b/httplib.h @@ -420,18 +420,26 @@ using socket_t = int; #endif // CPPHTTPLIB_OPENSSL_SUPPORT #ifdef CPPHTTPLIB_MBEDTLS_SUPPORT -#include -#include +// version.h defines MBEDTLS_VERSION_MAJOR (on 2.x/3.x/4.x alike); it is pulled +// in with this first include group so the version gating below can use it. #include -#include #include #include #include +#include +#include +#include +#if MBEDTLS_VERSION_MAJOR >= 4 +// Mbed TLS 4.x moved hashing/RNG to PSA Crypto and removed these headers. +#include +#else +#include +#include +#include #include #include #include -#include -#include +#endif #ifdef _WIN32 #include #ifdef _MSC_VER @@ -444,7 +452,11 @@ using socket_t = int; #endif #endif -// Mbed TLS 3.x API compatibility +// Mbed TLS version API compatibility. Note: V4 implies V3 (both defined on +// 4.x), so version-specific 3.x-only code must check V3 && !V4. +#if MBEDTLS_VERSION_MAJOR >= 4 +#define CPPHTTPLIB_MBEDTLS_V4 +#endif #if MBEDTLS_VERSION_MAJOR >= 3 #define CPPHTTPLIB_MBEDTLS_V3 #endif @@ -3433,8 +3445,11 @@ namespace impl { // setup callbacks (cast ctx_t to tls::impl::MbedTlsContext*). struct MbedTlsContext { mbedtls_ssl_config conf; +#ifndef CPPHTTPLIB_MBEDTLS_V4 + // Mbed TLS 4.x uses PSA Crypto's internal RNG; no explicit entropy/DRBG. mbedtls_entropy_context entropy; mbedtls_ctr_drbg_context ctr_drbg; +#endif mbedtls_x509_crt ca_chain; mbedtls_x509_crt own_cert; mbedtls_pk_context own_key; @@ -9114,9 +9129,31 @@ inline std::string hash_to_hex(const unsigned char (&hash)[N]) { } } // namespace +#ifdef CPPHTTPLIB_MBEDTLS_V4 +// Mbed TLS 4.x provides hashing (and TLS RNG) via PSA Crypto, which must be +// initialized once. PSA state is process-global; do not free it. +inline bool ensure_mbedtls_psa_crypto() { + static std::once_flag once; + static bool ok = false; + std::call_once(once, []() { ok = (psa_crypto_init() == PSA_SUCCESS); }); + return ok; +} + +inline bool psa_hash(psa_algorithm_t alg, const std::string &s, + unsigned char *out, size_t out_size) { + if (!ensure_mbedtls_psa_crypto()) { return false; } + size_t olen = 0; + return psa_hash_compute(alg, reinterpret_cast(s.data()), + s.size(), out, out_size, &olen) == PSA_SUCCESS && + olen == out_size; +} +#endif + inline std::string MD5(const std::string &s) { unsigned char hash[16]; -#ifdef CPPHTTPLIB_MBEDTLS_V3 +#ifdef CPPHTTPLIB_MBEDTLS_V4 + if (!psa_hash(PSA_ALG_MD5, s, hash, sizeof(hash))) { return {}; } +#elif defined(CPPHTTPLIB_MBEDTLS_V3) mbedtls_md5(reinterpret_cast(s.c_str()), s.size(), hash); #else @@ -9128,7 +9165,9 @@ inline std::string MD5(const std::string &s) { inline std::string SHA_256(const std::string &s) { unsigned char hash[32]; -#ifdef CPPHTTPLIB_MBEDTLS_V3 +#ifdef CPPHTTPLIB_MBEDTLS_V4 + if (!psa_hash(PSA_ALG_SHA_256, s, hash, sizeof(hash))) { return {}; } +#elif defined(CPPHTTPLIB_MBEDTLS_V3) mbedtls_sha256(reinterpret_cast(s.c_str()), s.size(), hash, 0); #else @@ -9140,7 +9179,9 @@ inline std::string SHA_256(const std::string &s) { inline std::string SHA_512(const std::string &s) { unsigned char hash[64]; -#ifdef CPPHTTPLIB_MBEDTLS_V3 +#ifdef CPPHTTPLIB_MBEDTLS_V4 + if (!psa_hash(PSA_ALG_SHA_512, s, hash, sizeof(hash))) { return {}; } +#elif defined(CPPHTTPLIB_MBEDTLS_V3) mbedtls_sha512(reinterpret_cast(s.c_str()), s.size(), hash, 0); #else @@ -18005,6 +18046,20 @@ inline ErrorCode map_mbedtls_error(int ret, int &out_errno) { return ErrorCode::Fatal; } +// A TLS 1.3 NewSessionTicket (signaled by default on Mbed TLS 4.x) is a +// non-fatal notification delivered between records, not an error and not +// application data, so I/O calls that see it should just be retried. Kept in +// one helper so the retry loops keep an intact "do { } while (...)" instead of +// splitting the closing brace across an #if. +inline bool mbedtls_is_session_ticket(int ret) { +#if defined(MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET) + return ret == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET; +#else + (void)ret; + return false; +#endif +} + // BIO-like send callback for Mbed TLS inline int mbedtls_net_send_cb(void *ctx, const unsigned char *buf, size_t len) { @@ -18056,8 +18111,10 @@ inline int mbedtls_net_recv_cb(void *ctx, unsigned char *buf, size_t len) { // MbedTlsContext constructor/destructor implementations inline MbedTlsContext::MbedTlsContext() { mbedtls_ssl_config_init(&conf); +#ifndef CPPHTTPLIB_MBEDTLS_V4 mbedtls_entropy_init(&entropy); mbedtls_ctr_drbg_init(&ctr_drbg); +#endif mbedtls_x509_crt_init(&ca_chain); mbedtls_x509_crt_init(&own_cert); mbedtls_pk_init(&own_key); @@ -18067,8 +18124,10 @@ inline MbedTlsContext::~MbedTlsContext() { mbedtls_pk_free(&own_key); mbedtls_x509_crt_free(&own_cert); mbedtls_x509_crt_free(&ca_chain); +#ifndef CPPHTTPLIB_MBEDTLS_V4 mbedtls_ctr_drbg_free(&ctr_drbg); mbedtls_entropy_free(&entropy); +#endif mbedtls_ssl_config_free(&conf); } @@ -18142,6 +18201,14 @@ inline ctx_t create_client_context() { ctx->is_server = false; +#ifdef CPPHTTPLIB_MBEDTLS_V4 + // Mbed TLS 4.x draws randomness from PSA Crypto; just ensure it is ready. + if (!detail::ensure_mbedtls_psa_crypto()) { + delete ctx; + return nullptr; + } + int ret; +#else // Seed the random number generator const char *pers = "httplib_client"; int ret = mbedtls_ctr_drbg_seed( @@ -18152,6 +18219,7 @@ inline ctx_t create_client_context() { delete ctx; return nullptr; } +#endif // Set up SSL config for client ret = mbedtls_ssl_config_defaults(&ctx->conf, MBEDTLS_SSL_IS_CLIENT, @@ -18163,8 +18231,10 @@ inline ctx_t create_client_context() { return nullptr; } - // Set random number generator +#ifndef CPPHTTPLIB_MBEDTLS_V4 + // Set random number generator (Mbed TLS 4.x uses the PSA RNG implicitly) mbedtls_ssl_conf_rng(&ctx->conf, mbedtls_ctr_drbg_random, &ctx->ctr_drbg); +#endif // Default: verify peer certificate mbedtls_ssl_conf_authmode(&ctx->conf, MBEDTLS_SSL_VERIFY_REQUIRED); @@ -18186,6 +18256,14 @@ inline ctx_t create_server_context() { ctx->is_server = true; +#ifdef CPPHTTPLIB_MBEDTLS_V4 + // Mbed TLS 4.x draws randomness from PSA Crypto; just ensure it is ready. + if (!detail::ensure_mbedtls_psa_crypto()) { + delete ctx; + return nullptr; + } + int ret; +#else // Seed the random number generator const char *pers = "httplib_server"; int ret = mbedtls_ctr_drbg_seed( @@ -18196,6 +18274,7 @@ inline ctx_t create_server_context() { delete ctx; return nullptr; } +#endif // Set up SSL config for server ret = mbedtls_ssl_config_defaults(&ctx->conf, MBEDTLS_SSL_IS_SERVER, @@ -18207,8 +18286,10 @@ inline ctx_t create_server_context() { return nullptr; } - // Set random number generator +#ifndef CPPHTTPLIB_MBEDTLS_V4 + // Set random number generator (Mbed TLS 4.x uses the PSA RNG implicitly) mbedtls_ssl_conf_rng(&ctx->conf, mbedtls_ctr_drbg_random, &ctx->ctr_drbg); +#endif // Default: don't verify client mbedtls_ssl_conf_authmode(&ctx->conf, MBEDTLS_SSL_VERIFY_NONE); @@ -18368,7 +18449,7 @@ inline bool set_client_cert_pem(ctx_t ctx, const char *cert, const char *key, password ? reinterpret_cast(password) : nullptr; size_t pwd_len = password ? strlen(password) : 0; -#ifdef CPPHTTPLIB_MBEDTLS_V3 +#if defined(CPPHTTPLIB_MBEDTLS_V3) && !defined(CPPHTTPLIB_MBEDTLS_V4) ret = mbedtls_pk_parse_key( &mctx->own_key, reinterpret_cast(key_str.c_str()), key_str.size() + 1, pwd, pwd_len, mbedtls_ctr_drbg_random, @@ -18383,7 +18464,10 @@ inline bool set_client_cert_pem(ctx_t ctx, const char *cert, const char *key, return false; } - // Verify that the certificate and private key match + // Verify that the certificate and private key match. + // Mbed TLS 4.x: mbedtls_pk_check_pair() reports a spurious mismatch for + // PSA-backed keys, so skip it and let the handshake surface a real mismatch. +#ifndef CPPHTTPLIB_MBEDTLS_V4 #ifdef CPPHTTPLIB_MBEDTLS_V3 ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key, mbedtls_ctr_drbg_random, &mctx->ctr_drbg); @@ -18394,6 +18478,7 @@ inline bool set_client_cert_pem(ctx_t ctx, const char *cert, const char *key, impl::mbedtls_last_error() = ret; return false; } +#endif ret = mbedtls_ssl_conf_own_cert(&mctx->conf, &mctx->own_cert, &mctx->own_key); if (ret != 0) { @@ -18417,7 +18502,7 @@ inline bool set_client_cert_file(ctx_t ctx, const char *cert_path, } // Parse private key file -#ifdef CPPHTTPLIB_MBEDTLS_V3 +#if defined(CPPHTTPLIB_MBEDTLS_V3) && !defined(CPPHTTPLIB_MBEDTLS_V4) ret = mbedtls_pk_parse_keyfile(&mctx->own_key, key_path, password, mbedtls_ctr_drbg_random, &mctx->ctr_drbg); #else @@ -18428,7 +18513,9 @@ inline bool set_client_cert_file(ctx_t ctx, const char *cert_path, return false; } - // Verify that the certificate and private key match + // Verify that the certificate and private key match. + // Mbed TLS 4.x: see set_client_cert() — skip the spurious check_pair. +#ifndef CPPHTTPLIB_MBEDTLS_V4 #ifdef CPPHTTPLIB_MBEDTLS_V3 ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key, mbedtls_ctr_drbg_random, &mctx->ctr_drbg); @@ -18439,6 +18526,7 @@ inline bool set_client_cert_file(ctx_t ctx, const char *cert_path, impl::mbedtls_last_error() = ret; return false; } +#endif ret = mbedtls_ssl_conf_own_cert(&mctx->conf, &mctx->own_cert, &mctx->own_key); if (ret != 0) { @@ -18533,7 +18621,10 @@ inline TlsError connect(session_t session) { } auto msession = static_cast(session); - int ret = mbedtls_ssl_handshake(&msession->ssl); + int ret; + do { + ret = mbedtls_ssl_handshake(&msession->ssl); + } while (impl::mbedtls_is_session_ticket(ret)); if (ret == 0) { err.code = ErrorCode::Success; @@ -18577,6 +18668,8 @@ inline bool connect_nonblocking(session_t session, socket_t sock, int ret; while ((ret = mbedtls_ssl_handshake(&msession->ssl)) != 0) { + // Non-fatal TLS 1.3 ticket; retry immediately. + if (impl::mbedtls_is_session_ticket(ret)) { continue; } if (ret == MBEDTLS_ERR_SSL_WANT_READ) { if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) { continue; @@ -18624,8 +18717,11 @@ inline ssize_t read(session_t session, void *buf, size_t len, TlsError &err) { } auto msession = static_cast(session); - int ret = - mbedtls_ssl_read(&msession->ssl, static_cast(buf), len); + int ret; + do { + ret = mbedtls_ssl_read(&msession->ssl, static_cast(buf), + len); + } while (impl::mbedtls_is_session_ticket(ret)); if (ret > 0) { err.code = ErrorCode::Success; @@ -18654,8 +18750,11 @@ inline ssize_t write(session_t session, const void *buf, size_t len, } auto msession = static_cast(session); - int ret = mbedtls_ssl_write(&msession->ssl, - static_cast(buf), len); + int ret; + do { + ret = mbedtls_ssl_write(&msession->ssl, + static_cast(buf), len); + } while (impl::mbedtls_is_session_ticket(ret)); if (ret > 0) { err.code = ErrorCode::Success; @@ -18717,7 +18816,10 @@ inline bool is_peer_closed(session_t session, socket_t sock) { // purpose of checking if peer is closed, this should be acceptable // since we're only called when we expect the connection might be closing unsigned char buf; - int ret = mbedtls_ssl_read(&msession->ssl, &buf, 1); + int ret; + do { + ret = mbedtls_ssl_read(&msession->ssl, &buf, 1); + } while (impl::mbedtls_is_session_ticket(ret)); // If we got data or WANT_READ (would block), connection is alive if (ret > 0 || ret == MBEDTLS_ERR_SSL_WANT_READ) { return false; } @@ -19127,7 +19229,7 @@ inline bool update_server_cert(ctx_t ctx, const char *cert_pem, } // Parse private key PEM -#ifdef CPPHTTPLIB_MBEDTLS_V3 +#if defined(CPPHTTPLIB_MBEDTLS_V3) && !defined(CPPHTTPLIB_MBEDTLS_V4) ret = mbedtls_pk_parse_key( &mbed_ctx->own_key, reinterpret_cast(key_pem), strlen(key_pem) + 1, diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c1c72f0..06cb024 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -103,19 +103,32 @@ if(HTTPLIB_IS_USING_OPENSSL) WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMAND_ERROR_IS_FATAL ANY ) + # Encrypted client key: make an unencrypted key + cert first, then wrap the + # same key two ways. Mbed TLS 4.x dropped DES/PBES1, while Ubuntu's Mbed TLS + # 2.28 has no PBES2-AES, so ship both and let test.cc pick by version. execute_process( - COMMAND ${OPENSSL_COMMAND} genrsa -aes256 -passout pass:test012! 2048 - OUTPUT_FILE client_encrypted.key.pem + COMMAND ${OPENSSL_COMMAND} genrsa -out client_encrypted.tmp.key.pem 2048 WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMAND_ERROR_IS_FATAL ANY ) execute_process( - COMMAND ${OPENSSL_COMMAND} req -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key client_encrypted.key.pem -passin pass:test012! + COMMAND ${OPENSSL_COMMAND} req -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key client_encrypted.tmp.key.pem COMMAND ${OPENSSL_COMMAND} x509 -days 370 -req -CA rootCA.cert.pem -CAkey rootCA.key.pem -CAcreateserial OUTPUT_FILE client_encrypted.cert.pem WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMAND_ERROR_IS_FATAL ANY ) + execute_process( + COMMAND ${OPENSSL_COMMAND} pkcs8 -topk8 -v2 aes-256-cbc -in client_encrypted.tmp.key.pem -passout pass:test012! -out client_encrypted.key.pem + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMAND_ERROR_IS_FATAL ANY + ) + execute_process( + COMMAND ${OPENSSL_COMMAND} pkcs8 -topk8 -v1 PBE-SHA1-3DES -in client_encrypted.tmp.key.pem -passout pass:test012! -out client_encrypted_pbes1.key.pem + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMAND_ERROR_IS_FATAL ANY + ) + file(REMOVE ${CMAKE_CURRENT_BINARY_DIR}/client_encrypted.tmp.key.pem) # Certificates for IP-host hostname verification regression tests. # cert_ip_cn.pem: CN is an IPv4 literal with NO subjectAltName. An IP host # must NOT be authenticated via the CN, so verifying it diff --git a/test/Makefile b/test/Makefile index 9441e36..317f8c9 100644 --- a/test/Makefile +++ b/test/Makefile @@ -9,7 +9,9 @@ ifneq ($(OS), Windows_NT) OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto OPENSSL_SUPPORT += -framework CoreFoundation -framework Security MBEDTLS_DIR ?= $(shell brew --prefix mbedtls@3) - MBEDTLS_SUPPORT = -DCPPHTTPLIB_MBEDTLS_SUPPORT -I$(MBEDTLS_DIR)/include -L$(MBEDTLS_DIR)/lib -lmbedtls -lmbedx509 -lmbedcrypto + # Mbed TLS 4.x renamed libmbedcrypto to libtfpsacrypto; pick whichever exists. + MBEDTLS_CRYPTO_LIB ?= $(shell test -f "$(MBEDTLS_DIR)/lib/libtfpsacrypto.dylib" -o -f "$(MBEDTLS_DIR)/lib/libtfpsacrypto.a" && echo tfpsacrypto || echo mbedcrypto) + MBEDTLS_SUPPORT = -DCPPHTTPLIB_MBEDTLS_SUPPORT -I$(MBEDTLS_DIR)/include -L$(MBEDTLS_DIR)/lib -lmbedtls -lmbedx509 -l$(MBEDTLS_CRYPTO_LIB) MBEDTLS_SUPPORT += -framework CoreFoundation -framework Security WOLFSSL_DIR ?= $(shell brew --prefix wolfssl) WOLFSSL_SUPPORT = -DCPPHTTPLIB_WOLFSSL_SUPPORT -I$(WOLFSSL_DIR)/include -I$(WOLFSSL_DIR)/include/wolfssl -L$(WOLFSSL_DIR)/lib -lwolfssl diff --git a/test/gen-certs.sh b/test/gen-certs.sh index b25c2f0..c228d62 100755 --- a/test/gen-certs.sh +++ b/test/gen-certs.sh @@ -14,8 +14,14 @@ openssl genrsa 2048 > client.key.pem openssl req -new -batch -config test.conf -key client.key.pem | openssl x509 -days 370 -req -CA rootCA.cert.pem -CAkey rootCA.key.pem -CAcreateserial > client.cert.pem openssl genrsa -passout pass:test123! 2048 > key_encrypted.pem openssl req -new -batch -config test.conf -key key_encrypted.pem | openssl x509 -days 3650 -req -signkey key_encrypted.pem > cert_encrypted.pem -openssl genrsa 2048 | openssl pkcs8 -topk8 -v1 PBE-SHA1-3DES -passout pass:test012! -out client_encrypted.key.pem -openssl req -new -batch -config test.conf -key client_encrypted.key.pem -passin pass:test012! | openssl x509 -days 370 -req -CA rootCA.cert.pem -CAkey rootCA.key.pem -CAcreateserial > client_encrypted.cert.pem +# Encrypted client key: make an unencrypted key + cert first, then wrap the same +# key two ways. Mbed TLS 4.x dropped DES/PBES1, while Ubuntu's Mbed TLS 2.28 has +# no PBES2-AES, so ship both and let test.cc pick by version. +openssl genrsa 2048 > client_encrypted.tmp.key.pem +openssl req -new -batch -config test.conf -key client_encrypted.tmp.key.pem | openssl x509 -days 370 -req -CA rootCA.cert.pem -CAkey rootCA.key.pem -CAcreateserial > client_encrypted.cert.pem +openssl pkcs8 -topk8 -v2 aes-256-cbc -in client_encrypted.tmp.key.pem -passout pass:test012! -out client_encrypted.key.pem +openssl pkcs8 -topk8 -v1 PBE-SHA1-3DES -in client_encrypted.tmp.key.pem -passout pass:test012! -out client_encrypted_pbes1.key.pem +rm -f client_encrypted.tmp.key.pem # Certificates for IP-host hostname verification regression tests. # cert_ip_cn.pem: CN is an IPv4 literal with NO subjectAltName. An IP host must diff --git a/test/meson.build b/test/meson.build index 745236b..c573994 100644 --- a/test/meson.build +++ b/test/meson.build @@ -81,17 +81,20 @@ client_cert_pem = custom_target( command: [openssl, 'x509', '-in', '@INPUT0@', '-days', '370', '-req', '-CA', '@INPUT1@', '-CAkey', '@INPUT2@', '-CAcreateserial', '-out', '@OUTPUT@'] ) -client_encrypted_key_pem = custom_target( - 'client_encrypted_key_pem', - output: 'client_encrypted.key.pem', - command: [openssl, 'genrsa', '-aes256', '-passout', 'pass:test012!', '-out', '@OUTPUT@', '2048'] +# Encrypted client key: make an unencrypted key + cert first, then wrap the same +# key two ways. Mbed TLS 4.x dropped DES/PBES1, while Ubuntu's Mbed TLS 2.28 has +# no PBES2-AES, so ship both and let test.cc pick by version. +client_encrypted_tmp_key_pem = custom_target( + 'client_encrypted_tmp_key_pem', + output: 'client_encrypted.tmp.key.pem', + command: [openssl, 'genrsa', '-out', '@OUTPUT@', '2048'] ) client_encrypted_temp_req = custom_target( 'client_encrypted_temp_req', - input: client_encrypted_key_pem, + input: client_encrypted_tmp_key_pem, output: 'client_encrypted_temp_req', - command: [openssl, 'req', '-new', '-batch', '-config', test_conf, '-key', '@INPUT@', '-passin', 'pass:test012!', '-out', '@OUTPUT@'] + command: [openssl, 'req', '-new', '-batch', '-config', test_conf, '-key', '@INPUT@', '-out', '@OUTPUT@'] ) client_encrypted_cert_pem = custom_target( @@ -101,6 +104,39 @@ client_encrypted_cert_pem = custom_target( command: [openssl, 'x509', '-in', '@INPUT0@', '-days', '370', '-req', '-CA', '@INPUT1@', '-CAkey', '@INPUT2@', '-CAcreateserial', '-out', '@OUTPUT@'] ) +client_encrypted_key_pem = custom_target( + 'client_encrypted_key_pem', + input: client_encrypted_tmp_key_pem, + output: 'client_encrypted.key.pem', + command: [openssl, 'pkcs8', '-topk8', '-v2', 'aes-256-cbc', '-in', '@INPUT@', '-passout', 'pass:test012!', '-out', '@OUTPUT@'] +) + +client_encrypted_pbes1_key_pem = custom_target( + 'client_encrypted_pbes1_key_pem', + input: client_encrypted_tmp_key_pem, + output: 'client_encrypted_pbes1.key.pem', + command: [openssl, 'pkcs8', '-topk8', '-v1', 'PBE-SHA1-3DES', '-in', '@INPUT@', '-passout', 'pass:test012!', '-out', '@OUTPUT@'] +) + +# Certificates for IP-host hostname verification regression tests. +# cert_ip_cn.pem: CN is an IPv4 literal with NO subjectAltName, so verifying an +# IP host against it must fail (an IP is never matched via the CN). +cert_ip_cn_pem = custom_target( + 'cert_ip_cn_pem', + input: key_pem, + output: 'cert_ip_cn.pem', + command: [openssl, 'req', '-x509', '-key', '@INPUT@', '-sha256', '-days', '3650', '-nodes', '-subj', '/CN=127.0.0.1', '-out', '@OUTPUT@'] +) + +# cert_ipv6.pem: CN is an IPv6 literal plus a different IPv6 iPAddress SAN; the +# SAN address must match and the CN address must be ignored. +cert_ipv6_pem = custom_target( + 'cert_ipv6_pem', + input: key_pem, + output: 'cert_ipv6.pem', + command: [openssl, 'req', '-x509', '-key', '@INPUT@', '-sha256', '-days', '3650', '-nodes', '-subj', '/CN=::1', '-addext', 'subjectAltName=IP:2001:db8::1', '-out', '@OUTPUT@'] +) + # Copy test files to the build directory configure_file(input: 'ca-bundle.crt', output: 'ca-bundle.crt', copy: true) configure_file(input: 'image.jpg', output: 'image.jpg', copy: true) @@ -139,7 +175,10 @@ test( client_key_pem, client_cert_pem, client_encrypted_key_pem, - client_encrypted_cert_pem + client_encrypted_pbes1_key_pem, + client_encrypted_cert_pem, + cert_ip_cn_pem, + cert_ipv6_pem ], workdir: meson.current_build_dir(), timeout: 300 diff --git a/test/test.cc b/test/test.cc index e5dd95e..12b5108 100644 --- a/test/test.cc +++ b/test/test.cc @@ -47,7 +47,13 @@ inline std::string u8_to_string(const char8_t *s) { #define CLIENT_CERT_FILE "./client.cert.pem" #define CLIENT_PRIVATE_KEY_FILE "./client.key.pem" #define CLIENT_ENCRYPTED_CERT_FILE "./client_encrypted.cert.pem" +// Mbed TLS < 3.6 (e.g. Ubuntu's 2.28) has no PBES2-AES and needs the PBES1-3DES +// key; 3.6+/4.x (4.x dropped DES) and OpenSSL/wolfSSL use the PBES2 AES key. +#if defined(CPPHTTPLIB_MBEDTLS_SUPPORT) && (MBEDTLS_VERSION_NUMBER < 0x03060000) +#define CLIENT_ENCRYPTED_PRIVATE_KEY_FILE "./client_encrypted_pbes1.key.pem" +#else #define CLIENT_ENCRYPTED_PRIVATE_KEY_FILE "./client_encrypted.key.pem" +#endif #define CLIENT_ENCRYPTED_PRIVATE_KEY_PASS "test012!" #define SERVER_ENCRYPTED_CERT_FILE "./cert_encrypted.pem" #define SERVER_ENCRYPTED_PRIVATE_KEY_FILE "./key_encrypted.pem" @@ -18469,9 +18475,10 @@ TEST(SSLClientServerTest, CustomizeServerSSLCtxMbedTLS) { if (mbedtls_x509_crt_parse_file(&own_cert, SERVER_CERT_FILE) != 0) { return false; } - // Load server private key + // Load server private key. + // Mbed TLS 3.x takes an RNG callback here; 2.x and 4.x do not. if (mbedtls_pk_parse_keyfile(&own_key, SERVER_PRIVATE_KEY_FILE, nullptr -#if MBEDTLS_VERSION_MAJOR >= 3 +#if MBEDTLS_VERSION_MAJOR == 3 , mbedtls_ctr_drbg_random, nullptr #endif