mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-28 12:40:54 +00:00
Merge the SSLClient and WebSocketClient TLS session setup
SSLClient::initialize_ssl kept its own copy of the session setup that detail::setup_client_tls_session already implemented for WebSocketClient. Extend the shared function with the pieces only SSLClient needed - a session verifier, an independent hostname verification flag, the context mutex, Windows Schannel verification and error details - and let initialize_ssl build a ClientTlsSessionOptions and call it. All of them default, so WebSocketClient's call site is unchanged. This settles one difference between the two: WebSocketClient used to call tls::set_hostname for named hosts, which on OpenSSL turns on verification during the handshake, while SSLClient always set SNI only and verified post-handshake. The shared function now does the latter for both, so tls::set_hostname loses its last caller and goes away, as does the write-only SSLClient::verify_result_. Certificate verification with a host name rather than an IP literal was the one combination the WebSocket tests never covered, and it is exactly the path this normalizes. WebSocketSSLDnsHostTest fills that in; cert2 gains a DNS:localhost SAN so a name can be verified against it.
This commit is contained in:
1 parent
98be5fd3a6
commit
1c2607cbb8
3 files changed
+231
-185
No files matched your search
@@ -3233,8 +3233,6 @@ private:
|
|||||||
// Used to keep custom CA configuration exclusive with system CA loading.
|
// Used to keep custom CA configuration exclusive with system CA loading.
|
||||||
bool ca_cert_store_set_ = false;
|
bool ca_cert_store_set_ = false;
|
||||||
|
|
||||||
long verify_result_ = 0;
|
|
||||||
|
|
||||||
std::function<SSLVerifierResponse(tls::session_t)> session_verifier_;
|
std::function<SSLVerifierResponse(tls::session_t)> session_verifier_;
|
||||||
|
|
||||||
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
|
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
|
||||||
@@ -4775,7 +4773,6 @@ void set_verify_client(ctx_t ctx, bool require);
|
|||||||
session_t create_session(ctx_t ctx, socket_t sock);
|
session_t create_session(ctx_t ctx, socket_t sock);
|
||||||
void free_session(session_t session);
|
void free_session(session_t session);
|
||||||
bool set_sni(session_t session, const char *hostname);
|
bool set_sni(session_t session, const char *hostname);
|
||||||
bool set_hostname(session_t session, const char *hostname);
|
|
||||||
|
|
||||||
// Handshake (non-blocking capable)
|
// Handshake (non-blocking capable)
|
||||||
TlsError connect(session_t session);
|
TlsError connect(session_t session);
|
||||||
@@ -10026,50 +10023,143 @@ inline bool load_client_ca_config(tls::ctx_t ctx,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool setup_client_tls_session(const std::string &host, tls::ctx_t ctx,
|
// The parts of session setup that only SSLClient needs. WebSocketClient takes
|
||||||
tls::session_t &session, socket_t sock,
|
// the defaults, which is what keeps the two clients on one implementation.
|
||||||
bool server_certificate_verification,
|
struct ClientTlsSessionOptions {
|
||||||
time_t timeout_sec, time_t timeout_usec) {
|
// SSLClient exposes this independently of certificate verification;
|
||||||
|
// WebSocketClient always checks the identity when it verifies the chain.
|
||||||
|
bool server_hostname_verification = true;
|
||||||
|
std::function<SSLVerifierResponse(tls::session_t)> session_verifier;
|
||||||
|
// When non-null, guards session creation against concurrent use of the
|
||||||
|
// context. A WebSocketClient is not safe to use from several threads to
|
||||||
|
// begin with, so it passes nothing.
|
||||||
|
std::mutex *ctx_mutex = nullptr;
|
||||||
|
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
|
||||||
|
// The caller decides whether Schannel has anything to say about this
|
||||||
|
// connection; see SSLClient::initialize_ssl().
|
||||||
|
bool windows_cert_verification = false;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
// Filled in on failure for callers that report error details.
|
||||||
|
struct ClientTlsSessionError {
|
||||||
|
Error error = Error::Success;
|
||||||
|
int ssl_error = 0;
|
||||||
|
uint64_t backend_error = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Establishes a client TLS session on an already connected socket. On failure
|
||||||
|
// the session is left for the caller to free: SSLClient frees it right away,
|
||||||
|
// WebSocketClient keeps it in a member that shutdown_and_close() cleans up.
|
||||||
|
inline bool setup_client_tls_session(
|
||||||
|
const std::string &host, tls::ctx_t ctx, tls::session_t &session,
|
||||||
|
socket_t sock, bool server_certificate_verification, time_t timeout_sec,
|
||||||
|
time_t timeout_usec, ClientTlsSessionError *out_error = nullptr,
|
||||||
|
const ClientTlsSessionOptions &options = ClientTlsSessionOptions()) {
|
||||||
using namespace tls;
|
using namespace tls;
|
||||||
|
|
||||||
if (!ctx) { return false; }
|
auto fail = [&](Error error, int ssl_error, uint64_t backend_error) {
|
||||||
|
if (out_error) {
|
||||||
|
out_error->error = error;
|
||||||
|
out_error->ssl_error = ssl_error;
|
||||||
|
out_error->backend_error = backend_error;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
bool is_ip = is_ip_address(host);
|
if (!ctx) {
|
||||||
|
session = nullptr;
|
||||||
|
return fail(Error::SSLConnection, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
#if defined(CPPHTTPLIB_MBEDTLS_SUPPORT) || defined(CPPHTTPLIB_WOLFSSL_SUPPORT)
|
#if defined(CPPHTTPLIB_MBEDTLS_SUPPORT) || defined(CPPHTTPLIB_WOLFSSL_SUPPORT)
|
||||||
// Chain verification happens during the handshake even for IP hosts; the
|
// Mbed TLS and wolfSSL need the verification mode set explicitly; OpenSSL
|
||||||
// certificate identity is verified post-handshake via verify_hostname()
|
// uses SSL_VERIFY_NONE and does all verification post-handshake. Chain
|
||||||
|
// verification happens during the handshake even for IP hosts; the
|
||||||
|
// certificate identity is verified post-handshake via verify_hostname().
|
||||||
set_verify_client(ctx, server_certificate_verification);
|
set_verify_client(ctx, server_certificate_verification);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
session = create_session(ctx, sock);
|
{
|
||||||
if (!session) { return false; }
|
std::unique_lock<std::mutex> guard;
|
||||||
|
if (options.ctx_mutex) {
|
||||||
|
guard = std::unique_lock<std::mutex>(*options.ctx_mutex);
|
||||||
|
}
|
||||||
|
session = create_session(ctx, sock);
|
||||||
|
}
|
||||||
|
if (!session) { return fail(Error::SSLConnection, 0, get_error()); }
|
||||||
|
|
||||||
// RFC 6066: SNI must not be set for IP addresses. On Mbed TLS and wolfSSL
|
// RFC 6066: SNI must not be set for IP addresses. On Mbed TLS and wolfSSL
|
||||||
// set_hostname also sets SNI, so it must be skipped for IP hosts as well;
|
// set_sni also turns on hostname verification during the handshake, so it
|
||||||
// their identity is checked post-handshake below instead.
|
// must be skipped for IP hosts as well; their identity is checked
|
||||||
if (!is_ip) {
|
// post-handshake below instead.
|
||||||
if (server_certificate_verification) {
|
if (!is_ip_address(host)) {
|
||||||
set_hostname(session, host.c_str());
|
if (!set_sni(session, host.c_str())) {
|
||||||
} else {
|
return fail(Error::SSLConnection, 0, get_error());
|
||||||
set_sni(session, host.c_str());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!connect_nonblocking(session, sock, timeout_sec, timeout_usec, nullptr)) {
|
TlsError tls_err;
|
||||||
return false;
|
if (!connect_nonblocking(session, sock, timeout_sec, timeout_usec,
|
||||||
|
&tls_err)) {
|
||||||
|
auto error = Error::SSLConnection;
|
||||||
|
if (tls_err.code == ErrorCode::CertVerifyFailed) {
|
||||||
|
error = Error::SSLServerVerification;
|
||||||
|
} else if (tls_err.code == ErrorCode::HostnameMismatch) {
|
||||||
|
error = Error::SSLServerHostnameVerification;
|
||||||
|
}
|
||||||
|
return fail(error, static_cast<int>(tls_err.code), tls_err.backend_code);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (server_certificate_verification) {
|
auto verification_status = SSLVerifierResponse::NoDecisionMade;
|
||||||
if (get_verify_result(session) != 0) { return false; }
|
if (options.session_verifier) {
|
||||||
|
verification_status = options.session_verifier(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verification_status == SSLVerifierResponse::CertificateRejected) {
|
||||||
|
return fail(Error::SSLServerVerification, 0, get_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verification_status == SSLVerifierResponse::NoDecisionMade &&
|
||||||
|
server_certificate_verification) {
|
||||||
|
auto verify_result = get_verify_result(session);
|
||||||
|
if (verify_result != 0) {
|
||||||
|
return fail(Error::SSLServerVerification, 0,
|
||||||
|
static_cast<uint64_t>(verify_result));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto server_cert = get_peer_cert(session);
|
||||||
|
if (!server_cert) {
|
||||||
|
return fail(Error::SSLServerVerification, 0, get_error());
|
||||||
|
}
|
||||||
|
auto cert_guard = detail::scope_exit([&] { free_cert(server_cert); });
|
||||||
|
|
||||||
// Identity check against the peer certificate, post-handshake for all
|
// Identity check against the peer certificate, post-handshake for all
|
||||||
// backends (same as SSLClient). For IP hosts this is the only identity
|
// backends. For IP hosts this is the only identity verification, since no
|
||||||
// verification since no hostname is bound during the handshake.
|
// hostname is bound during the handshake.
|
||||||
auto server_cert = get_peer_cert(session);
|
if (options.server_hostname_verification) {
|
||||||
if (!server_cert) { return false; }
|
if (!verify_hostname(server_cert, host.c_str())) {
|
||||||
auto cert_guard = detail::scope_exit([&] { free_cert(server_cert); });
|
return fail(Error::SSLServerHostnameVerification, 0,
|
||||||
if (!verify_hostname(server_cert, host.c_str())) { return false; }
|
hostname_mismatch_code());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
|
||||||
|
// Additional Windows Schannel verification.
|
||||||
|
// This provides real-time certificate validation with Windows Update
|
||||||
|
// integration, working with both OpenSSL and MbedTLS backends.
|
||||||
|
if (options.windows_cert_verification) {
|
||||||
|
std::vector<unsigned char> der;
|
||||||
|
if (get_cert_der(server_cert, der)) {
|
||||||
|
uint64_t wincrypt_error = 0;
|
||||||
|
if (!verify_cert_with_windows_schannel(
|
||||||
|
der, host, options.server_hostname_verification,
|
||||||
|
wincrypt_error)) {
|
||||||
|
return fail(Error::SSLServerVerification, 0, wincrypt_error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -17106,8 +17196,6 @@ inline bool SSLClient::load_certs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
|
inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
|
||||||
using namespace tls;
|
|
||||||
|
|
||||||
// Load CA certificates if server verification is enabled
|
// Load CA certificates if server verification is enabled
|
||||||
if (server_certificate_verification_) {
|
if (server_certificate_verification_) {
|
||||||
if (!load_certs()) {
|
if (!load_certs()) {
|
||||||
@@ -17117,134 +17205,40 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_ip = detail::is_ip_address(host_);
|
detail::ClientTlsSessionOptions options;
|
||||||
|
options.server_hostname_verification = server_hostname_verification_;
|
||||||
#if defined(CPPHTTPLIB_MBEDTLS_SUPPORT) || defined(CPPHTTPLIB_WOLFSSL_SUPPORT)
|
options.session_verifier = session_verifier_;
|
||||||
// MbedTLS/wolfSSL need explicit verification mode (OpenSSL uses
|
options.ctx_mutex = &ctx_mutex_;
|
||||||
// SSL_VERIFY_NONE by default and performs all verification post-handshake).
|
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
|
||||||
// Chain verification happens during the handshake even for IP hosts; the
|
// Skip Schannel when a custom CA cert is specified, as the Windows
|
||||||
// certificate identity is verified post-handshake via verify_hostname().
|
// certificate store would not know about user-provided CA certificates.
|
||||||
set_verify_client(ctx_, server_certificate_verification_);
|
// Also skip when system CA trust is explicitly disabled.
|
||||||
|
options.windows_cert_verification =
|
||||||
|
enable_windows_cert_verification_ &&
|
||||||
|
system_ca_mode_ != SystemCAMode::Disabled && ca_cert_file_path_.empty() &&
|
||||||
|
ca_cert_dir_path_.empty() && ca_cert_pem_.empty() && !ca_cert_store_set_;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Create TLS session
|
tls::session_t session = nullptr;
|
||||||
session_t session = nullptr;
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> guard(ctx_mutex_);
|
|
||||||
session = create_session(ctx_, socket.sock);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!session) {
|
|
||||||
error = Error::SSLConnection;
|
|
||||||
last_backend_error_ = get_error();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use scope_exit to ensure session is freed on error paths
|
// Use scope_exit to ensure session is freed on error paths
|
||||||
bool success = false;
|
bool success = false;
|
||||||
auto session_guard = detail::scope_exit([&] {
|
auto session_guard = detail::scope_exit([&] {
|
||||||
if (!success) { free_session(session); }
|
if (!success) { tls::free_session(session); }
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set SNI extension (skip for IP addresses per RFC 6066).
|
detail::ClientTlsSessionError tls_error;
|
||||||
// On MbedTLS, set_sni also enables hostname verification internally.
|
if (!detail::setup_client_tls_session(
|
||||||
// On OpenSSL, set_sni only sets SNI; verification is done post-handshake.
|
host_, ctx_, session, socket.sock, server_certificate_verification_,
|
||||||
if (!is_ip) {
|
connection_timeout_sec_, connection_timeout_usec_, &tls_error,
|
||||||
if (!set_sni(session, host_.c_str())) {
|
options)) {
|
||||||
error = Error::SSLConnection;
|
error = tls_error.error;
|
||||||
last_backend_error_ = get_error();
|
last_ssl_error_ = tls_error.ssl_error;
|
||||||
return false;
|
last_backend_error_ = tls_error.backend_error;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform non-blocking TLS handshake with timeout
|
|
||||||
TlsError tls_err;
|
|
||||||
if (!connect_nonblocking(session, socket.sock, connection_timeout_sec_,
|
|
||||||
connection_timeout_usec_, &tls_err)) {
|
|
||||||
last_ssl_error_ = static_cast<int>(tls_err.code);
|
|
||||||
last_backend_error_ = tls_err.backend_code;
|
|
||||||
if (tls_err.code == ErrorCode::CertVerifyFailed) {
|
|
||||||
error = Error::SSLServerVerification;
|
|
||||||
} else if (tls_err.code == ErrorCode::HostnameMismatch) {
|
|
||||||
error = Error::SSLServerHostnameVerification;
|
|
||||||
} else {
|
|
||||||
error = Error::SSLConnection;
|
|
||||||
}
|
|
||||||
output_error_log(error, nullptr);
|
output_error_log(error, nullptr);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post-handshake session verifier callback
|
|
||||||
auto verification_status = SSLVerifierResponse::NoDecisionMade;
|
|
||||||
if (session_verifier_) { verification_status = session_verifier_(session); }
|
|
||||||
|
|
||||||
if (verification_status == SSLVerifierResponse::CertificateRejected) {
|
|
||||||
last_backend_error_ = get_error();
|
|
||||||
error = Error::SSLServerVerification;
|
|
||||||
output_error_log(error, nullptr);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default server certificate verification
|
|
||||||
if (verification_status == SSLVerifierResponse::NoDecisionMade &&
|
|
||||||
server_certificate_verification_) {
|
|
||||||
verify_result_ = tls::get_verify_result(session);
|
|
||||||
if (verify_result_ != 0) {
|
|
||||||
last_backend_error_ = static_cast<uint64_t>(verify_result_);
|
|
||||||
error = Error::SSLServerVerification;
|
|
||||||
output_error_log(error, nullptr);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto server_cert = get_peer_cert(session);
|
|
||||||
if (!server_cert) {
|
|
||||||
last_backend_error_ = get_error();
|
|
||||||
error = Error::SSLServerVerification;
|
|
||||||
output_error_log(error, nullptr);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
auto cert_guard = detail::scope_exit([&] { free_cert(server_cert); });
|
|
||||||
|
|
||||||
// Hostname verification (post-handshake for all cases).
|
|
||||||
// On OpenSSL, verification is always post-handshake (SSL_VERIFY_NONE).
|
|
||||||
// On MbedTLS, set_sni already enabled hostname verification during
|
|
||||||
// handshake for non-IP hosts, but this check is still needed for IP
|
|
||||||
// addresses where SNI is not set.
|
|
||||||
if (server_hostname_verification_) {
|
|
||||||
if (!verify_hostname(server_cert, host_.c_str())) {
|
|
||||||
last_backend_error_ = hostname_mismatch_code();
|
|
||||||
error = Error::SSLServerHostnameVerification;
|
|
||||||
output_error_log(error, nullptr);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
|
|
||||||
// Additional Windows Schannel verification.
|
|
||||||
// This provides real-time certificate validation with Windows Update
|
|
||||||
// integration, working with both OpenSSL and MbedTLS backends.
|
|
||||||
// Skip when a custom CA cert is specified, as the Windows certificate
|
|
||||||
// store would not know about user-provided CA certificates. Also skip
|
|
||||||
// when system CA trust is explicitly disabled.
|
|
||||||
if (enable_windows_cert_verification_ &&
|
|
||||||
system_ca_mode_ != SystemCAMode::Disabled &&
|
|
||||||
ca_cert_file_path_.empty() && ca_cert_dir_path_.empty() &&
|
|
||||||
ca_cert_pem_.empty() && !ca_cert_store_set_) {
|
|
||||||
std::vector<unsigned char> der;
|
|
||||||
if (get_cert_der(server_cert, der)) {
|
|
||||||
uint64_t wincrypt_error = 0;
|
|
||||||
if (!detail::verify_cert_with_windows_schannel(
|
|
||||||
der, host_, server_hostname_verification_, wincrypt_error)) {
|
|
||||||
last_backend_error_ = wincrypt_error;
|
|
||||||
error = Error::SSLServerVerification;
|
|
||||||
output_error_log(error, nullptr);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
success = true;
|
success = true;
|
||||||
socket.ssl = session;
|
socket.ssl = session;
|
||||||
return true;
|
return true;
|
||||||
@@ -17974,32 +17968,6 @@ inline bool set_sni(session_t session, const char *hostname) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool set_hostname(session_t session, const char *hostname) {
|
|
||||||
if (!session || !hostname) return false;
|
|
||||||
|
|
||||||
auto ssl = static_cast<SSL *>(session);
|
|
||||||
|
|
||||||
// Enable hostname verification
|
|
||||||
auto param = SSL_get0_param(ssl);
|
|
||||||
if (!param) return false;
|
|
||||||
|
|
||||||
if (detail::is_ip_address(hostname)) {
|
|
||||||
// RFC 6066: SNI must not be set for IP addresses; verify against the
|
|
||||||
// certificate's IP SANs instead of its DNS names
|
|
||||||
if (X509_VERIFY_PARAM_set1_ip_asc(param, hostname) != 1) { return false; }
|
|
||||||
} else {
|
|
||||||
// Set SNI (Server Name Indication)
|
|
||||||
if (!set_sni(session, hostname)) { return false; }
|
|
||||||
|
|
||||||
X509_VERIFY_PARAM_set_hostflags(param,
|
|
||||||
X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
|
|
||||||
if (X509_VERIFY_PARAM_set1_host(param, hostname, 0) != 1) { return false; }
|
|
||||||
}
|
|
||||||
|
|
||||||
SSL_set_verify(ssl, SSL_VERIFY_PEER, nullptr);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline TlsError connect(session_t session) {
|
inline TlsError connect(session_t session) {
|
||||||
if (!session) { return TlsError(); }
|
if (!session) { return TlsError(); }
|
||||||
|
|
||||||
@@ -19224,11 +19192,6 @@ inline bool set_sni(session_t session, const char *hostname) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool set_hostname(session_t session, const char *hostname) {
|
|
||||||
// In Mbed TLS, set_hostname also sets up hostname verification
|
|
||||||
return set_sni(session, hostname);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline TlsError connect(session_t session) {
|
inline TlsError connect(session_t session) {
|
||||||
TlsError err;
|
TlsError err;
|
||||||
if (!session) {
|
if (!session) {
|
||||||
@@ -20381,11 +20344,6 @@ inline bool set_sni(session_t session, const char *hostname) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool set_hostname(session_t session, const char *hostname) {
|
|
||||||
// In wolfSSL, set_hostname also sets up hostname verification
|
|
||||||
return set_sni(session, hostname);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline TlsError connect(session_t session) {
|
inline TlsError connect(session_t session) {
|
||||||
TlsError err;
|
TlsError err;
|
||||||
if (!session) {
|
if (!session) {
|
||||||
|
|||||||
@@ -21030,6 +21030,94 @@ TEST(WebSocketSSLVerifyTest, TrustedChainWrongIdentityFails) {
|
|||||||
|
|
||||||
ASSERT_FALSE(client.connect());
|
ASSERT_FALSE(client.connect());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The tests above all connect to an IP literal, for which RFC 6066 forbids
|
||||||
|
// SNI, so nothing binds the host name to the TLS session. These bind the
|
||||||
|
// server to "localhost" instead, the only shape that exercises the SNI and
|
||||||
|
// hostname-verification path with certificate verification left enabled.
|
||||||
|
class WebSocketSSLDnsHostTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
void Start(const char *cert_file) {
|
||||||
|
server_ = httplib::detail::make_unique<SSLServer>(cert_file,
|
||||||
|
SERVER_PRIVATE_KEY_FILE);
|
||||||
|
ASSERT_TRUE(server_->is_valid());
|
||||||
|
server_->WebSocket("/echo", [](const Request &, ws::WebSocket &ws) {
|
||||||
|
std::string msg;
|
||||||
|
while (ws.read(msg)) {
|
||||||
|
ws.send(msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
port_ = server_->bind_to_any_port("localhost");
|
||||||
|
server_thread_ = std::thread([this]() { server_->listen_after_bind(); });
|
||||||
|
server_->wait_until_ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override {
|
||||||
|
if (server_) { server_->stop(); }
|
||||||
|
if (server_thread_.joinable()) { server_thread_.join(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string url() const {
|
||||||
|
return "wss://localhost:" + std::to_string(port_) + "/echo";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<SSLServer> server_;
|
||||||
|
std::thread server_thread_;
|
||||||
|
int port_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// cert2 carries a DNS:localhost SAN, so both the chain and the identity check
|
||||||
|
// out and the connection is usable
|
||||||
|
TEST_F(WebSocketSSLDnsHostTest, TrustedChainMatchingNameVerifies) {
|
||||||
|
Start(SERVER_CERT2_FILE);
|
||||||
|
|
||||||
|
ws::WebSocketClient client(url());
|
||||||
|
client.set_ca_cert_path(SERVER_CERT2_FILE);
|
||||||
|
|
||||||
|
ASSERT_TRUE(client.connect());
|
||||||
|
ASSERT_TRUE(client.send("hello"));
|
||||||
|
std::string msg;
|
||||||
|
EXPECT_EQ(ws::Text, client.read(msg));
|
||||||
|
EXPECT_EQ("hello", msg);
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// cert.pem has a CN but no SAN, so trusting it as a CA satisfies the chain
|
||||||
|
// while the identity check must still reject "localhost"
|
||||||
|
TEST_F(WebSocketSSLDnsHostTest, TrustedChainWrongNameFails) {
|
||||||
|
Start(SERVER_CERT_FILE);
|
||||||
|
|
||||||
|
ws::WebSocketClient client(url());
|
||||||
|
client.set_ca_cert_path(SERVER_CERT_FILE);
|
||||||
|
|
||||||
|
ASSERT_FALSE(client.connect());
|
||||||
|
}
|
||||||
|
|
||||||
|
// A CA that did not sign the server certificate fails the chain, even though
|
||||||
|
// the name would match
|
||||||
|
TEST_F(WebSocketSSLDnsHostTest, UntrustedChainFails) {
|
||||||
|
Start(SERVER_CERT2_FILE);
|
||||||
|
|
||||||
|
ws::WebSocketClient client(url());
|
||||||
|
client.set_ca_cert_path(CLIENT_CA_CERT_FILE);
|
||||||
|
|
||||||
|
ASSERT_FALSE(client.connect());
|
||||||
|
}
|
||||||
|
|
||||||
|
// With verification disabled, neither the chain nor the name is checked
|
||||||
|
TEST_F(WebSocketSSLDnsHostTest, VerificationDisabledAcceptsAnyName) {
|
||||||
|
Start(SERVER_CERT_FILE);
|
||||||
|
|
||||||
|
ws::WebSocketClient client(url());
|
||||||
|
client.enable_server_certificate_verification(false);
|
||||||
|
|
||||||
|
ASSERT_TRUE(client.connect());
|
||||||
|
ASSERT_TRUE(client.send("hello"));
|
||||||
|
std::string msg;
|
||||||
|
EXPECT_EQ(ws::Text, client.read(msg));
|
||||||
|
EXPECT_EQ("hello", msg);
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if !defined(_WIN32)
|
#if !defined(_WIN32)
|
||||||
|
|||||||
+1
-1
@@ -18,4 +18,4 @@ emailAddress = test@email.address
|
|||||||
challengePassword = 1234
|
challengePassword = 1234
|
||||||
|
|
||||||
[SAN]
|
[SAN]
|
||||||
subjectAltName=IP:127.0.0.1
|
subjectAltName=IP:127.0.0.1,DNS:localhost
|
||||||
Reference in new issue
Block a user