Keep the TLS error behind a failed read or write in the client's Result

A request that fails after the TLS handshake reports Error::Read or
Error::Write, and SSLSocketStream dropped the TLS error behind it. On
TLS 1.3, where a server rejects a client without a certificate only
after the handshake, ssl_error() and ssl_backend_error() were 0 instead
of showing, for example, OpenSSL's "tlsv13 alert certificate required".

SSLSocketStream now keeps the error from its last failed TLS read or
write, and SSLClient copies it into the values Result reports. Error
itself stays Read or Write.

Those values are also reset at the start of each request, so a later
successful request no longer reports the TLS error of an earlier one. A
client whose TLS context failed to set up keeps reporting that error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGAYpQgXt95LdDQcJYgjB8
This commit is contained in:
Claude committed 2026-10-17 01:54:23 +00:00
1 parent b4ef313cc7
commit 28741cb9d1
2 files changed
+73 -5

No files matched your search

+35
View File
@@ -14612,6 +14612,8 @@ void ClientCertMissing(const ClientCertServerSetup &setup = nullptr) {
if (res.error() == Error::SSLConnection) {
EXPECT_NE(0UL, res.ssl_backend_error());
}
// A rejection noticed on the write keeps the TLS error the write hit
if (res.error() == Error::Write) { EXPECT_NE(0, res.ssl_error()); }
}
TEST(SSLClientServerTest, ClientCertMissing) { ClientCertMissing(); }
@@ -14639,6 +14641,39 @@ TEST(SSLClientServerTest, ClientCertStillRequiredAfterVerifyCallbackCleared) {
ClientCertStillRequired(true);
}
// A TLS error from one request must not show up in a later request's Result
TEST(SSLClientServerTest, SSLErrorNotCarriedToLaterRequest) {
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
ASSERT_TRUE(svr.is_valid());
svr.Get("/test", [](const Request &, Response &res) {
res.set_content("test", "text/plain");
});
auto port = svr.bind_to_any_port(HOST);
ASSERT_LT(0, port);
thread t = thread([&]() { svr.listen_after_bind(); });
auto se = detail::scope_exit([&] {
svr.stop();
t.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
SSLClient cli(HOST, port);
cli.set_connection_timeout(30);
cli.enable_server_hostname_verification(false);
// cert.pem is not trusted, so the handshake fails
ASSERT_FALSE(cli.Get("/test"));
cli.enable_server_certificate_verification(false);
auto res = cli.Get("/test");
ASSERT_TRUE(res) << "Error: " << to_string(res.error());
EXPECT_EQ(0, res.ssl_error());
EXPECT_EQ(0UL, res.ssl_backend_error());
}
// PEM memory-based constructor tests (works with all TLS backends)
void PemMemoryClientCertPresent(
const std::string &client_cert_file,