1
0
mirror of synced 2025-07-17 17:40:57 +03:00
* Fix #1416

* Update

* Update
This commit is contained in:
yhirose
2025-07-05 15:17:53 -04:00
committed by GitHub
parent 120405beac
commit cb85e573de
3 changed files with 178 additions and 16 deletions

View File

@ -85,6 +85,49 @@ cli.enable_server_hostname_verification(false);
> [!NOTE]
> When using SSL, it seems impossible to avoid SIGPIPE in all cases, since on some operating systems, SIGPIPE can only be suppressed on a per-message basis, but there is no way to make the OpenSSL library do so for its internal communications. If your program needs to avoid being terminated on SIGPIPE, the only fully general way might be to set up a signal handler for SIGPIPE to handle or ignore it yourself.
### SSL Error Handling
When SSL operations fail, cpp-httplib provides detailed error information through two separate error fields:
```c++
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include "path/to/httplib.h"
httplib::Client cli("https://example.com");
auto res = cli.Get("/");
if (!res) {
// Check the error type
auto err = res.error();
switch (err) {
case httplib::Error::SSLConnection:
std::cout << "SSL connection failed, SSL error: "
<< res->ssl_error() << std::endl;
break;
case httplib::Error::SSLLoadingCerts:
std::cout << "SSL cert loading failed, OpenSSL error: "
<< std::hex << res->ssl_openssl_error() << std::endl;
break;
case httplib::Error::SSLServerVerification:
std::cout << "SSL verification failed, X509 error: "
<< res->ssl_openssl_error() << std::endl;
break;
case httplib::Error::SSLServerHostnameVerification:
std::cout << "SSL hostname verification failed, X509 error: "
<< res->ssl_openssl_error() << std::endl;
break;
default:
std::cout << "HTTP error: " << httplib::to_string(err) << std::endl;
}
}
}
```
Server
------

View File

@ -1246,6 +1246,17 @@ public:
Headers &&request_headers = Headers{})
: res_(std::move(res)), err_(err),
request_headers_(std::move(request_headers)) {}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
Result(std::unique_ptr<Response> &&res, Error err, Headers &&request_headers,
int ssl_error)
: res_(std::move(res)), err_(err),
request_headers_(std::move(request_headers)), ssl_error_(ssl_error) {}
Result(std::unique_ptr<Response> &&res, Error err, Headers &&request_headers,
int ssl_error, unsigned long ssl_openssl_error)
: res_(std::move(res)), err_(err),
request_headers_(std::move(request_headers)), ssl_error_(ssl_error),
ssl_openssl_error_(ssl_openssl_error) {}
#endif
// Response
operator bool() const { return res_ != nullptr; }
bool operator==(std::nullptr_t) const { return res_ == nullptr; }
@ -1260,6 +1271,13 @@ public:
// Error
Error error() const { return err_; }
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
// SSL Error
int ssl_error() const { return ssl_error_; }
// OpenSSL Error
unsigned long ssl_openssl_error() const { return ssl_openssl_error_; }
#endif
// Request Headers
bool has_request_header(const std::string &key) const;
std::string get_request_header_value(const std::string &key,
@ -1273,6 +1291,10 @@ private:
std::unique_ptr<Response> res_;
Error err_ = Error::Unknown;
Headers request_headers_;
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
int ssl_error_ = 0;
unsigned long ssl_openssl_error_ = 0;
#endif
};
class ClientImpl {
@ -1570,6 +1592,11 @@ protected:
Logger logger_;
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
int last_ssl_error_ = 0;
unsigned long last_openssl_error_ = 0;
#endif
private:
bool send_(Request &req, Response &res, Error &error);
Result send_(Request &&req);
@ -1840,6 +1867,9 @@ private:
SSL_CTX *ctx_;
std::mutex ctx_mutex_;
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
int last_ssl_error_ = 0;
#endif
};
class SSLClient final : public ClientImpl {
@ -8173,7 +8203,12 @@ inline Result ClientImpl::send_(Request &&req) {
auto res = detail::make_unique<Response>();
auto error = Error::Success;
auto ret = send(req, *res, error);
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers),
last_ssl_error_, last_openssl_error_};
#else
return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)};
#endif
}
inline bool ClientImpl::handle_request(Stream &strm, Request &req,
@ -8723,7 +8758,12 @@ inline Result ClientImpl::send_with_content_provider(
req, body, content_length, std::move(content_provider),
std::move(content_provider_without_length), content_type, error);
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
return Result{std::move(res), error, std::move(req.headers), last_ssl_error_,
last_openssl_error_};
#else
return Result{std::move(res), error, std::move(req.headers)};
#endif
}
inline std::string
@ -9790,8 +9830,8 @@ inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, socket_t sock,
template <typename U>
bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl,
U ssl_connect_or_accept,
time_t timeout_sec,
time_t timeout_usec) {
time_t timeout_sec, time_t timeout_usec,
int *ssl_error) {
auto res = 0;
while ((res = ssl_connect_or_accept(ssl)) != 1) {
auto err = SSL_get_error(ssl, res);
@ -9804,6 +9844,7 @@ bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl,
break;
default: break;
}
if (ssl_error) { *ssl_error = err; }
return false;
}
return true;
@ -9897,9 +9938,10 @@ inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
if (ret >= 0) { return ret; }
err = SSL_get_error(ssl_, ret);
} else {
return -1;
break;
}
}
assert(ret < 0);
}
return ret;
} else {
@ -9929,9 +9971,10 @@ inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) {
if (ret >= 0) { return ret; }
err = SSL_get_error(ssl_, ret);
} else {
return -1;
break;
}
}
assert(ret < 0);
}
return ret;
}
@ -9982,6 +10025,7 @@ inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) !=
1 ||
SSL_CTX_check_private_key(ctx_) != 1) {
last_ssl_error_ = static_cast<int>(ERR_get_error());
SSL_CTX_free(ctx_);
ctx_ = nullptr;
} else if (client_ca_cert_file_path || client_ca_cert_dir_path) {
@ -10055,7 +10099,8 @@ inline bool SSLServer::process_and_close_socket(socket_t sock) {
sock, ctx_, ctx_mutex_,
[&](SSL *ssl2) {
return detail::ssl_connect_or_accept_nonblocking(
sock, ssl2, SSL_accept, read_timeout_sec_, read_timeout_usec_);
sock, ssl2, SSL_accept, read_timeout_sec_, read_timeout_usec_,
&last_ssl_error_);
},
[](SSL * /*ssl2*/) { return true; });
@ -10123,6 +10168,7 @@ inline SSLClient::SSLClient(const std::string &host, int port,
SSL_FILETYPE_PEM) != 1 ||
SSL_CTX_use_PrivateKey_file(ctx_, client_key_path.c_str(),
SSL_FILETYPE_PEM) != 1) {
last_openssl_error_ = ERR_get_error();
SSL_CTX_free(ctx_);
ctx_ = nullptr;
}
@ -10149,6 +10195,7 @@ inline SSLClient::SSLClient(const std::string &host, int port,
if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 ||
SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) {
last_openssl_error_ = ERR_get_error();
SSL_CTX_free(ctx_);
ctx_ = nullptr;
}
@ -10292,11 +10339,13 @@ inline bool SSLClient::load_certs() {
if (!ca_cert_file_path_.empty()) {
if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(),
nullptr)) {
last_openssl_error_ = ERR_get_error();
ret = false;
}
} else if (!ca_cert_dir_path_.empty()) {
if (!SSL_CTX_load_verify_locations(ctx_, nullptr,
ca_cert_dir_path_.c_str())) {
last_openssl_error_ = ERR_get_error();
ret = false;
}
} else {
@ -10329,7 +10378,7 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
if (!detail::ssl_connect_or_accept_nonblocking(
socket.sock, ssl2, SSL_connect, connection_timeout_sec_,
connection_timeout_usec_)) {
connection_timeout_usec_, &last_ssl_error_)) {
error = Error::SSLConnection;
return false;
}
@ -10342,6 +10391,7 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
}
if (verification_status == SSLVerifierResponse::CertificateRejected) {
last_openssl_error_ = ERR_get_error();
error = Error::SSLServerVerification;
return false;
}
@ -10350,6 +10400,7 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
verify_result_ = SSL_get_verify_result(ssl2);
if (verify_result_ != X509_V_OK) {
last_openssl_error_ = static_cast<unsigned long>(verify_result_);
error = Error::SSLServerVerification;
return false;
}
@ -10358,12 +10409,14 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
auto se = detail::scope_exit([&] { X509_free(server_cert); });
if (server_cert == nullptr) {
last_openssl_error_ = ERR_get_error();
error = Error::SSLServerVerification;
return false;
}
if (server_hostname_verification_) {
if (!verify_host(server_cert)) {
last_openssl_error_ = X509_V_ERR_HOSTNAME_MISMATCH;
error = Error::SSLServerHostnameVerification;
return false;
}

View File

@ -7492,6 +7492,47 @@ TEST(SSLClientTest, ServerNameIndication_Online) {
ASSERT_EQ(StatusCode::OK_200, res->status);
}
TEST(SSLClientTest, ServerCertificateVerificationError_Online) {
// Use a site that will cause SSL verification failure due to self-signed cert
SSLClient cli("self-signed.badssl.com", 443);
cli.enable_server_certificate_verification(true);
auto res = cli.Get("/");
ASSERT_TRUE(!res);
EXPECT_EQ(Error::SSLServerVerification, res.error());
// For SSL server verification errors, ssl_error should be 0, only
// ssl_openssl_error should be set
EXPECT_EQ(0, res.ssl_error());
// Verify OpenSSL error is captured for SSLServerVerification
// This occurs when SSL_get_verify_result() returns a verification failure
EXPECT_EQ(static_cast<unsigned long>(X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT),
res.ssl_openssl_error());
}
TEST(SSLClientTest, ServerHostnameVerificationError_Online) {
// Use a site where hostname doesn't match the certificate
// badssl.com provides wrong.host.badssl.com which has cert for *.badssl.com
SSLClient cli("wrong.host.badssl.com", 443);
cli.enable_server_certificate_verification(true);
cli.enable_server_hostname_verification(true);
auto res = cli.Get("/");
ASSERT_TRUE(!res);
EXPECT_EQ(Error::SSLServerHostnameVerification, res.error());
// For SSL hostname verification errors, ssl_error should be 0, only
// ssl_openssl_error should be set
EXPECT_EQ(0, res.ssl_error());
// Verify OpenSSL error is captured for SSLServerHostnameVerification
// This occurs when verify_host() fails due to hostname mismatch
EXPECT_EQ(static_cast<unsigned long>(X509_V_ERR_HOSTNAME_MISMATCH),
res.ssl_openssl_error());
}
TEST(SSLClientTest, ServerCertificateVerification1_Online) {
Client cli("https://google.com");
auto res = cli.Get("/");
@ -7500,15 +7541,6 @@ TEST(SSLClientTest, ServerCertificateVerification1_Online) {
}
TEST(SSLClientTest, ServerCertificateVerification2_Online) {
SSLClient cli("google.com");
cli.enable_server_certificate_verification(true);
cli.set_ca_cert_path("hello");
auto res = cli.Get("/");
ASSERT_TRUE(!res);
EXPECT_EQ(Error::SSLLoadingCerts, res.error());
}
TEST(SSLClientTest, ServerCertificateVerification3_Online) {
SSLClient cli("google.com");
cli.set_ca_cert_path(CA_CERT_FILE);
auto res = cli.Get("/");
@ -7516,6 +7548,29 @@ TEST(SSLClientTest, ServerCertificateVerification3_Online) {
ASSERT_EQ(StatusCode::MovedPermanently_301, res->status);
}
TEST(SSLClientTest, ServerCertificateVerification3_Online) {
SSLClient cli("google.com");
cli.enable_server_certificate_verification(true);
cli.set_ca_cert_path("hello");
auto res = cli.Get("/");
ASSERT_TRUE(!res);
EXPECT_EQ(Error::SSLLoadingCerts, res.error());
// For SSL_CTX operations, ssl_error should be 0, only ssl_openssl_error
// should be set
EXPECT_EQ(0, res.ssl_error());
// Verify OpenSSL error is captured for SSLLoadingCerts
// This error occurs when SSL_CTX_load_verify_locations() fails
// > openssl errstr 0x80000002
// error:80000002:system library::No such file or directory
// > openssl errstr 0xA000126
// error:0A000126:SSL routines::unexpected eof while reading
EXPECT_TRUE(res.ssl_openssl_error() == 0x80000002 ||
res.ssl_openssl_error() == 0xA000126);
}
TEST(SSLClientTest, ServerCertificateVerification4) {
SSLServer svr(SERVER_CERT2_FILE, SERVER_PRIVATE_KEY_FILE);
ASSERT_TRUE(svr.is_valid());
@ -7790,10 +7845,20 @@ TEST(SSLClientServerTest, ClientCertMissing) {
svr.wait_until_ready();
SSLClient cli(HOST, PORT);
auto res = cli.Get("/test");
cli.set_connection_timeout(30);
auto res = cli.Get("/test");
ASSERT_TRUE(!res);
EXPECT_EQ(Error::SSLServerVerification, res.error());
// For SSL server verification errors, ssl_error should be 0, only
// ssl_openssl_error should be set
EXPECT_EQ(0, res.ssl_error());
// Verify OpenSSL error is captured for SSLServerVerification
// Note: This test may have different error codes depending on the exact
// verification failure
EXPECT_NE(0UL, res.ssl_openssl_error());
}
TEST(SSLClientServerTest, TrustDirOptional) {
@ -7868,6 +7933,7 @@ TEST(SSLClientServerTest, SSLConnectTimeout) {
auto res = cli.Get("/test");
ASSERT_TRUE(!res);
EXPECT_EQ(Error::SSLConnection, res.error());
EXPECT_EQ(SSL_ERROR_WANT_READ, res.ssl_error());
}
TEST(SSLClientServerTest, CustomizeServerSSLCtx) {