Skip to content

Commit cb85e57

Browse files
authored
Fix #1416 (#2169)
* Fix #1416 * Update * Update
1 parent 120405b commit cb85e57

File tree

3 files changed

+176
-14
lines changed

3 files changed

+176
-14
lines changed

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,49 @@ cli.enable_server_hostname_verification(false);
8585
> [!NOTE]
8686
> 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.
8787
88+
### SSL Error Handling
89+
90+
When SSL operations fail, cpp-httplib provides detailed error information through two separate error fields:
91+
92+
```c++
93+
#define CPPHTTPLIB_OPENSSL_SUPPORT
94+
#include "path/to/httplib.h"
95+
96+
httplib::Client cli("https://example.com");
97+
98+
auto res = cli.Get("/");
99+
if (!res) {
100+
// Check the error type
101+
auto err = res.error();
102+
103+
switch (err) {
104+
case httplib::Error::SSLConnection:
105+
std::cout << "SSL connection failed, SSL error: "
106+
<< res->ssl_error() << std::endl;
107+
break;
108+
109+
case httplib::Error::SSLLoadingCerts:
110+
std::cout << "SSL cert loading failed, OpenSSL error: "
111+
<< std::hex << res->ssl_openssl_error() << std::endl;
112+
break;
113+
114+
case httplib::Error::SSLServerVerification:
115+
std::cout << "SSL verification failed, X509 error: "
116+
<< res->ssl_openssl_error() << std::endl;
117+
break;
118+
119+
case httplib::Error::SSLServerHostnameVerification:
120+
std::cout << "SSL hostname verification failed, X509 error: "
121+
<< res->ssl_openssl_error() << std::endl;
122+
break;
123+
124+
default:
125+
std::cout << "HTTP error: " << httplib::to_string(err) << std::endl;
126+
}
127+
}
128+
}
129+
```
130+
88131
Server
89132
------
90133

httplib.h

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1246,6 +1246,17 @@ class Result {
12461246
Headers &&request_headers = Headers{})
12471247
: res_(std::move(res)), err_(err),
12481248
request_headers_(std::move(request_headers)) {}
1249+
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1250+
Result(std::unique_ptr<Response> &&res, Error err, Headers &&request_headers,
1251+
int ssl_error)
1252+
: res_(std::move(res)), err_(err),
1253+
request_headers_(std::move(request_headers)), ssl_error_(ssl_error) {}
1254+
Result(std::unique_ptr<Response> &&res, Error err, Headers &&request_headers,
1255+
int ssl_error, unsigned long ssl_openssl_error)
1256+
: res_(std::move(res)), err_(err),
1257+
request_headers_(std::move(request_headers)), ssl_error_(ssl_error),
1258+
ssl_openssl_error_(ssl_openssl_error) {}
1259+
#endif
12491260
// Response
12501261
operator bool() const { return res_ != nullptr; }
12511262
bool operator==(std::nullptr_t) const { return res_ == nullptr; }
@@ -1260,6 +1271,13 @@ class Result {
12601271
// Error
12611272
Error error() const { return err_; }
12621273

1274+
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1275+
// SSL Error
1276+
int ssl_error() const { return ssl_error_; }
1277+
// OpenSSL Error
1278+
unsigned long ssl_openssl_error() const { return ssl_openssl_error_; }
1279+
#endif
1280+
12631281
// Request Headers
12641282
bool has_request_header(const std::string &key) const;
12651283
std::string get_request_header_value(const std::string &key,
@@ -1273,6 +1291,10 @@ class Result {
12731291
std::unique_ptr<Response> res_;
12741292
Error err_ = Error::Unknown;
12751293
Headers request_headers_;
1294+
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1295+
int ssl_error_ = 0;
1296+
unsigned long ssl_openssl_error_ = 0;
1297+
#endif
12761298
};
12771299

12781300
class ClientImpl {
@@ -1570,6 +1592,11 @@ class ClientImpl {
15701592

15711593
Logger logger_;
15721594

1595+
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1596+
int last_ssl_error_ = 0;
1597+
unsigned long last_openssl_error_ = 0;
1598+
#endif
1599+
15731600
private:
15741601
bool send_(Request &req, Response &res, Error &error);
15751602
Result send_(Request &&req);
@@ -1840,6 +1867,9 @@ class SSLServer : public Server {
18401867

18411868
SSL_CTX *ctx_;
18421869
std::mutex ctx_mutex_;
1870+
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1871+
int last_ssl_error_ = 0;
1872+
#endif
18431873
};
18441874

18451875
class SSLClient final : public ClientImpl {
@@ -8173,7 +8203,12 @@ inline Result ClientImpl::send_(Request &&req) {
81738203
auto res = detail::make_unique<Response>();
81748204
auto error = Error::Success;
81758205
auto ret = send(req, *res, error);
8206+
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
8207+
return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers),
8208+
last_ssl_error_, last_openssl_error_};
8209+
#else
81768210
return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)};
8211+
#endif
81778212
}
81788213

81798214
inline bool ClientImpl::handle_request(Stream &strm, Request &req,
@@ -8723,7 +8758,12 @@ inline Result ClientImpl::send_with_content_provider(
87238758
req, body, content_length, std::move(content_provider),
87248759
std::move(content_provider_without_length), content_type, error);
87258760

8761+
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
8762+
return Result{std::move(res), error, std::move(req.headers), last_ssl_error_,
8763+
last_openssl_error_};
8764+
#else
87268765
return Result{std::move(res), error, std::move(req.headers)};
8766+
#endif
87278767
}
87288768

87298769
inline std::string
@@ -9790,8 +9830,8 @@ inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, socket_t sock,
97909830
template <typename U>
97919831
bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl,
97929832
U ssl_connect_or_accept,
9793-
time_t timeout_sec,
9794-
time_t timeout_usec) {
9833+
time_t timeout_sec, time_t timeout_usec,
9834+
int *ssl_error) {
97959835
auto res = 0;
97969836
while ((res = ssl_connect_or_accept(ssl)) != 1) {
97979837
auto err = SSL_get_error(ssl, res);
@@ -9804,6 +9844,7 @@ bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl,
98049844
break;
98059845
default: break;
98069846
}
9847+
if (ssl_error) { *ssl_error = err; }
98079848
return false;
98089849
}
98099850
return true;
@@ -9897,9 +9938,10 @@ inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
98979938
if (ret >= 0) { return ret; }
98989939
err = SSL_get_error(ssl_, ret);
98999940
} else {
9900-
return -1;
9941+
break;
99019942
}
99029943
}
9944+
assert(ret < 0);
99039945
}
99049946
return ret;
99059947
} else {
@@ -9929,9 +9971,10 @@ inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) {
99299971
if (ret >= 0) { return ret; }
99309972
err = SSL_get_error(ssl_, ret);
99319973
} else {
9932-
return -1;
9974+
break;
99339975
}
99349976
}
9977+
assert(ret < 0);
99359978
}
99369979
return ret;
99379980
}
@@ -9982,6 +10025,7 @@ inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
998210025
SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) !=
998310026
1 ||
998410027
SSL_CTX_check_private_key(ctx_) != 1) {
10028+
last_ssl_error_ = static_cast<int>(ERR_get_error());
998510029
SSL_CTX_free(ctx_);
998610030
ctx_ = nullptr;
998710031
} 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) {
1005510099
sock, ctx_, ctx_mutex_,
1005610100
[&](SSL *ssl2) {
1005710101
return detail::ssl_connect_or_accept_nonblocking(
10058-
sock, ssl2, SSL_accept, read_timeout_sec_, read_timeout_usec_);
10102+
sock, ssl2, SSL_accept, read_timeout_sec_, read_timeout_usec_,
10103+
&last_ssl_error_);
1005910104
},
1006010105
[](SSL * /*ssl2*/) { return true; });
1006110106

@@ -10123,6 +10168,7 @@ inline SSLClient::SSLClient(const std::string &host, int port,
1012310168
SSL_FILETYPE_PEM) != 1 ||
1012410169
SSL_CTX_use_PrivateKey_file(ctx_, client_key_path.c_str(),
1012510170
SSL_FILETYPE_PEM) != 1) {
10171+
last_openssl_error_ = ERR_get_error();
1012610172
SSL_CTX_free(ctx_);
1012710173
ctx_ = nullptr;
1012810174
}
@@ -10149,6 +10195,7 @@ inline SSLClient::SSLClient(const std::string &host, int port,
1014910195

1015010196
if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 ||
1015110197
SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) {
10198+
last_openssl_error_ = ERR_get_error();
1015210199
SSL_CTX_free(ctx_);
1015310200
ctx_ = nullptr;
1015410201
}
@@ -10292,11 +10339,13 @@ inline bool SSLClient::load_certs() {
1029210339
if (!ca_cert_file_path_.empty()) {
1029310340
if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(),
1029410341
nullptr)) {
10342+
last_openssl_error_ = ERR_get_error();
1029510343
ret = false;
1029610344
}
1029710345
} else if (!ca_cert_dir_path_.empty()) {
1029810346
if (!SSL_CTX_load_verify_locations(ctx_, nullptr,
1029910347
ca_cert_dir_path_.c_str())) {
10348+
last_openssl_error_ = ERR_get_error();
1030010349
ret = false;
1030110350
}
1030210351
} else {
@@ -10329,7 +10378,7 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
1032910378

1033010379
if (!detail::ssl_connect_or_accept_nonblocking(
1033110380
socket.sock, ssl2, SSL_connect, connection_timeout_sec_,
10332-
connection_timeout_usec_)) {
10381+
connection_timeout_usec_, &last_ssl_error_)) {
1033310382
error = Error::SSLConnection;
1033410383
return false;
1033510384
}
@@ -10342,6 +10391,7 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
1034210391
}
1034310392

1034410393
if (verification_status == SSLVerifierResponse::CertificateRejected) {
10394+
last_openssl_error_ = ERR_get_error();
1034510395
error = Error::SSLServerVerification;
1034610396
return false;
1034710397
}
@@ -10350,6 +10400,7 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
1035010400
verify_result_ = SSL_get_verify_result(ssl2);
1035110401

1035210402
if (verify_result_ != X509_V_OK) {
10403+
last_openssl_error_ = static_cast<unsigned long>(verify_result_);
1035310404
error = Error::SSLServerVerification;
1035410405
return false;
1035510406
}
@@ -10358,12 +10409,14 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
1035810409
auto se = detail::scope_exit([&] { X509_free(server_cert); });
1035910410

1036010411
if (server_cert == nullptr) {
10412+
last_openssl_error_ = ERR_get_error();
1036110413
error = Error::SSLServerVerification;
1036210414
return false;
1036310415
}
1036410416

1036510417
if (server_hostname_verification_) {
1036610418
if (!verify_host(server_cert)) {
10419+
last_openssl_error_ = X509_V_ERR_HOSTNAME_MISMATCH;
1036710420
error = Error::SSLServerHostnameVerification;
1036810421
return false;
1036910422
}

test/test.cc

Lines changed: 74 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7492,6 +7492,47 @@ TEST(SSLClientTest, ServerNameIndication_Online) {
74927492
ASSERT_EQ(StatusCode::OK_200, res->status);
74937493
}
74947494

7495+
TEST(SSLClientTest, ServerCertificateVerificationError_Online) {
7496+
// Use a site that will cause SSL verification failure due to self-signed cert
7497+
SSLClient cli("self-signed.badssl.com", 443);
7498+
cli.enable_server_certificate_verification(true);
7499+
auto res = cli.Get("/");
7500+
7501+
ASSERT_TRUE(!res);
7502+
EXPECT_EQ(Error::SSLServerVerification, res.error());
7503+
7504+
// For SSL server verification errors, ssl_error should be 0, only
7505+
// ssl_openssl_error should be set
7506+
EXPECT_EQ(0, res.ssl_error());
7507+
7508+
// Verify OpenSSL error is captured for SSLServerVerification
7509+
// This occurs when SSL_get_verify_result() returns a verification failure
7510+
EXPECT_EQ(static_cast<unsigned long>(X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT),
7511+
res.ssl_openssl_error());
7512+
}
7513+
7514+
TEST(SSLClientTest, ServerHostnameVerificationError_Online) {
7515+
// Use a site where hostname doesn't match the certificate
7516+
// badssl.com provides wrong.host.badssl.com which has cert for *.badssl.com
7517+
SSLClient cli("wrong.host.badssl.com", 443);
7518+
cli.enable_server_certificate_verification(true);
7519+
cli.enable_server_hostname_verification(true);
7520+
7521+
auto res = cli.Get("/");
7522+
ASSERT_TRUE(!res);
7523+
7524+
EXPECT_EQ(Error::SSLServerHostnameVerification, res.error());
7525+
7526+
// For SSL hostname verification errors, ssl_error should be 0, only
7527+
// ssl_openssl_error should be set
7528+
EXPECT_EQ(0, res.ssl_error());
7529+
7530+
// Verify OpenSSL error is captured for SSLServerHostnameVerification
7531+
// This occurs when verify_host() fails due to hostname mismatch
7532+
EXPECT_EQ(static_cast<unsigned long>(X509_V_ERR_HOSTNAME_MISMATCH),
7533+
res.ssl_openssl_error());
7534+
}
7535+
74957536
TEST(SSLClientTest, ServerCertificateVerification1_Online) {
74967537
Client cli("https://google.com");
74977538
auto res = cli.Get("/");
@@ -7501,19 +7542,33 @@ TEST(SSLClientTest, ServerCertificateVerification1_Online) {
75017542

75027543
TEST(SSLClientTest, ServerCertificateVerification2_Online) {
75037544
SSLClient cli("google.com");
7504-
cli.enable_server_certificate_verification(true);
7505-
cli.set_ca_cert_path("hello");
7545+
cli.set_ca_cert_path(CA_CERT_FILE);
75067546
auto res = cli.Get("/");
7507-
ASSERT_TRUE(!res);
7508-
EXPECT_EQ(Error::SSLLoadingCerts, res.error());
7547+
ASSERT_TRUE(res);
7548+
ASSERT_EQ(StatusCode::MovedPermanently_301, res->status);
75097549
}
75107550

75117551
TEST(SSLClientTest, ServerCertificateVerification3_Online) {
75127552
SSLClient cli("google.com");
7513-
cli.set_ca_cert_path(CA_CERT_FILE);
7553+
cli.enable_server_certificate_verification(true);
7554+
cli.set_ca_cert_path("hello");
7555+
75147556
auto res = cli.Get("/");
7515-
ASSERT_TRUE(res);
7516-
ASSERT_EQ(StatusCode::MovedPermanently_301, res->status);
7557+
ASSERT_TRUE(!res);
7558+
EXPECT_EQ(Error::SSLLoadingCerts, res.error());
7559+
7560+
// For SSL_CTX operations, ssl_error should be 0, only ssl_openssl_error
7561+
// should be set
7562+
EXPECT_EQ(0, res.ssl_error());
7563+
7564+
// Verify OpenSSL error is captured for SSLLoadingCerts
7565+
// This error occurs when SSL_CTX_load_verify_locations() fails
7566+
// > openssl errstr 0x80000002
7567+
// error:80000002:system library::No such file or directory
7568+
// > openssl errstr 0xA000126
7569+
// error:0A000126:SSL routines::unexpected eof while reading
7570+
EXPECT_TRUE(res.ssl_openssl_error() == 0x80000002 ||
7571+
res.ssl_openssl_error() == 0xA000126);
75177572
}
75187573

75197574
TEST(SSLClientTest, ServerCertificateVerification4) {
@@ -7790,10 +7845,20 @@ TEST(SSLClientServerTest, ClientCertMissing) {
77907845
svr.wait_until_ready();
77917846

77927847
SSLClient cli(HOST, PORT);
7793-
auto res = cli.Get("/test");
77947848
cli.set_connection_timeout(30);
7849+
7850+
auto res = cli.Get("/test");
77957851
ASSERT_TRUE(!res);
77967852
EXPECT_EQ(Error::SSLServerVerification, res.error());
7853+
7854+
// For SSL server verification errors, ssl_error should be 0, only
7855+
// ssl_openssl_error should be set
7856+
EXPECT_EQ(0, res.ssl_error());
7857+
7858+
// Verify OpenSSL error is captured for SSLServerVerification
7859+
// Note: This test may have different error codes depending on the exact
7860+
// verification failure
7861+
EXPECT_NE(0UL, res.ssl_openssl_error());
77977862
}
77987863

77997864
TEST(SSLClientServerTest, TrustDirOptional) {
@@ -7868,6 +7933,7 @@ TEST(SSLClientServerTest, SSLConnectTimeout) {
78687933
auto res = cli.Get("/test");
78697934
ASSERT_TRUE(!res);
78707935
EXPECT_EQ(Error::SSLConnection, res.error());
7936+
EXPECT_EQ(SSL_ERROR_WANT_READ, res.ssl_error());
78717937
}
78727938

78737939
TEST(SSLClientServerTest, CustomizeServerSSLCtx) {

0 commit comments

Comments
 (0)