Files
cpp-httplib/docs/tls_migration_guide.md
2026-01-12 21:48:22 -05:00

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_CERTIFICATE is enabled