From 3b67cb19eb3167a1afd6954cf674266fcd14b28d Mon Sep 17 00:00:00 2001 From: yhirose Date: Mon, 12 Jan 2026 21:13:52 -0500 Subject: [PATCH] Phase 6 and 7 --- .github/workflows/test.yaml | 43 +++-- README.md | 18 +- docs/httplib_tls_migration.ja.md | 272 ------------------------------- docs/tls_migration_guide.md | 234 ++++++++++++++++++++++++++ httplib.h | 9 + test/Makefile | 40 ++--- 6 files changed, 309 insertions(+), 307 deletions(-) delete mode 100644 docs/httplib_tls_migration.ja.md create mode 100644 docs/tls_migration_guide.md diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 59c3a29..dc8463c 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -75,21 +75,32 @@ jobs: (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') + strategy: + matrix: + tls_backend: [openssl, mbedtls] + name: ubuntu (${{ matrix.tls_backend }}) steps: - name: checkout uses: actions/checkout@v4 - - name: install libraries + - name: install common libraries run: | - sudo dpkg --add-architecture i386 sudo apt-get update - sudo apt-get install -y libc6-dev${{ matrix.config.arch_suffix }} libstdc++-13-dev${{ matrix.config.arch_suffix }} \ - libssl-dev${{ matrix.config.arch_suffix }} libcurl4-openssl-dev${{ matrix.config.arch_suffix }} \ - zlib1g-dev${{ matrix.config.arch_suffix }} libbrotli-dev${{ matrix.config.arch_suffix }} \ - libzstd-dev${{ matrix.config.arch_suffix }} - - name: build and run tests - run: cd test && make EXTRA_CXXFLAGS="${{ matrix.config.arch_flags }}" + sudo apt-get install -y libcurl4-openssl-dev zlib1g-dev libbrotli-dev libzstd-dev + - name: install OpenSSL + if: matrix.tls_backend == 'openssl' + run: sudo apt-get install -y libssl-dev + - name: install Mbed TLS + if: matrix.tls_backend == 'mbedtls' + run: sudo apt-get install -y libmbedtls-dev + - name: build and run tests (OpenSSL) + if: matrix.tls_backend == 'openssl' + run: cd test && make + - name: build and run tests (Mbed TLS) + if: matrix.tls_backend == 'mbedtls' + run: cd test && make test_split_mbedtls && make test_mbedtls && ./test_mbedtls - name: run fuzz test target - run: cd test && make EXTRA_CXXFLAGS="${{ matrix.config.arch_flags }}" fuzz_test + if: matrix.tls_backend == 'openssl' + run: cd test && make fuzz_test macos: runs-on: macos-latest @@ -98,12 +109,24 @@ jobs: (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_macos == 'true') + strategy: + matrix: + tls_backend: [openssl, mbedtls] + name: macos (${{ matrix.tls_backend }}) steps: - name: checkout uses: actions/checkout@v4 - - name: build and run tests + - name: install Mbed TLS + if: matrix.tls_backend == 'mbedtls' + run: brew install mbedtls + - name: build and run tests (OpenSSL) + if: matrix.tls_backend == 'openssl' run: cd test && make + - name: build and run tests (Mbed TLS) + if: matrix.tls_backend == 'mbedtls' + run: cd test && make test_mbedtls && ./test_mbedtls - name: run fuzz test target + if: matrix.tls_backend == 'openssl' run: cd test && make fuzz_test windows: diff --git a/README.md b/README.md index aa94afe..6ad6694 100644 --- a/README.md +++ b/README.md @@ -50,19 +50,25 @@ if (auto res = cli.Get("/hi")) { } ``` -SSL Support ------------ +SSL/TLS Support +--------------- -SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked. +cpp-httplib supports multiple TLS backends through an abstraction layer: + +| Backend | Define | Libraries | +| :------ | :----- | :-------- | +| OpenSSL | `CPPHTTPLIB_OPENSSL_SUPPORT` | `libssl`, `libcrypto` | +| Mbed TLS | `CPPHTTPLIB_MBEDTLS_SUPPORT` | `libmbedtls`, `libmbedx509`, `libmbedcrypto` | > [!NOTE] -> cpp-httplib currently supports only version 3.0 or later. Please see [this page](https://www.openssl.org/policies/releasestrat.html) to get more information. +> OpenSSL 3.0 or later is required. Please see [this page](https://www.openssl.org/policies/releasestrat.html) for more information. > [!TIP] -> For macOS: cpp-httplib now can use system certs with `CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN`. `CoreFoundation` and `Security` should be linked with `-framework`. +> For macOS: cpp-httplib can use system certs with `CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN`. `CoreFoundation` and `Security` should be linked with `-framework`. ```c++ -#define CPPHTTPLIB_OPENSSL_SUPPORT +// Use either OpenSSL or Mbed TLS +#define CPPHTTPLIB_OPENSSL_SUPPORT // or CPPHTTPLIB_MBEDTLS_SUPPORT #include "path/to/httplib.h" // Server diff --git a/docs/httplib_tls_migration.ja.md b/docs/httplib_tls_migration.ja.md deleted file mode 100644 index 6bbc3ff..0000000 --- a/docs/httplib_tls_migration.ja.md +++ /dev/null @@ -1,272 +0,0 @@ -# httplib TLS 抽象化移行ガイド - -## 概要 - -OpenSSL に依存する `httplib.h` の TLS 実装を、バックエンド非依存の `detail::tls` 抽象レイヤーに移行する計画と進捗。 - -**現在の状態:** - -- OpenSSL: **554/554 テスト合格** ✅ -- Mbed TLS: **548/548 テスト合格** ✅ -- 差分: **6 テスト** (OpenSSL 固有型を直接使用) - ---- - -## 基本チェック項目 - -各フェーズで確認すること: - -- [ ] 既存の公開 API に破壊的変更がない -- [ ] `cd test && make` 通過 -- [ ] `cd test && make test_split` 通過 -- [ ] メモリリークなし、コンパイル警告なし -- [ ] `make style_check` 通過 - ---- - -## フェーズ 1〜4: OpenSSL 抽象化 ✅ 完了 - -| フェーズ | 内容 | 状態 | -| --- | --- | --- | -| 1 | `namespace detail::tls` 追加、36 個の API 宣言と OpenSSL 実装 | ✅ | -| 2 | `SSLClient`, `SSLSocketStream` を抽象 API に移行 | ✅ | -| 3 | `SSLServer` を抽象 API に移行 | ✅ | -| 4 | 古い `detail::ssl_*` ヘルパーを削除 | ✅ | - ---- - -## フェーズ 5: Mbed TLS バックエンド ✅ 完了 - -**ビルド:** `cd test && make test_mbedtls` - -**参考:** [Mbed TLS GitHub](https://github.com/Mbed-TLS/mbedtls) - -### 実装済み機能 - -- グローバル初期化 (`tls_global_init/cleanup`) -- クライアント/サーバーコンテキスト -- セッション管理、非ブロッキングハンドシェイク -- I/O (`tls_read/write/pending/shutdown`) -- 証明書検証 (DNS 名 + IP アドレス SAN) -- システム証明書 (Windows/macOS) -- HTTPS→HTTPS リダイレクト時の CA 転送 -- クライアント証明書認証 (Mutual TLS) -- 証明書イントロスペクション (`tls_get_cert_subject_cn()` 等) - -### OpenSSL 専用テスト(6 テスト) - -以下のテストは OpenSSL 固有型 (`X509*`, `EVP_PKEY*`, `X509_STORE*`, `SSL_CTX*`) を直接使用するため、OpenSSL 専用として残存。各テストには抽象化 API を使用した代替テストが用意されている。 - -| OpenSSL 専用テスト | 代替テスト(両バックエンド対応) | 備考 | -| --- | --- | --- | -| `UpdateCAStore` | `UpdateCAStoreWithPem` | `X509_STORE*` を直接使用 | -| `MemoryClientCertPresent` | `PemMemoryClientCertPresent` | `X509*`, `EVP_PKEY*` を直接使用 | -| `MemoryClientEncryptedCertPresent` | `PemMemoryClientEncryptedCertPresent` | 同上 | -| `ClientCAListFromX509Store` | `ClientCAListFromPem` | `X509_STORE*` を直接使用 | -| `CustomizeServerSSLCtx` | `CustomizeServerSSLCtxGeneric` | `SSL_CTX` コールバックコンストラクタ | -| `Issue2251_ClientCertFileNotMatchingKey` | なし | OpenSSL 固有の検出タイミング | - -**注記:** - -- `Issue2251_ClientCertFileNotMatchingKey` は OpenSSL が証明書/鍵不一致をコンテキスト作成時に検出する挙動をテスト。Mbed TLS は接続時まで検出しないため、バックエンド固有の挙動として許容。 -- `CustomizeServerSSLCtx` は `SSLServer(std::function)` コンストラクタをテスト。`CustomizeServerSSLCtxGeneric` はバックエンド非依存の `SSLServer(std::function)` コンストラクタを使用。 -- `CustomizeServerSSLCtxMbedTLS` は mbedTLS 固有の `SSLServer(std::function)` コンストラクタをテスト。 - ---- - -## フェーズ 6-8: 将来 - -| フェーズ | 内容 | 状態 | -| --- | --- | --- | -| 6 | wolfSSL バックエンド | 未着手 | -| 7 | CI マトリックステスト | 未着手 | -| 8 | README.md 更新 | 未着手 | - ---- - -## API 一覧 - -```cpp -namespace httplib::detail::tls { - -// エラー型 -enum class ErrorCode : int { - Success = 0, WantRead, WantWrite, PeerClosed, Fatal, SyscallError, - CertVerifyFailed, HostnameMismatch, -}; -struct TlsError { ErrorCode code; uint64_t backend_code; int sys_errno; }; - -// ハンドル -using tls_ctx_t = void*; -using tls_session_t = void*; -using tls_cert_t = void*; -using tls_ca_store_t = void*; - -// グローバル -bool tls_global_init(); -void tls_global_cleanup(); - -// コンテキスト -tls_ctx_t tls_create_client_context(); -tls_ctx_t tls_create_server_context(); -void tls_free_context(tls_ctx_t ctx); -bool tls_set_min_version(tls_ctx_t ctx, int version); -bool tls_load_ca_pem(tls_ctx_t ctx, const char* pem, size_t len); -bool tls_load_ca_file(tls_ctx_t ctx, const char* path); -bool tls_load_ca_dir(tls_ctx_t ctx, const char* path); -bool tls_load_system_certs(tls_ctx_t ctx); -bool tls_set_client_cert_pem(tls_ctx_t, const char* cert, const char* key, const char* pw); -bool tls_set_client_cert_file(tls_ctx_t, const char* cert, const char* key, const char* pw); -bool tls_set_server_cert_pem(tls_ctx_t, const char* cert, const char* key, const char* pw); -bool tls_set_server_cert_file(tls_ctx_t, const char* cert, const char* key, const char* pw); -bool tls_set_client_ca_file(tls_ctx_t ctx, const char* ca_file, const char* ca_dir); -void tls_set_verify_client(tls_ctx_t ctx, bool require); - -// セッション -tls_session_t tls_create_session(tls_ctx_t ctx, socket_t sock); -void tls_free_session(tls_session_t session); -bool tls_set_sni(tls_session_t session, const char* hostname); -bool tls_set_hostname(tls_session_t session, const char* hostname); - -// ハンドシェイク -TlsError tls_connect(tls_session_t session); -TlsError tls_accept(tls_session_t session); -bool tls_connect_nonblocking(tls_session_t, socket_t, time_t sec, time_t usec, TlsError*); -bool tls_accept_nonblocking(tls_session_t, socket_t, time_t sec, time_t usec, TlsError*); - -// I/O -ssize_t tls_read(tls_session_t, void* buf, size_t len, TlsError& err); -ssize_t tls_write(tls_session_t, const void* buf, size_t len, TlsError& err); -int tls_pending(tls_session_t session); -void tls_shutdown(tls_session_t session, bool graceful); -bool tls_is_peer_closed(tls_session_t session, socket_t sock); - -// 証明書 -tls_cert_t tls_get_peer_cert(tls_session_t session); -void tls_free_cert(tls_cert_t cert); -bool tls_verify_hostname(tls_cert_t cert, const char* hostname); -long tls_get_verify_result(tls_session_t session); - -// 証明書イントロスペクション -std::string tls_get_cert_subject_cn(tls_cert_t cert); -std::string tls_get_cert_issuer_name(tls_cert_t cert); -std::vector tls_get_cert_sans(tls_cert_t cert); -bool tls_get_cert_validity(tls_cert_t cert, std::time_t& not_before, std::time_t& not_after); -std::string tls_get_cert_serial(tls_cert_t cert); - -// CA ストア -tls_ca_store_t tls_create_ca_store(const char* pem, size_t len); -void tls_free_ca_store(tls_ca_store_t store); -bool tls_set_ca_store(tls_ctx_t ctx, tls_ca_store_t store); - -// エラー -uint64_t tls_peek_error(); -uint64_t tls_get_error(); -std::string tls_error_string(uint64_t code); - -} // namespace -``` - ---- - -## バックエンド比較 - -| API | OpenSSL | Mbed TLS | wolfSSL | -| --- | --- | --- | --- | -| `tls_global_init()` | `OPENSSL_init_ssl()` | `mbedtls_entropy_init()` | `wolfSSL_Init()` | -| `tls_create_client_context()` | `SSL_CTX_new()` | `MbedTlsContext` 構造体 | `wolfSSL_CTX_new()` | -| `tls_create_session()` | `SSL_new()` | `MbedTlsSession` 構造体 | `wolfSSL_new()` | -| `tls_read/write()` | `SSL_read/write()` | `mbedtls_ssl_read/write()` | `wolfSSL_read/write()` | -| `tls_get_peer_cert()` | `SSL_get1_peer_certificate()` | `mbedtls_ssl_get_peer_cert()` | `wolfSSL_get_peer_certificate()` | -| `tls_verify_hostname()` | `X509_check_host()` | 手動実装 (SAN 検証) | `wolfSSL_X509_check_host()` | -| `tls_is_peer_closed()` | `SSL_peek()` | `select()` + `recv(MSG_PEEK)` | `wolfSSL_peek()` | -| `tls_create_ca_store()` | `X509_STORE_new()` + PEM 解析 | `mbedtls_x509_crt` + PEM 解析 | `wolfSSL_X509_STORE_new()` | -| `tls_set_ca_store()` | `SSL_CTX_set_cert_store()` | `mbedtls_ssl_conf_ca_chain()` | `wolfSSL_CTX_set_cert_store()` | - -| ErrorCode | OpenSSL | Mbed TLS | wolfSSL | -| --- | --- | --- | --- | -| `WantRead` | `SSL_ERROR_WANT_READ` | `MBEDTLS_ERR_SSL_WANT_READ` | `SSL_ERROR_WANT_READ` | -| `WantWrite` | `SSL_ERROR_WANT_WRITE` | `MBEDTLS_ERR_SSL_WANT_WRITE` | `SSL_ERROR_WANT_WRITE` | -| `PeerClosed` | `SSL_ERROR_ZERO_RETURN` | `MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY` | `SSL_ERROR_ZERO_RETURN` | - ---- - -## 設計メモ - -### バックエンド固有 API - -共通 API + バックエンド固有 API の方式を採用: - -```cpp -// 共通 API(全バックエンド) -class SSLClient { - void *tls_context() const; // バックエンド非依存 - void set_ca_cert_path(const std::string& path); - void load_ca_cert_store(const char* pem, size_t len); -}; - -class SSLServer { - void *tls_context() const; // バックエンド非依存 - // コールバックで ctx を設定(void* を適切な型にキャスト) - SSLServer(const std::function &setup_callback); -}; - -// バックエンド固有 API -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -class SSLClient { - SSL_CTX* ssl_context() const; - void set_ca_cert_store(X509_STORE* store); -}; - -class SSLServer { - SSL_CTX* ssl_context() const; - // OpenSSL ネイティブ API へのフルアクセス - SSLServer(const std::function &setup_callback); -}; -#endif - -#ifdef CPPHTTPLIB_MBEDTLS_SUPPORT -class SSLClient { - mbedtls_ssl_config* ssl_config() const; -}; - -class SSLServer { - mbedtls_ssl_config* ssl_config() const; - // mbedTLS ネイティブ API へのフルアクセス - SSLServer(const std::function &setup_callback); -}; -#endif -``` - -### 実装時の教訓 - -1. **機能を無効化する際は代替実装を用意** - `if (false)` で無効化すると機能が失われる -2. **移行対象を完全にリストアップ** - 間接的に依存する関数も含める -3. **テスト失敗時はデバッグ出力で挙動確認** -4. **`git stash` で元コードとの比較を早めに行う** - -### Mbed TLS 固有の注意点 - -1. **SAN フォーマット**: Mbed TLS 3.x は ASN.1 タグなしの生データ(DNS は ASCII、IP は 4/16 バイト) -2. **エラーキュー**: スレッドローカル変数 `mbedtls_last_error()` で管理 -3. **接続状態確認**: `select()` + `recv(MSG_PEEK)` で TCP レベル確認(SSL ステートを変更しない) -4. **ピア証明書保持**: `MBEDTLS_SSL_KEEP_PEER_CERTIFICATE` が有効な場合、`mbedtls_ssl_get_peer_cert()` でクライアント/サーバー両方の証明書を取得可能 - ---- - -## テスト - -```bash -cd test && make # OpenSSL テスト -cd test && make test_mbedtls # Mbed TLS テスト -cd test && make test_split # 分割ビルドテスト -cd test && make proxy # プロキシテスト (Docker 必要) -``` - -**証明書ファイル** (`test/gen-certs.sh` で生成): - -| ファイル | 用途 | -| --- | --- | -| `cert.pem`, `key.pem` | サーバー証明書 | -| `rootCA.cert.pem` | ルート CA | -| `client.cert.pem`, `client.key.pem` | クライアント証明書 | diff --git a/docs/tls_migration_guide.md b/docs/tls_migration_guide.md new file mode 100644 index 0000000..67d0276 --- /dev/null +++ b/docs/tls_migration_guide.md @@ -0,0 +1,234 @@ +# httplib TLS Abstraction Migration Guide + +## Overview + +Migrating the TLS implementation in `httplib.h` from OpenSSL-dependent code to a backend-agnostic `detail::tls` abstraction layer. + +**Current Status:** + +- OpenSSL: **554/554 tests passing** +- Mbed TLS: **548/548 tests passing** +- Difference: **6 tests** (using OpenSSL-specific types directly) + +--- + +## Build & Test + +```bash +cd test && make # OpenSSL tests +cd test && make test_mbedtls # Mbed TLS tests +cd test && make test_split # Split build tests +cd test && make proxy # Proxy tests (requires Docker) +``` + +--- + +## OpenSSL-Only Tests (6 tests) + +The following tests use OpenSSL-specific types (`X509*`, `EVP_PKEY*`, `X509_STORE*`, `SSL_CTX*`) directly, so they remain as OpenSSL-only. + +| OpenSSL-Only Test | Alternative Test (both backends) | +| --- | --- | +| `UpdateCAStore` | `UpdateCAStoreWithPem` | +| `MemoryClientCertPresent` | `PemMemoryClientCertPresent` | +| `MemoryClientEncryptedCertPresent` | `PemMemoryClientEncryptedCertPresent` | +| `ClientCAListFromX509Store` | `ClientCAListFromPem` | +| `CustomizeServerSSLCtx` | `CustomizeServerSSLCtxGeneric` | +| `Issue2251_ClientCertFileNotMatchingKey` | `TlsApiTest.ClientCertKeyMismatch` | + +`Issue2251_ClientCertFileNotMatchingKey` tests the behavior where OpenSSL detects certificate/key mismatch at context creation time. `TlsApiTest.ClientCertKeyMismatch` directly tests the abstract TLS API (`tls_set_client_cert_file`) and works with both backends. OpenSSL returns an error immediately, while Mbed TLS succeeds but fails at connection time (this behavior is covered by `Issue2251_SwappedClientCertAndKey`). + +--- + +## Completed Phases + +| Phase | Description | Status | +| --- | --- | --- | +| 1 | TLS abstraction layer design & basic API | ✅ Complete | +| 2 | OpenSSL backend implementation | ✅ Complete | +| 3 | Mbed TLS backend implementation | ✅ Complete | +| 4 | Integration into httplib.h | ✅ Complete | +| 5 | Test migration & backend-specific API | ✅ Complete | +| 6 | CI matrix testing | ✅ Complete | +| 7 | README.md update | ✅ Complete | + +--- + +## Future Work + +| Description | Status | +| --- | --- | +| wolfSSL backend | Not started | + +--- + +## API Reference + +```cpp +namespace httplib::detail::tls { + +// Error types +enum class ErrorCode : int { + Success = 0, WantRead, WantWrite, PeerClosed, Fatal, SyscallError, + CertVerifyFailed, HostnameMismatch, +}; +struct TlsError { ErrorCode code; uint64_t backend_code; int sys_errno; }; + +// Handles +using tls_ctx_t = void*; +using tls_session_t = void*; +using tls_cert_t = void*; +using tls_ca_store_t = void*; + +// Global +bool tls_global_init(); +void tls_global_cleanup(); + +// Context +tls_ctx_t tls_create_client_context(); +tls_ctx_t tls_create_server_context(); +void tls_free_context(tls_ctx_t ctx); +bool tls_set_min_version(tls_ctx_t ctx, int version); +bool tls_load_ca_pem(tls_ctx_t ctx, const char* pem, size_t len); +bool tls_load_ca_file(tls_ctx_t ctx, const char* path); +bool tls_load_ca_dir(tls_ctx_t ctx, const char* path); +bool tls_load_system_certs(tls_ctx_t ctx); +bool tls_set_client_cert_pem(tls_ctx_t, const char* cert, const char* key, const char* pw); +bool tls_set_client_cert_file(tls_ctx_t, const char* cert, const char* key, const char* pw); +bool tls_set_server_cert_pem(tls_ctx_t, const char* cert, const char* key, const char* pw); +bool tls_set_server_cert_file(tls_ctx_t, const char* cert, const char* key, const char* pw); +bool tls_set_client_ca_file(tls_ctx_t ctx, const char* ca_file, const char* ca_dir); +void tls_set_verify_client(tls_ctx_t ctx, bool require); + +// Session +tls_session_t tls_create_session(tls_ctx_t ctx, socket_t sock); +void tls_free_session(tls_session_t session); +bool tls_set_sni(tls_session_t session, const char* hostname); +bool tls_set_hostname(tls_session_t session, const char* hostname); + +// Handshake +TlsError tls_connect(tls_session_t session); +TlsError tls_accept(tls_session_t session); +bool tls_connect_nonblocking(tls_session_t, socket_t, time_t sec, time_t usec, TlsError*); +bool tls_accept_nonblocking(tls_session_t, socket_t, time_t sec, time_t usec, TlsError*); + +// I/O +ssize_t tls_read(tls_session_t, void* buf, size_t len, TlsError& err); +ssize_t tls_write(tls_session_t, const void* buf, size_t len, TlsError& err); +int tls_pending(tls_session_t session); +void tls_shutdown(tls_session_t session, bool graceful); +bool tls_is_peer_closed(tls_session_t session, socket_t sock); + +// Certificate +tls_cert_t tls_get_peer_cert(tls_session_t session); +void tls_free_cert(tls_cert_t cert); +bool tls_verify_hostname(tls_cert_t cert, const char* hostname); +long tls_get_verify_result(tls_session_t session); + +// Certificate introspection +std::string tls_get_cert_subject_cn(tls_cert_t cert); +std::string tls_get_cert_issuer_name(tls_cert_t cert); +bool tls_get_cert_sans(tls_cert_t cert, std::vector& sans); +bool tls_get_cert_validity(tls_cert_t cert, time_t& not_before, time_t& not_after); +std::string tls_get_cert_serial(tls_cert_t cert); + +// CA store +tls_ca_store_t tls_create_ca_store(const char* pem, size_t len); +void tls_free_ca_store(tls_ca_store_t store); +bool tls_set_ca_store(tls_ctx_t ctx, tls_ca_store_t store); + +// Server certificate update +bool tls_update_server_cert(tls_ctx_t ctx, const char* cert_pem, const char* key_pem, const char* pw); +bool tls_update_server_client_ca(tls_ctx_t ctx, const char* ca_pem); + +// Verify callback +using TlsVerifyCallback = std::function; +using TlsVerifyCallbackEx = std::function; +bool tls_set_verify_callback(tls_ctx_t ctx, TlsVerifyCallback callback); +bool tls_set_verify_callback_ex(tls_ctx_t ctx, TlsVerifyCallbackEx callback); +long tls_get_verify_error(tls_session_t session); +std::string tls_verify_error_string(long error_code); + +// Error +uint64_t tls_peek_error(); +uint64_t tls_get_error(); +std::string tls_error_string(uint64_t code); + +} // namespace +``` + +--- + +## Backend Comparison + +| API | OpenSSL | Mbed TLS | +| --- | --- | --- | +| `tls_global_init()` | `OPENSSL_init_ssl()` | `mbedtls_entropy_init()` | +| `tls_create_client_context()` | `SSL_CTX_new()` | `MbedTlsContext` struct | +| `tls_create_session()` | `SSL_new()` | `MbedTlsSession` struct | +| `tls_read/write()` | `SSL_read/write()` | `mbedtls_ssl_read/write()` | +| `tls_get_peer_cert()` | `SSL_get1_peer_certificate()` | `mbedtls_ssl_get_peer_cert()` | +| `tls_verify_hostname()` | `X509_check_host()` | Manual implementation (SAN verification) | +| `tls_is_peer_closed()` | `SSL_peek()` | `select()` + `recv(MSG_PEEK)` | +| `tls_create_ca_store()` | `X509_STORE_new()` + PEM parsing | `mbedtls_x509_crt` + PEM parsing | +| `tls_set_ca_store()` | `SSL_CTX_set_cert_store()` | `mbedtls_ssl_conf_ca_chain()` | + +| ErrorCode | OpenSSL | Mbed TLS | +| --- | --- | --- | +| `WantRead` | `SSL_ERROR_WANT_READ` | `MBEDTLS_ERR_SSL_WANT_READ` | +| `WantWrite` | `SSL_ERROR_WANT_WRITE` | `MBEDTLS_ERR_SSL_WANT_WRITE` | +| `PeerClosed` | `SSL_ERROR_ZERO_RETURN` | `MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY` | + +--- + +## Backend-Specific API + +Adopting a common API + backend-specific API approach: + +```cpp +// Common API (all backends) +class SSLClient { + void *tls_context() const; + void set_ca_cert_path(const std::string& path); + void load_ca_cert_store(const char* pem, size_t len); +}; + +class SSLServer { + void *tls_context() const; + SSLServer(const std::function &setup_callback); +}; + +// OpenSSL-specific API +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +class SSLClient { + SSL_CTX* ssl_context() const; + void set_ca_cert_store(X509_STORE* store); +}; + +class SSLServer { + SSL_CTX* ssl_context() const; + SSLServer(const std::function &setup_callback); +}; +#endif + +// Mbed TLS-specific API +#ifdef CPPHTTPLIB_MBEDTLS_SUPPORT +class SSLClient { + mbedtls_ssl_config* ssl_config() const; +}; + +class SSLServer { + mbedtls_ssl_config* ssl_config() const; + SSLServer(const std::function &setup_callback); +}; +#endif +``` + +--- + +## Mbed TLS Implementation Notes + +- **SAN format**: Mbed TLS 3.x provides raw data without ASN.1 tags (DNS is ASCII, IP is 4/16 bytes) +- **Error queue**: Managed using thread-local variables +- **Connection state check**: TCP-level verification using `select()` + `recv(MSG_PEEK)` +- **Peer certificate**: Available when `MBEDTLS_SSL_KEEP_PEER_CERTIFICATE` is enabled diff --git a/httplib.h b/httplib.h index 06fd556..fbfad02 100644 --- a/httplib.h +++ b/httplib.h @@ -16190,6 +16190,15 @@ inline SSLClient::SSLClient(const std::string &host, int port, } } +inline void SSLClient::set_ca_cert_store(void *ca_cert_store) { + if (ca_cert_store && ctx_) { + // tls_set_ca_store takes ownership of ca_cert_store + detail::tls::tls_set_ca_store(ctx_, ca_cert_store); + } else if (ca_cert_store) { + detail::tls::tls_free_ca_store(ca_cert_store); + } +} + inline void SSLClient::load_ca_cert_store(const char *ca_cert, std::size_t size) { if (ctx_ && ca_cert && size > 0) { diff --git a/test/Makefile b/test/Makefile index 050a862..ae6fd46 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,38 +1,40 @@ CXX = clang++ CXXFLAGS = -g -std=c++11 -I. -Wall -Wextra -Wtype-limits -Wconversion -Wshadow $(EXTRA_CXXFLAGS) -DCPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO # -fno-exceptions -DCPPHTTPLIB_NO_EXCEPTIONS -fsanitize=address -PREFIX ?= $(shell brew --prefix) - -OPENSSL_DIR = $(PREFIX)/opt/openssl@3 -OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto - -MBEDTLS_DIR = $(PREFIX)/opt/mbedtls -MBEDTLS_SUPPORT = -DCPPHTTPLIB_MBEDTLS_SUPPORT -I$(MBEDTLS_DIR)/include -L$(MBEDTLS_DIR)/lib -lmbedtls -lmbedx509 -lmbedcrypto - ifneq ($(OS), Windows_NT) UNAME_S := $(shell uname -s) ifeq ($(UNAME_S), Darwin) + # macOS: use Homebrew paths + PREFIX ?= $(shell brew --prefix) + OPENSSL_DIR = $(PREFIX)/opt/openssl@3 + OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto OPENSSL_SUPPORT += -DCPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN -framework Security + MBEDTLS_DIR = $(PREFIX)/opt/mbedtls + MBEDTLS_SUPPORT = -DCPPHTTPLIB_MBEDTLS_SUPPORT -I$(MBEDTLS_DIR)/include -L$(MBEDTLS_DIR)/lib -lmbedtls -lmbedx509 -lmbedcrypto MBEDTLS_SUPPORT += -DCPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN -framework Security + else + # Linux: use system paths + OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -lssl -lcrypto + MBEDTLS_SUPPORT = -DCPPHTTPLIB_MBEDTLS_SUPPORT -lmbedtls -lmbedx509 -lmbedcrypto endif endif ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz -BROTLI_DIR = $(PREFIX)/opt/brotli -BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon -lbrotlienc -lbrotlidec - -ZSTD_DIR = $(PREFIX)/opt/zstd -ZSTD_SUPPORT = -DCPPHTTPLIB_ZSTD_SUPPORT -I$(ZSTD_DIR)/include -L$(ZSTD_DIR)/lib -lzstd - -LIBS = -lpthread -lcurl ifneq ($(OS), Windows_NT) UNAME_S := $(shell uname -s) ifeq ($(UNAME_S), Darwin) - LIBS += -framework CoreFoundation -framework CFNetwork - endif - ifneq ($(UNAME_S), Darwin) - LIBS += -lanl + # macOS: use Homebrew paths for brotli and zstd + BROTLI_DIR = $(PREFIX)/opt/brotli + BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon -lbrotlienc -lbrotlidec + ZSTD_DIR = $(PREFIX)/opt/zstd + ZSTD_SUPPORT = -DCPPHTTPLIB_ZSTD_SUPPORT -I$(ZSTD_DIR)/include -L$(ZSTD_DIR)/lib -lzstd + LIBS = -lpthread -lcurl -framework CoreFoundation -framework CFNetwork + else + # Linux: use system paths + BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -lbrotlicommon -lbrotlienc -lbrotlidec + ZSTD_SUPPORT = -DCPPHTTPLIB_ZSTD_SUPPORT -lzstd + LIBS = -lpthread -lcurl -lanl endif endif