mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-01-19 04:52:08 +00:00
Phase 6 and 7
This commit is contained in:
43
.github/workflows/test.yaml
vendored
43
.github/workflows/test.yaml
vendored
@@ -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:
|
||||
|
||||
18
README.md
18
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
|
||||
|
||||
@@ -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
234
docs/tls_migration_guide.md
Normal 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
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user