mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-01-26 19:22:09 +00:00
8.1 KiB
8.1 KiB
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
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
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:
// 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_CERTIFICATEis enabled