mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-19 11:19:36 +00:00
Keep a client's limits and TLS checks across redirects
A cross-host redirect is followed by a new client, which did not get the payload limit, the max timeout, the connection timeout's microseconds or the hostname address map. Over HTTPS it also lost the session verifier, the server certificate verifier and a store given to set_ca_cert_store(), and fell back to the default CAs. Copy them over. The store belongs to the original TLS context, so pass on the CA certificates it holds as PEM. After the redirect, the original client also answered a 401 or 407 from the new host with its own digest credentials and sent that to its own host, with the new host's path. Return the redirected response as it is, since the client that followed it has already answered what it could.
This commit is contained in:
1 parent
344311c1c3
commit
2b66aa65e1
2 files changed
+245
-94
No files matched your search
@@ -3455,11 +3455,12 @@ private:
|
|||||||
|
|
||||||
std::function<SSLVerifierResponse(tls::session_t)> session_verifier_;
|
std::function<SSLVerifierResponse(tls::session_t)> session_verifier_;
|
||||||
|
|
||||||
|
// Like ca_cert_store_set_, keeps what ctx_ cannot report back: the verifier
|
||||||
|
// set_server_certificate_verifier() installed there.
|
||||||
|
tls::VerifyCallback server_certificate_verifier_;
|
||||||
|
|
||||||
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
|
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
|
||||||
bool enable_windows_cert_verification_ = true;
|
bool enable_windows_cert_verification_ = true;
|
||||||
// Like ca_cert_store_set_, tracks what ctx_ cannot report back: whether
|
|
||||||
// set_server_certificate_verifier() installed a verifier.
|
|
||||||
bool server_certificate_verifier_set_ = false;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
friend class ClientImpl;
|
friend class ClientImpl;
|
||||||
@@ -11026,6 +11027,26 @@ inline bool load_client_ca_config(tls::ctx_t ctx,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline std::string ca_certs_to_pem(tls::ctx_t ctx) {
|
||||||
|
std::vector<tls::cert_t> certs;
|
||||||
|
tls::get_ca_certs(ctx, certs);
|
||||||
|
|
||||||
|
std::string pem;
|
||||||
|
for (auto cert : certs) {
|
||||||
|
std::vector<unsigned char> der;
|
||||||
|
if (tls::get_cert_der(cert, der)) {
|
||||||
|
auto b64 = base64_encode(std::string(der.begin(), der.end()));
|
||||||
|
pem += "-----BEGIN CERTIFICATE-----\n";
|
||||||
|
for (size_t i = 0; i < b64.size(); i += 64) {
|
||||||
|
pem += b64.substr(i, 64) + "\n";
|
||||||
|
}
|
||||||
|
pem += "-----END CERTIFICATE-----\n";
|
||||||
|
}
|
||||||
|
tls::free_cert(cert);
|
||||||
|
}
|
||||||
|
return pem;
|
||||||
|
}
|
||||||
|
|
||||||
// The parts of session setup that only SSLClient needs, plus the handful
|
// The parts of session setup that only SSLClient needs, plus the handful
|
||||||
// WebSocketClient also exposes; everything else takes the defaults, which is
|
// WebSocketClient also exposes; everything else takes the defaults, which is
|
||||||
// what keeps the two clients on one implementation.
|
// what keeps the two clients on one implementation.
|
||||||
@@ -15818,7 +15839,10 @@ inline bool ClientImpl::handle_request(Stream &strm, Request &req,
|
|||||||
|
|
||||||
if (300 < res.status && res.status < 400 && follow_location_) {
|
if (300 < res.status && res.status < 400 && follow_location_) {
|
||||||
req = std::move(req_save);
|
req = std::move(req_save);
|
||||||
ret = redirect(req, res, error);
|
// The client that followed the redirect has answered any challenge it
|
||||||
|
// could. Retrying below would sign another host's challenge and send it
|
||||||
|
// here.
|
||||||
|
return redirect(req, res, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CPPHTTPLIB_SSL_ENABLED
|
#ifdef CPPHTTPLIB_SSL_ENABLED
|
||||||
@@ -15951,12 +15975,27 @@ inline bool ClientImpl::create_redirect_client(
|
|||||||
redirect_client.system_ca_mode_ = system_ca_mode_;
|
redirect_client.system_ca_mode_ = system_ca_mode_;
|
||||||
|
|
||||||
// Transfer CA certificate to redirect client
|
// Transfer CA certificate to redirect client
|
||||||
if (!ca_cert_pem_.empty()) {
|
redirect_client.load_ca_cert_store(ca_cert_pem_.c_str(),
|
||||||
redirect_client.load_ca_cert_store(ca_cert_pem_.c_str(),
|
ca_cert_pem_.size());
|
||||||
ca_cert_pem_.size());
|
redirect_client.set_ca_cert_path(ca_cert_file_path_, ca_cert_dir_path_);
|
||||||
}
|
|
||||||
if (!ca_cert_file_path_.empty()) {
|
if (is_ssl()) {
|
||||||
redirect_client.set_ca_cert_path(ca_cert_file_path_, ca_cert_dir_path_);
|
auto &self = static_cast<SSLClient &>(*this);
|
||||||
|
// A store given to set_ca_cert_store() lives only in this client's
|
||||||
|
// context, so pass on the CA certificates the context holds.
|
||||||
|
if (self.ca_cert_store_set_) {
|
||||||
|
auto pem = detail::ca_certs_to_pem(self.ctx_);
|
||||||
|
redirect_client.load_ca_cert_store(pem.data(), pem.size());
|
||||||
|
}
|
||||||
|
redirect_client.set_session_verifier(self.session_verifier_);
|
||||||
|
if (self.server_certificate_verifier_) {
|
||||||
|
redirect_client.set_server_certificate_verifier(
|
||||||
|
self.server_certificate_verifier_);
|
||||||
|
}
|
||||||
|
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
|
||||||
|
redirect_client.enable_windows_cert_verification_ =
|
||||||
|
self.enable_windows_cert_verification_;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client certificates are set through constructor for SSLClient
|
// Client certificates are set through constructor for SSLClient
|
||||||
@@ -15989,9 +16028,14 @@ inline bool ClientImpl::create_redirect_client(
|
|||||||
template <typename ClientType>
|
template <typename ClientType>
|
||||||
inline void ClientImpl::setup_redirect_client(ClientType &client) {
|
inline void ClientImpl::setup_redirect_client(ClientType &client) {
|
||||||
// Copy basic settings first
|
// Copy basic settings first
|
||||||
client.set_connection_timeout(connection_timeout_sec_);
|
client.set_connection_timeout(connection_timeout_sec_,
|
||||||
|
connection_timeout_usec_);
|
||||||
client.set_read_timeout(read_timeout_sec_, read_timeout_usec_);
|
client.set_read_timeout(read_timeout_sec_, read_timeout_usec_);
|
||||||
client.set_write_timeout(write_timeout_sec_, write_timeout_usec_);
|
client.set_write_timeout(write_timeout_sec_, write_timeout_usec_);
|
||||||
|
client.set_max_timeout(max_timeout_msec_);
|
||||||
|
if (has_payload_max_length_) {
|
||||||
|
client.set_payload_max_length(payload_max_length_);
|
||||||
|
}
|
||||||
client.set_keep_alive(keep_alive_);
|
client.set_keep_alive(keep_alive_);
|
||||||
client.set_follow_location(
|
client.set_follow_location(
|
||||||
true); // Enable redirects to handle multi-step redirects
|
true); // Enable redirects to handle multi-step redirects
|
||||||
@@ -16029,6 +16073,7 @@ inline void ClientImpl::setup_redirect_client(ClientType &client) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Copy network and socket settings
|
// Copy network and socket settings
|
||||||
|
client.addr_map_ = addr_map_;
|
||||||
client.set_address_family(address_family_);
|
client.set_address_family(address_family_);
|
||||||
client.set_tcp_nodelay(tcp_nodelay_);
|
client.set_tcp_nodelay(tcp_nodelay_);
|
||||||
client.set_ipv6_v6only(ipv6_v6only_);
|
client.set_ipv6_v6only(ipv6_v6only_);
|
||||||
@@ -18764,10 +18809,8 @@ inline void SSLClient::set_ca_cert_store(tls::ca_store_t ca_cert_store) {
|
|||||||
inline void
|
inline void
|
||||||
SSLClient::set_server_certificate_verifier(tls::VerifyCallback verifier) {
|
SSLClient::set_server_certificate_verifier(tls::VerifyCallback verifier) {
|
||||||
if (!ctx_) { return; }
|
if (!ctx_) { return; }
|
||||||
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
|
server_certificate_verifier_ = verifier;
|
||||||
server_certificate_verifier_set_ = static_cast<bool>(verifier);
|
tls::set_verify_callback(ctx_, std::move(verifier));
|
||||||
#endif
|
|
||||||
tls::set_verify_callback(ctx_, verifier);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void SSLClient::set_session_verifier(
|
inline void SSLClient::set_session_verifier(
|
||||||
@@ -18830,7 +18873,8 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
|
|||||||
ca_cert_dir_path_.empty() && ca_cert_pem_.empty() && !ca_cert_store_set_;
|
ca_cert_dir_path_.empty() && ca_cert_pem_.empty() && !ca_cert_store_set_;
|
||||||
// Only a verifier set through set_server_certificate_verifier() is seen
|
// Only a verifier set through set_server_certificate_verifier() is seen
|
||||||
// here, not one installed with tls::set_verify_callback() directly.
|
// here, not one installed with tls::set_verify_callback() directly.
|
||||||
options.server_certificate_verifier_set = server_certificate_verifier_set_;
|
options.server_certificate_verifier_set =
|
||||||
|
static_cast<bool>(server_certificate_verifier_);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
tls::session_t session = nullptr;
|
tls::session_t session = nullptr;
|
||||||
|
|||||||
+185
-78
@@ -3801,51 +3801,59 @@ TEST(RedirectToDifferentPort, Redirect) {
|
|||||||
EXPECT_EQ("Hello World!", res->body);
|
EXPECT_EQ("Hello World!", res->body);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
// GET /redir on one server redirects to /target on another port, so the
|
||||||
TestDoNotForwardCredentialsOnRedirect(std::function<void(Client &)> set_auth) {
|
// client follows it with a new client.
|
||||||
Server svr1;
|
static Result
|
||||||
std::string captured_authorization;
|
GetThroughRedirectToDifferentPort(Server::Handler target,
|
||||||
svr1.Get("/target", [&](const Request &req, Response &res) {
|
const std::function<void(Client &)> &setup,
|
||||||
captured_authorization = req.get_header_value("Authorization");
|
const Headers &headers = Headers()) {
|
||||||
res.set_content("OK", "text/plain");
|
Server target_svr;
|
||||||
|
target_svr.Get("/target", target);
|
||||||
|
|
||||||
|
int target_port = 0;
|
||||||
|
auto target_thread = std::thread([&]() {
|
||||||
|
target_port = target_svr.bind_to_any_port(HOST);
|
||||||
|
target_svr.listen_after_bind();
|
||||||
});
|
});
|
||||||
|
|
||||||
int svr1_port = 0;
|
Server origin_svr;
|
||||||
auto thread1 = std::thread([&]() {
|
origin_svr.Get("/redir", [&](const Request &, Response &res) {
|
||||||
svr1_port = svr1.bind_to_any_port(HOST);
|
|
||||||
svr1.listen_after_bind();
|
|
||||||
});
|
|
||||||
|
|
||||||
Server svr2;
|
|
||||||
svr2.Get("/redir", [&](const Request & /*req*/, Response &res) {
|
|
||||||
res.set_redirect(
|
res.set_redirect(
|
||||||
"http://localhost:" + std::to_string(svr1_port) + "/target", 302);
|
"http://localhost:" + std::to_string(target_port) + "/target", 302);
|
||||||
});
|
});
|
||||||
|
|
||||||
int svr2_port = 0;
|
int origin_port = 0;
|
||||||
auto thread2 = std::thread([&]() {
|
auto origin_thread = std::thread([&]() {
|
||||||
svr2_port = svr2.bind_to_any_port(HOST);
|
origin_port = origin_svr.bind_to_any_port(HOST);
|
||||||
svr2.listen_after_bind();
|
origin_svr.listen_after_bind();
|
||||||
});
|
});
|
||||||
auto se = detail::scope_exit([&] {
|
auto se = detail::scope_exit([&] {
|
||||||
svr2.stop();
|
origin_svr.stop();
|
||||||
thread2.join();
|
origin_thread.join();
|
||||||
svr1.stop();
|
target_svr.stop();
|
||||||
thread1.join();
|
target_thread.join();
|
||||||
ASSERT_FALSE(svr2.is_running());
|
|
||||||
ASSERT_FALSE(svr1.is_running());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
svr1.wait_until_ready();
|
target_svr.wait_until_ready();
|
||||||
svr2.wait_until_ready();
|
origin_svr.wait_until_ready();
|
||||||
|
|
||||||
Client cli("localhost", svr2_port);
|
Client cli("localhost", origin_port);
|
||||||
cli.set_follow_location(true);
|
cli.set_follow_location(true);
|
||||||
set_auth(cli);
|
setup(cli);
|
||||||
|
return cli.Get("/redir", headers);
|
||||||
|
}
|
||||||
|
|
||||||
auto res = cli.Get("/redir");
|
static void
|
||||||
ASSERT_TRUE(res) << "Error: " << to_string(res.error());
|
TestDoNotForwardCredentialsOnRedirect(std::function<void(Client &)> set_auth) {
|
||||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
std::string captured_authorization;
|
||||||
|
auto result = GetThroughRedirectToDifferentPort(
|
||||||
|
[&](const Request &req, Response &res) {
|
||||||
|
captured_authorization = req.get_header_value("Authorization");
|
||||||
|
res.set_content("OK", "text/plain");
|
||||||
|
},
|
||||||
|
set_auth);
|
||||||
|
ASSERT_TRUE(result) << "Error: " << to_string(result.error());
|
||||||
|
EXPECT_EQ(StatusCode::OK_200, result->status);
|
||||||
// RFC 9110: credentials MUST NOT be forwarded to a different host
|
// RFC 9110: credentials MUST NOT be forwarded to a different host
|
||||||
EXPECT_TRUE(captured_authorization.empty());
|
EXPECT_TRUE(captured_authorization.empty());
|
||||||
}
|
}
|
||||||
@@ -3861,52 +3869,15 @@ TEST(RedirectToDifferentPort, DoNotForwardCredentialsBearerToken) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(RedirectToDifferentPort, DoNotForwardCookie) {
|
TEST(RedirectToDifferentPort, DoNotForwardCookie) {
|
||||||
Server svr1;
|
|
||||||
std::string captured_cookie;
|
std::string captured_cookie;
|
||||||
bool target_hit = false;
|
auto result = GetThroughRedirectToDifferentPort(
|
||||||
svr1.Get("/target", [&](const Request &req, Response &res) {
|
[&](const Request &req, Response &res) {
|
||||||
captured_cookie = req.get_header_value("Cookie");
|
captured_cookie = req.get_header_value("Cookie");
|
||||||
target_hit = true;
|
res.set_content("OK", "text/plain");
|
||||||
res.set_content("OK", "text/plain");
|
},
|
||||||
});
|
[](Client &) {}, {{"Cookie", "session_id=SECRET; auth=PRIVATE"}});
|
||||||
|
ASSERT_TRUE(result) << "Error: " << to_string(result.error());
|
||||||
int svr1_port = 0;
|
EXPECT_EQ(StatusCode::OK_200, result->status);
|
||||||
auto thread1 = std::thread([&]() {
|
|
||||||
svr1_port = svr1.bind_to_any_port(HOST);
|
|
||||||
svr1.listen_after_bind();
|
|
||||||
});
|
|
||||||
|
|
||||||
Server svr2;
|
|
||||||
svr2.Get("/redir", [&](const Request & /*req*/, Response &res) {
|
|
||||||
res.set_redirect(
|
|
||||||
"http://localhost:" + std::to_string(svr1_port) + "/target", 302);
|
|
||||||
});
|
|
||||||
|
|
||||||
int svr2_port = 0;
|
|
||||||
auto thread2 = std::thread([&]() {
|
|
||||||
svr2_port = svr2.bind_to_any_port(HOST);
|
|
||||||
svr2.listen_after_bind();
|
|
||||||
});
|
|
||||||
auto se = detail::scope_exit([&] {
|
|
||||||
svr2.stop();
|
|
||||||
thread2.join();
|
|
||||||
svr1.stop();
|
|
||||||
thread1.join();
|
|
||||||
ASSERT_FALSE(svr2.is_running());
|
|
||||||
ASSERT_FALSE(svr1.is_running());
|
|
||||||
});
|
|
||||||
|
|
||||||
svr1.wait_until_ready();
|
|
||||||
svr2.wait_until_ready();
|
|
||||||
|
|
||||||
Client cli("localhost", svr2_port);
|
|
||||||
cli.set_follow_location(true);
|
|
||||||
|
|
||||||
Headers headers = {{"Cookie", "session_id=SECRET; auth=PRIVATE"}};
|
|
||||||
auto res = cli.Get("/redir", headers);
|
|
||||||
ASSERT_TRUE(res) << "Error: " << to_string(res.error());
|
|
||||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
|
||||||
EXPECT_TRUE(target_hit);
|
|
||||||
// Cookie MUST NOT be forwarded to a different host (GHSA-22mf-w2v3-r2jv)
|
// Cookie MUST NOT be forwarded to a different host (GHSA-22mf-w2v3-r2jv)
|
||||||
EXPECT_TRUE(captured_cookie.empty());
|
EXPECT_TRUE(captured_cookie.empty());
|
||||||
}
|
}
|
||||||
@@ -3944,6 +3915,64 @@ TEST(RedirectToSamePort, ForwardCookie) {
|
|||||||
EXPECT_EQ("session_id=SECRET", captured_cookie);
|
EXPECT_EQ("session_id=SECRET", captured_cookie);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(RedirectToDifferentPort, ForwardDefaultHeadersButNotCredentials) {
|
||||||
|
std::string authorization;
|
||||||
|
std::string trace_id;
|
||||||
|
auto result = GetThroughRedirectToDifferentPort(
|
||||||
|
[&](const Request &req, Response &res) {
|
||||||
|
authorization = req.get_header_value("Authorization");
|
||||||
|
trace_id = req.get_header_value("X-Trace-Id");
|
||||||
|
res.set_content("OK", "text/plain");
|
||||||
|
},
|
||||||
|
[](Client &cli) {
|
||||||
|
cli.set_default_headers(
|
||||||
|
{{"Authorization", "Bearer SECRET"}, {"X-Trace-Id", "42"}});
|
||||||
|
});
|
||||||
|
ASSERT_TRUE(result) << "Error: " << to_string(result.error());
|
||||||
|
EXPECT_EQ(StatusCode::OK_200, result->status);
|
||||||
|
// Like a header given with the request, a default one goes along unless it
|
||||||
|
// carries credentials (RFC 9110 15.4).
|
||||||
|
EXPECT_TRUE(authorization.empty());
|
||||||
|
EXPECT_EQ("42", trace_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RedirectToDifferentPort, KeepPayloadMaxLength) {
|
||||||
|
auto result = GetThroughRedirectToDifferentPort(
|
||||||
|
[](const Request &, Response &res) {
|
||||||
|
res.set_content(std::string(2048, 'x'), "text/plain");
|
||||||
|
},
|
||||||
|
[](Client &cli) { cli.set_payload_max_length(1024); });
|
||||||
|
EXPECT_FALSE(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RedirectToDifferentPort, KeepMaxTimeout) {
|
||||||
|
auto result = GetThroughRedirectToDifferentPort(
|
||||||
|
[](const Request &req, Response &res) {
|
||||||
|
// Answer after a second, or once the client gives up.
|
||||||
|
for (auto i = 0; i < 100 && !req.is_connection_closed(); i++) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
}
|
||||||
|
res.set_content("late", "text/plain");
|
||||||
|
},
|
||||||
|
[](Client &cli) { cli.set_max_timeout(std::chrono::milliseconds(200)); });
|
||||||
|
ASSERT_FALSE(result);
|
||||||
|
EXPECT_EQ(Error::Read, result.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CPPHTTPLIB_SSL_ENABLED
|
||||||
|
TEST(RedirectToDifferentPort, DoNotAnswerChallengeFromRedirectTarget) {
|
||||||
|
auto result = GetThroughRedirectToDifferentPort(
|
||||||
|
[](const Request &, Response &res) {
|
||||||
|
res.status = StatusCode::Unauthorized_401;
|
||||||
|
res.set_header("WWW-Authenticate",
|
||||||
|
"Digest realm=\"r\", nonce=\"n\", qop=\"auth\"");
|
||||||
|
},
|
||||||
|
[](Client &cli) { cli.set_digest_auth("user", "pass"); });
|
||||||
|
ASSERT_TRUE(result) << "Error: " << to_string(result.error());
|
||||||
|
EXPECT_EQ(StatusCode::Unauthorized_401, result->status);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
TEST(RedirectToDifferentPort, OverflowPortNumber) {
|
TEST(RedirectToDifferentPort, OverflowPortNumber) {
|
||||||
Server svr;
|
Server svr;
|
||||||
svr.Get("/redir", [&](const Request & /*req*/, Response &res) {
|
svr.Get("/redir", [&](const Request & /*req*/, Response &res) {
|
||||||
@@ -16349,6 +16378,84 @@ TEST(SSLClientRedirectTest, CertFile) {
|
|||||||
ASSERT_TRUE(res) << "Error: " << to_string(res.error());
|
ASSERT_TRUE(res) << "Error: " << to_string(res.error());
|
||||||
ASSERT_EQ(StatusCode::OK_200, res->status);
|
ASSERT_EQ(StatusCode::OK_200, res->status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Redirects from a server with SERVER_CERT2_FILE to one with target_cert on
|
||||||
|
// another port, so the client follows it with a new client.
|
||||||
|
static Result GetThroughTlsRedirectToDifferentPort(
|
||||||
|
const std::function<void(SSLClient &)> &setup,
|
||||||
|
const char *target_cert = SERVER_CERT2_FILE) {
|
||||||
|
auto ssl_port = PORT + 1;
|
||||||
|
|
||||||
|
SSLServer ssl_svr1(SERVER_CERT2_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||||
|
ssl_svr1.Get("/index", [&](const Request &, Response &res) {
|
||||||
|
res.set_redirect("https://127.0.0.1:" + std::to_string(ssl_port) +
|
||||||
|
"/index");
|
||||||
|
});
|
||||||
|
|
||||||
|
SSLServer ssl_svr2(target_cert, SERVER_PRIVATE_KEY_FILE);
|
||||||
|
ssl_svr2.Get("/index", [&](const Request &, Response &res) {
|
||||||
|
res.set_content("test", "text/plain");
|
||||||
|
});
|
||||||
|
|
||||||
|
thread t = thread([&]() { ssl_svr1.listen("127.0.0.1", PORT); });
|
||||||
|
thread t2 = thread([&]() { ssl_svr2.listen("127.0.0.1", ssl_port); });
|
||||||
|
auto se = detail::scope_exit([&] {
|
||||||
|
ssl_svr2.stop();
|
||||||
|
ssl_svr1.stop();
|
||||||
|
t2.join();
|
||||||
|
t.join();
|
||||||
|
});
|
||||||
|
|
||||||
|
ssl_svr1.wait_until_ready();
|
||||||
|
ssl_svr2.wait_until_ready();
|
||||||
|
|
||||||
|
SSLClient cli("127.0.0.1", PORT);
|
||||||
|
cli.set_follow_location(true);
|
||||||
|
cli.set_connection_timeout(30);
|
||||||
|
setup(cli);
|
||||||
|
return cli.Get("/index");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SSLClientRedirectTest, KeepSessionVerifier) {
|
||||||
|
auto calls = 0;
|
||||||
|
auto res = GetThroughTlsRedirectToDifferentPort([&](SSLClient &cli) {
|
||||||
|
cli.enable_server_certificate_verification(false);
|
||||||
|
// Accept the first server only.
|
||||||
|
cli.set_session_verifier([&](tls::session_t) {
|
||||||
|
return ++calls == 1 ? SSLVerifierResponse::NoDecisionMade
|
||||||
|
: SSLVerifierResponse::CertificateRejected;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
EXPECT_FALSE(res);
|
||||||
|
EXPECT_EQ(2, calls);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SSLClientRedirectTest, KeepServerCertificateVerifier) {
|
||||||
|
std::string pinned;
|
||||||
|
auto res = GetThroughTlsRedirectToDifferentPort(
|
||||||
|
[&](SSLClient &cli) {
|
||||||
|
cli.enable_server_certificate_verification(false);
|
||||||
|
// Pin the first certificate seen.
|
||||||
|
cli.set_server_certificate_verifier([&](const tls::VerifyContext &ctx) {
|
||||||
|
if (pinned.empty()) { pinned = ctx.serial(); }
|
||||||
|
return ctx.serial() == pinned;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// A certificate other than the first server's.
|
||||||
|
SERVER_CERT_FILE);
|
||||||
|
EXPECT_FALSE(res);
|
||||||
|
EXPECT_FALSE(pinned.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SSLClientRedirectTest, KeepCaCertStore) {
|
||||||
|
auto res = GetThroughTlsRedirectToDifferentPort([](SSLClient &cli) {
|
||||||
|
std::string cert;
|
||||||
|
read_file(SERVER_CERT2_FILE, cert);
|
||||||
|
cli.set_ca_cert_store(tls::create_ca_store(cert.c_str(), cert.size()));
|
||||||
|
});
|
||||||
|
ASSERT_TRUE(res) << "Error: " << to_string(res.error());
|
||||||
|
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CPPHTTPLIB_SSL_ENABLED
|
#ifdef CPPHTTPLIB_SSL_ENABLED
|
||||||
|
|||||||
Reference in new issue
Block a user