Phase 6 and 7

This commit is contained in:
yhirose
2026-01-12 21:13:52 -05:00
parent 369b93613e
commit 3b67cb19eb
6 changed files with 309 additions and 307 deletions

View File

@@ -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:

View File

@@ -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

View File

@@ -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<bool(SSL_CTX&)>)` コンストラクタをテスト。`CustomizeServerSSLCtxGeneric` はバックエンド非依存の `SSLServer(std::function<bool(void*)>)` コンストラクタを使用。
- `CustomizeServerSSLCtxMbedTLS` は mbedTLS 固有の `SSLServer(std::function<bool(mbedtls_ssl_config&)>)` コンストラクタをテスト。
---
## フェーズ 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<TlsSanEntry> 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<bool(void *ctx)> &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<bool(SSL_CTX &ssl_ctx)> &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<bool(mbedtls_ssl_config &conf)> &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` | クライアント証明書 |

234
docs/tls_migration_guide.md Normal file
View File

@@ -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<TlsSanEntry>& 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<bool(bool, void*)>;
using TlsVerifyCallbackEx = std::function<bool(bool, void*, int)>;
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<bool(void *ctx)> &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<bool(SSL_CTX &ssl_ctx)> &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<bool(mbedtls_ssl_config &conf)> &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

View File

@@ -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) {

View File

@@ -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