Add Mbed TLS 4.x support (PSA Crypto) (#2502)

* 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.

* CI: test Mbed TLS 4.x on macOS, 3.x on Ubuntu 26.04

Homebrew's default mbedtls is now 4.x, so switch the macOS build and CI job
to it (drop the mbedtls@3 pin). That leaves 3.x (Ubuntu 24.04 apt ships 2.28,
macOS now 4.x) uncovered, so add an ubuntu-26.04 job whose apt provides Mbed
TLS 3.6. Net coverage: 2.28 (ubuntu-latest), 3.6 (ubuntu-26.04), 4.2 (macOS).

ubuntu-26.04 is a public-preview runner image; fold it into the main ubuntu
matrix once ubuntu-latest moves to 26.04.

* Document Mbed TLS 4.x support and libtfpsacrypto rename

Update README.md and the tour's TLS setup pages (en/ja) to note that
Mbed TLS 4.x is now auto-detected and that it renames libmbedcrypto
to libtfpsacrypto.
This commit is contained in:
yhirose
2026-07-21 18:45:48 -04:00
committed by GitHub
parent 6c4cbd49a2
commit 54b3c1d072
10 changed files with 241 additions and 40 deletions
+28 -1
View File
@@ -120,6 +120,33 @@ jobs:
- name: build and run ThreadPool test - name: build and run ThreadPool test
run: cd test && make test_thread_pool && ./test_thread_pool run: cd test && make test_thread_pool && ./test_thread_pool
# Ubuntu 26.04's apt ships Mbed TLS 3.6, giving 3.x coverage that
# ubuntu-latest (24.04 = 2.28) and macOS (Homebrew = 4.x) no longer provide.
# Uses the 26.04 public-preview image; fold into the main ubuntu matrix once
# ubuntu-latest moves to 26.04.
ubuntu-2604-mbedtls:
runs-on: ubuntu-26.04
if: >
(github.event_name == 'push') ||
(github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true')
name: ubuntu-26.04 (mbedtls 3.x)
steps:
- name: checkout
uses: actions/checkout@v4
- name: install common libraries
run: |
sudo apt-get update
sudo apt-get install -y libcurl4-openssl-dev zlib1g-dev libbrotli-dev libzstd-dev
- name: install Mbed TLS
run: sudo apt-get install -y libmbedtls-dev
- name: build and run tests (Mbed TLS)
# Run mbedTLS shards with reduced parallelism — under ASAN+mbedTLS the
# default 4 shards overload CI runners enough that timing-sensitive
# ServerTest cases flake on first-request keep-alive reuse.
run: cd test && make test_split_mbedtls && SHARDS=2 make test_mbedtls_parallel
# BoringSSL is Google's fork of OpenSSL. It has no API stability guarantee # BoringSSL is Google's fork of OpenSSL. It has no API stability guarantee
# and is not packaged by distros, so we build it from source. cpp-httplib # and is not packaged by distros, so we build it from source. cpp-httplib
# treats it as an OpenSSL backend variant via the OPENSSL_IS_BORINGSSL # treats it as an OpenSSL backend variant via the OPENSSL_IS_BORINGSSL
@@ -372,7 +399,7 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: install Mbed TLS - name: install Mbed TLS
if: matrix.tls_backend == 'mbedtls' if: matrix.tls_backend == 'mbedtls'
run: brew install mbedtls@3 run: brew install mbedtls
- name: install wolfSSL - name: install wolfSSL
if: matrix.tls_backend == 'wolfssl' if: matrix.tls_backend == 'wolfssl'
run: brew install wolfssl run: brew install wolfssl
+1 -1
View File
@@ -67,7 +67,7 @@ cpp-httplib supports multiple TLS backends through an abstraction layer:
| Backend | Define | Libraries | Notes | | Backend | Define | Libraries | Notes |
| :------ | :----- | :-------- | :---- | | :------ | :----- | :-------- | :---- |
| OpenSSL | `CPPHTTPLIB_OPENSSL_SUPPORT` | `libssl`, `libcrypto` | [3.0 or later](https://www.openssl.org/policies/releasestrat.html) required | | OpenSSL | `CPPHTTPLIB_OPENSSL_SUPPORT` | `libssl`, `libcrypto` | [3.0 or later](https://www.openssl.org/policies/releasestrat.html) required |
| Mbed TLS | `CPPHTTPLIB_MBEDTLS_SUPPORT` | `libmbedtls`, `libmbedx509`, `libmbedcrypto` | 2.x and 3.x supported (auto-detected) | | Mbed TLS | `CPPHTTPLIB_MBEDTLS_SUPPORT` | `libmbedtls`, `libmbedx509`, `libmbedcrypto` | 2.x, 3.x, and 4.x supported (auto-detected); 4.x renames `libmbedcrypto` to `libtfpsacrypto` |
| wolfSSL | `CPPHTTPLIB_WOLFSSL_SUPPORT` | `libwolfssl` | 5.x supported; must build with `--enable-opensslall` | | wolfSSL | `CPPHTTPLIB_WOLFSSL_SUPPORT` | `libwolfssl` | 5.x supported; must build with `--enable-opensslall` |
> [!NOTE] > [!NOTE]
+2
View File
@@ -79,6 +79,8 @@ cpp-httplib also supports Mbed TLS and wolfSSL in addition to OpenSSL. You can s
| Mbed TLS | `CPPHTTPLIB_MBEDTLS_SUPPORT` | `libmbedtls`, `libmbedx509`, `libmbedcrypto` | | Mbed TLS | `CPPHTTPLIB_MBEDTLS_SUPPORT` | `libmbedtls`, `libmbedx509`, `libmbedcrypto` |
| wolfSSL | `CPPHTTPLIB_WOLFSSL_SUPPORT` | `libwolfssl` | | wolfSSL | `CPPHTTPLIB_WOLFSSL_SUPPORT` | `libwolfssl` |
Mbed TLS 2.x, 3.x, and 4.x are all supported and auto-detected. Note that Mbed TLS 4.x renames `libmbedcrypto` to `libtfpsacrypto`, so link against that instead.
This tour assumes OpenSSL, but the API is the same regardless of which backend you choose. This tour assumes OpenSSL, but the API is the same regardless of which backend you choose.
## Next Step ## Next Step
+2
View File
@@ -79,6 +79,8 @@ cpp-httplibはOpenSSL以外にも、Mbed TLSとwolfSSLに対応しています
| Mbed TLS | `CPPHTTPLIB_MBEDTLS_SUPPORT` | `libmbedtls`, `libmbedx509`, `libmbedcrypto` | | Mbed TLS | `CPPHTTPLIB_MBEDTLS_SUPPORT` | `libmbedtls`, `libmbedx509`, `libmbedcrypto` |
| wolfSSL | `CPPHTTPLIB_WOLFSSL_SUPPORT` | `libwolfssl` | | wolfSSL | `CPPHTTPLIB_WOLFSSL_SUPPORT` | `libwolfssl` |
Mbed TLSは2.x、3.x、4.xいずれも対応していて、自動判定されます。4.xでは`libmbedcrypto`が`libtfpsacrypto`という名前に変わっているので、リンクするライブラリはそちらに読み替えてください。
このTourではOpenSSLを前提に進めますが、APIはどのバックエンドでも共通です。 このTourではOpenSSLを前提に進めますが、APIはどのバックエンドでも共通です。
## 次のステップ ## 次のステップ
+124 -22
View File
@@ -420,18 +420,26 @@ using socket_t = int;
#endif // CPPHTTPLIB_OPENSSL_SUPPORT #endif // CPPHTTPLIB_OPENSSL_SUPPORT
#ifdef CPPHTTPLIB_MBEDTLS_SUPPORT #ifdef CPPHTTPLIB_MBEDTLS_SUPPORT
#include <mbedtls/ctr_drbg.h> // version.h defines MBEDTLS_VERSION_MAJOR (on 2.x/3.x/4.x alike); it is pulled
#include <mbedtls/entropy.h> // in with this first include group so the version gating below can use it.
#include <mbedtls/error.h> #include <mbedtls/error.h>
#include <mbedtls/md5.h>
#include <mbedtls/net_sockets.h> #include <mbedtls/net_sockets.h>
#include <mbedtls/oid.h> #include <mbedtls/oid.h>
#include <mbedtls/pk.h> #include <mbedtls/pk.h>
#include <mbedtls/ssl.h>
#include <mbedtls/version.h>
#include <mbedtls/x509_crt.h>
#if MBEDTLS_VERSION_MAJOR >= 4
// Mbed TLS 4.x moved hashing/RNG to PSA Crypto and removed these headers.
#include <psa/crypto.h>
#else
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/entropy.h>
#include <mbedtls/md5.h>
#include <mbedtls/sha1.h> #include <mbedtls/sha1.h>
#include <mbedtls/sha256.h> #include <mbedtls/sha256.h>
#include <mbedtls/sha512.h> #include <mbedtls/sha512.h>
#include <mbedtls/ssl.h> #endif
#include <mbedtls/x509_crt.h>
#ifdef _WIN32 #ifdef _WIN32
#include <wincrypt.h> #include <wincrypt.h>
#ifdef _MSC_VER #ifdef _MSC_VER
@@ -444,7 +452,11 @@ using socket_t = int;
#endif #endif
#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 #if MBEDTLS_VERSION_MAJOR >= 3
#define CPPHTTPLIB_MBEDTLS_V3 #define CPPHTTPLIB_MBEDTLS_V3
#endif #endif
@@ -3433,8 +3445,11 @@ namespace impl {
// setup callbacks (cast ctx_t to tls::impl::MbedTlsContext*). // setup callbacks (cast ctx_t to tls::impl::MbedTlsContext*).
struct MbedTlsContext { struct MbedTlsContext {
mbedtls_ssl_config conf; 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_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg; mbedtls_ctr_drbg_context ctr_drbg;
#endif
mbedtls_x509_crt ca_chain; mbedtls_x509_crt ca_chain;
mbedtls_x509_crt own_cert; mbedtls_x509_crt own_cert;
mbedtls_pk_context own_key; mbedtls_pk_context own_key;
@@ -9114,9 +9129,31 @@ inline std::string hash_to_hex(const unsigned char (&hash)[N]) {
} }
} // namespace } // 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<const uint8_t *>(s.data()),
s.size(), out, out_size, &olen) == PSA_SUCCESS &&
olen == out_size;
}
#endif
inline std::string MD5(const std::string &s) { inline std::string MD5(const std::string &s) {
unsigned char hash[16]; 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<const unsigned char *>(s.c_str()), s.size(), mbedtls_md5(reinterpret_cast<const unsigned char *>(s.c_str()), s.size(),
hash); hash);
#else #else
@@ -9128,7 +9165,9 @@ inline std::string MD5(const std::string &s) {
inline std::string SHA_256(const std::string &s) { inline std::string SHA_256(const std::string &s) {
unsigned char hash[32]; 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<const unsigned char *>(s.c_str()), s.size(), mbedtls_sha256(reinterpret_cast<const unsigned char *>(s.c_str()), s.size(),
hash, 0); hash, 0);
#else #else
@@ -9140,7 +9179,9 @@ inline std::string SHA_256(const std::string &s) {
inline std::string SHA_512(const std::string &s) { inline std::string SHA_512(const std::string &s) {
unsigned char hash[64]; 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<const unsigned char *>(s.c_str()), s.size(), mbedtls_sha512(reinterpret_cast<const unsigned char *>(s.c_str()), s.size(),
hash, 0); hash, 0);
#else #else
@@ -18005,6 +18046,20 @@ inline ErrorCode map_mbedtls_error(int ret, int &out_errno) {
return ErrorCode::Fatal; 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 // BIO-like send callback for Mbed TLS
inline int mbedtls_net_send_cb(void *ctx, const unsigned char *buf, inline int mbedtls_net_send_cb(void *ctx, const unsigned char *buf,
size_t len) { 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 // MbedTlsContext constructor/destructor implementations
inline MbedTlsContext::MbedTlsContext() { inline MbedTlsContext::MbedTlsContext() {
mbedtls_ssl_config_init(&conf); mbedtls_ssl_config_init(&conf);
#ifndef CPPHTTPLIB_MBEDTLS_V4
mbedtls_entropy_init(&entropy); mbedtls_entropy_init(&entropy);
mbedtls_ctr_drbg_init(&ctr_drbg); mbedtls_ctr_drbg_init(&ctr_drbg);
#endif
mbedtls_x509_crt_init(&ca_chain); mbedtls_x509_crt_init(&ca_chain);
mbedtls_x509_crt_init(&own_cert); mbedtls_x509_crt_init(&own_cert);
mbedtls_pk_init(&own_key); mbedtls_pk_init(&own_key);
@@ -18067,8 +18124,10 @@ inline MbedTlsContext::~MbedTlsContext() {
mbedtls_pk_free(&own_key); mbedtls_pk_free(&own_key);
mbedtls_x509_crt_free(&own_cert); mbedtls_x509_crt_free(&own_cert);
mbedtls_x509_crt_free(&ca_chain); mbedtls_x509_crt_free(&ca_chain);
#ifndef CPPHTTPLIB_MBEDTLS_V4
mbedtls_ctr_drbg_free(&ctr_drbg); mbedtls_ctr_drbg_free(&ctr_drbg);
mbedtls_entropy_free(&entropy); mbedtls_entropy_free(&entropy);
#endif
mbedtls_ssl_config_free(&conf); mbedtls_ssl_config_free(&conf);
} }
@@ -18142,6 +18201,14 @@ inline ctx_t create_client_context() {
ctx->is_server = false; 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 // Seed the random number generator
const char *pers = "httplib_client"; const char *pers = "httplib_client";
int ret = mbedtls_ctr_drbg_seed( int ret = mbedtls_ctr_drbg_seed(
@@ -18152,6 +18219,7 @@ inline ctx_t create_client_context() {
delete ctx; delete ctx;
return nullptr; return nullptr;
} }
#endif
// Set up SSL config for client // Set up SSL config for client
ret = mbedtls_ssl_config_defaults(&ctx->conf, MBEDTLS_SSL_IS_CLIENT, ret = mbedtls_ssl_config_defaults(&ctx->conf, MBEDTLS_SSL_IS_CLIENT,
@@ -18163,8 +18231,10 @@ inline ctx_t create_client_context() {
return nullptr; 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); mbedtls_ssl_conf_rng(&ctx->conf, mbedtls_ctr_drbg_random, &ctx->ctr_drbg);
#endif
// Default: verify peer certificate // Default: verify peer certificate
mbedtls_ssl_conf_authmode(&ctx->conf, MBEDTLS_SSL_VERIFY_REQUIRED); mbedtls_ssl_conf_authmode(&ctx->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
@@ -18186,6 +18256,14 @@ inline ctx_t create_server_context() {
ctx->is_server = true; 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 // Seed the random number generator
const char *pers = "httplib_server"; const char *pers = "httplib_server";
int ret = mbedtls_ctr_drbg_seed( int ret = mbedtls_ctr_drbg_seed(
@@ -18196,6 +18274,7 @@ inline ctx_t create_server_context() {
delete ctx; delete ctx;
return nullptr; return nullptr;
} }
#endif
// Set up SSL config for server // Set up SSL config for server
ret = mbedtls_ssl_config_defaults(&ctx->conf, MBEDTLS_SSL_IS_SERVER, ret = mbedtls_ssl_config_defaults(&ctx->conf, MBEDTLS_SSL_IS_SERVER,
@@ -18207,8 +18286,10 @@ inline ctx_t create_server_context() {
return nullptr; 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); mbedtls_ssl_conf_rng(&ctx->conf, mbedtls_ctr_drbg_random, &ctx->ctr_drbg);
#endif
// Default: don't verify client // Default: don't verify client
mbedtls_ssl_conf_authmode(&ctx->conf, MBEDTLS_SSL_VERIFY_NONE); 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<const unsigned char *>(password) : nullptr; password ? reinterpret_cast<const unsigned char *>(password) : nullptr;
size_t pwd_len = password ? strlen(password) : 0; 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( ret = mbedtls_pk_parse_key(
&mctx->own_key, reinterpret_cast<const unsigned char *>(key_str.c_str()), &mctx->own_key, reinterpret_cast<const unsigned char *>(key_str.c_str()),
key_str.size() + 1, pwd, pwd_len, mbedtls_ctr_drbg_random, 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; 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 #ifdef CPPHTTPLIB_MBEDTLS_V3
ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key, ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key,
mbedtls_ctr_drbg_random, &mctx->ctr_drbg); 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; impl::mbedtls_last_error() = ret;
return false; return false;
} }
#endif
ret = mbedtls_ssl_conf_own_cert(&mctx->conf, &mctx->own_cert, &mctx->own_key); ret = mbedtls_ssl_conf_own_cert(&mctx->conf, &mctx->own_cert, &mctx->own_key);
if (ret != 0) { if (ret != 0) {
@@ -18417,7 +18502,7 @@ inline bool set_client_cert_file(ctx_t ctx, const char *cert_path,
} }
// Parse private key file // 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, ret = mbedtls_pk_parse_keyfile(&mctx->own_key, key_path, password,
mbedtls_ctr_drbg_random, &mctx->ctr_drbg); mbedtls_ctr_drbg_random, &mctx->ctr_drbg);
#else #else
@@ -18428,7 +18513,9 @@ inline bool set_client_cert_file(ctx_t ctx, const char *cert_path,
return false; 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 #ifdef CPPHTTPLIB_MBEDTLS_V3
ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key, ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key,
mbedtls_ctr_drbg_random, &mctx->ctr_drbg); 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; impl::mbedtls_last_error() = ret;
return false; return false;
} }
#endif
ret = mbedtls_ssl_conf_own_cert(&mctx->conf, &mctx->own_cert, &mctx->own_key); ret = mbedtls_ssl_conf_own_cert(&mctx->conf, &mctx->own_cert, &mctx->own_key);
if (ret != 0) { if (ret != 0) {
@@ -18533,7 +18621,10 @@ inline TlsError connect(session_t session) {
} }
auto msession = static_cast<impl::MbedTlsSession *>(session); auto msession = static_cast<impl::MbedTlsSession *>(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) { if (ret == 0) {
err.code = ErrorCode::Success; err.code = ErrorCode::Success;
@@ -18577,6 +18668,8 @@ inline bool connect_nonblocking(session_t session, socket_t sock,
int ret; int ret;
while ((ret = mbedtls_ssl_handshake(&msession->ssl)) != 0) { 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 (ret == MBEDTLS_ERR_SSL_WANT_READ) {
if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) { if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) {
continue; continue;
@@ -18624,8 +18717,11 @@ inline ssize_t read(session_t session, void *buf, size_t len, TlsError &err) {
} }
auto msession = static_cast<impl::MbedTlsSession *>(session); auto msession = static_cast<impl::MbedTlsSession *>(session);
int ret = int ret;
mbedtls_ssl_read(&msession->ssl, static_cast<unsigned char *>(buf), len); do {
ret = mbedtls_ssl_read(&msession->ssl, static_cast<unsigned char *>(buf),
len);
} while (impl::mbedtls_is_session_ticket(ret));
if (ret > 0) { if (ret > 0) {
err.code = ErrorCode::Success; 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<impl::MbedTlsSession *>(session); auto msession = static_cast<impl::MbedTlsSession *>(session);
int ret = mbedtls_ssl_write(&msession->ssl, int ret;
static_cast<const unsigned char *>(buf), len); do {
ret = mbedtls_ssl_write(&msession->ssl,
static_cast<const unsigned char *>(buf), len);
} while (impl::mbedtls_is_session_ticket(ret));
if (ret > 0) { if (ret > 0) {
err.code = ErrorCode::Success; 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 // purpose of checking if peer is closed, this should be acceptable
// since we're only called when we expect the connection might be closing // since we're only called when we expect the connection might be closing
unsigned char buf; 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 we got data or WANT_READ (would block), connection is alive
if (ret > 0 || ret == MBEDTLS_ERR_SSL_WANT_READ) { return false; } 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 // Parse private key PEM
#ifdef CPPHTTPLIB_MBEDTLS_V3 #if defined(CPPHTTPLIB_MBEDTLS_V3) && !defined(CPPHTTPLIB_MBEDTLS_V4)
ret = mbedtls_pk_parse_key( ret = mbedtls_pk_parse_key(
&mbed_ctx->own_key, reinterpret_cast<const unsigned char *>(key_pem), &mbed_ctx->own_key, reinterpret_cast<const unsigned char *>(key_pem),
strlen(key_pem) + 1, strlen(key_pem) + 1,
+16 -3
View File
@@ -103,19 +103,32 @@ if(HTTPLIB_IS_USING_OPENSSL)
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY 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( execute_process(
COMMAND ${OPENSSL_COMMAND} genrsa -aes256 -passout pass:test012! 2048 COMMAND ${OPENSSL_COMMAND} genrsa -out client_encrypted.tmp.key.pem 2048
OUTPUT_FILE client_encrypted.key.pem
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY COMMAND_ERROR_IS_FATAL ANY
) )
execute_process( 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 COMMAND ${OPENSSL_COMMAND} x509 -days 370 -req -CA rootCA.cert.pem -CAkey rootCA.key.pem -CAcreateserial
OUTPUT_FILE client_encrypted.cert.pem OUTPUT_FILE client_encrypted.cert.pem
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY 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. # Certificates for IP-host hostname verification regression tests.
# cert_ip_cn.pem: CN is an IPv4 literal with NO subjectAltName. An IP host # 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 # must NOT be authenticated via the CN, so verifying it
+5 -2
View File
@@ -8,8 +8,11 @@ ifneq ($(OS), Windows_NT)
OPENSSL_DIR = $(PREFIX)/opt/openssl@3 OPENSSL_DIR = $(PREFIX)/opt/openssl@3
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
OPENSSL_SUPPORT += -framework CoreFoundation -framework Security OPENSSL_SUPPORT += -framework CoreFoundation -framework Security
MBEDTLS_DIR ?= $(shell brew --prefix mbedtls@3) # Homebrew's default mbedtls is 4.x; override MBEDTLS_DIR for other versions.
MBEDTLS_SUPPORT = -DCPPHTTPLIB_MBEDTLS_SUPPORT -I$(MBEDTLS_DIR)/include -L$(MBEDTLS_DIR)/lib -lmbedtls -lmbedx509 -lmbedcrypto MBEDTLS_DIR ?= $(shell brew --prefix mbedtls)
# 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 MBEDTLS_SUPPORT += -framework CoreFoundation -framework Security
WOLFSSL_DIR ?= $(shell brew --prefix wolfssl) 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 WOLFSSL_SUPPORT = -DCPPHTTPLIB_WOLFSSL_SUPPORT -I$(WOLFSSL_DIR)/include -I$(WOLFSSL_DIR)/include/wolfssl -L$(WOLFSSL_DIR)/lib -lwolfssl
+8 -2
View File
@@ -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 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 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 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 # Encrypted client key: make an unencrypted key + cert first, then wrap the same
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 # 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. # Certificates for IP-host hostname verification regression tests.
# cert_ip_cn.pem: CN is an IPv4 literal with NO subjectAltName. An IP host must # cert_ip_cn.pem: CN is an IPv4 literal with NO subjectAltName. An IP host must
+46 -7
View File
@@ -81,17 +81,20 @@ client_cert_pem = custom_target(
command: [openssl, 'x509', '-in', '@INPUT0@', '-days', '370', '-req', '-CA', '@INPUT1@', '-CAkey', '@INPUT2@', '-CAcreateserial', '-out', '@OUTPUT@'] command: [openssl, 'x509', '-in', '@INPUT0@', '-days', '370', '-req', '-CA', '@INPUT1@', '-CAkey', '@INPUT2@', '-CAcreateserial', '-out', '@OUTPUT@']
) )
client_encrypted_key_pem = custom_target( # Encrypted client key: make an unencrypted key + cert first, then wrap the same
'client_encrypted_key_pem', # key two ways. Mbed TLS 4.x dropped DES/PBES1, while Ubuntu's Mbed TLS 2.28 has
output: 'client_encrypted.key.pem', # no PBES2-AES, so ship both and let test.cc pick by version.
command: [openssl, 'genrsa', '-aes256', '-passout', 'pass:test012!', '-out', '@OUTPUT@', '2048'] 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 = custom_target(
'client_encrypted_temp_req', 'client_encrypted_temp_req',
input: client_encrypted_key_pem, input: client_encrypted_tmp_key_pem,
output: 'client_encrypted_temp_req', 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( 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@'] 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 # Copy test files to the build directory
configure_file(input: 'ca-bundle.crt', output: 'ca-bundle.crt', copy: true) configure_file(input: 'ca-bundle.crt', output: 'ca-bundle.crt', copy: true)
configure_file(input: 'image.jpg', output: 'image.jpg', copy: true) configure_file(input: 'image.jpg', output: 'image.jpg', copy: true)
@@ -139,7 +175,10 @@ test(
client_key_pem, client_key_pem,
client_cert_pem, client_cert_pem,
client_encrypted_key_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(), workdir: meson.current_build_dir(),
timeout: 300 timeout: 300
+9 -2
View File
@@ -47,7 +47,13 @@ inline std::string u8_to_string(const char8_t *s) {
#define CLIENT_CERT_FILE "./client.cert.pem" #define CLIENT_CERT_FILE "./client.cert.pem"
#define CLIENT_PRIVATE_KEY_FILE "./client.key.pem" #define CLIENT_PRIVATE_KEY_FILE "./client.key.pem"
#define CLIENT_ENCRYPTED_CERT_FILE "./client_encrypted.cert.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" #define CLIENT_ENCRYPTED_PRIVATE_KEY_FILE "./client_encrypted.key.pem"
#endif
#define CLIENT_ENCRYPTED_PRIVATE_KEY_PASS "test012!" #define CLIENT_ENCRYPTED_PRIVATE_KEY_PASS "test012!"
#define SERVER_ENCRYPTED_CERT_FILE "./cert_encrypted.pem" #define SERVER_ENCRYPTED_CERT_FILE "./cert_encrypted.pem"
#define SERVER_ENCRYPTED_PRIVATE_KEY_FILE "./key_encrypted.pem" #define SERVER_ENCRYPTED_PRIVATE_KEY_FILE "./key_encrypted.pem"
@@ -18505,9 +18511,10 @@ TEST(SSLClientServerTest, CustomizeServerSSLCtxMbedTLS) {
if (mbedtls_x509_crt_parse_file(&own_cert, SERVER_CERT_FILE) != 0) { if (mbedtls_x509_crt_parse_file(&own_cert, SERVER_CERT_FILE) != 0) {
return false; 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_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 mbedtls_ctr_drbg_random, nullptr
#endif #endif