Skip to content

Commit 6608ddd

Browse files
committed
fix(tls): assign OpenSSL and WolfSSL errors a proper error_category
Errors from ERR_get_error() (OpenSSL) and wolfSSL_get_error() (WolfSSL) were wrapped in std::system_category(), but these are library-specific codes, not errno values. error_code::message() therefore rendered garbage such as "system:167772294: Unknown error 167772294". Add a dedicated error_category for each backend whose message() decodes the native code via the library's own string routines, and route every conversion site through it: - openssl_category() + make_openssl_error(), with ERR_LIB_SYS codes still mapped to std::system_category() since their reason is a genuine errno. - wolfssl_category() (flat enum, no SYS special-case), rendered via wolfSSL_ERR_error_string_n. Both categories are public (declared in the respective *_stream.hpp) so callers can detect and compare TLS errors. Add a testErrorCategory unit test per backend; run_tls_test_fail gains an optional client_ec out-parameter to capture the handshake error. Fixes #223
1 parent 9ee42fa commit 6608ddd

7 files changed

Lines changed: 189 additions & 27 deletions

File tree

include/boost/corosio/openssl_stream.hpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//
22
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3+
// Copyright (c) 2026 Michael Vandeberg
34
//
45
// Distributed under the Boost Software License, Version 1.0. (See accompanying
56
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -19,6 +20,7 @@
1920
#include <boost/capy/io_task.hpp>
2021

2122
#include <concepts>
23+
#include <system_error>
2224

2325
namespace boost::corosio {
2426

@@ -197,6 +199,24 @@ class BOOST_COROSIO_DECL openssl_stream final : public tls_stream
197199
static impl* make_impl(capy::any_stream& stream, tls_context const& ctx);
198200
};
199201

202+
/** Return the error category for raw OpenSSL errors.
203+
204+
Errors reported by @ref openssl_stream that originate from the OpenSSL
205+
error queue (`ERR_get_error`) are assigned this category. Its
206+
`message()` decodes the packed OpenSSL error code using OpenSSL's own
207+
diagnostic strings, so printing such an `error_code` yields a readable
208+
description (for example, "certificate verify failed").
209+
210+
OpenSSL errors whose library is `ERR_LIB_SYS` are reported with
211+
`std::system_category()` instead, since their reason code is a genuine
212+
`errno` value.
213+
214+
@return A reference to a static category object with name
215+
`"corosio.openssl"`.
216+
*/
217+
BOOST_COROSIO_DECL std::error_category const&
218+
openssl_category() noexcept;
219+
200220
} // namespace boost::corosio
201221

202222
#endif

include/boost/corosio/wolfssl_stream.hpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//
22
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3+
// Copyright (c) 2026 Michael Vandeberg
34
//
45
// Distributed under the Boost Software License, Version 1.0. (See accompanying
56
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -19,6 +20,7 @@
1920
#include <boost/capy/io_task.hpp>
2021

2122
#include <concepts>
23+
#include <system_error>
2224

2325
namespace boost::corosio {
2426

@@ -197,6 +199,20 @@ class BOOST_COROSIO_DECL wolfssl_stream final : public tls_stream
197199
static impl* make_impl(capy::any_stream& stream, tls_context const& ctx);
198200
};
199201

202+
/** Return the error category for raw WolfSSL errors.
203+
204+
Errors reported by @ref wolfssl_stream that originate from
205+
`wolfSSL_get_error` are assigned this category. Its `message()`
206+
decodes the WolfSSL error code using WolfSSL's own diagnostic
207+
strings, so printing such an `error_code` yields a readable
208+
description (for example, "ASN no signer error to confirm failure").
209+
210+
@return A reference to a static category object with name
211+
`"corosio.wolfssl"`.
212+
*/
213+
BOOST_COROSIO_DECL std::error_category const&
214+
wolfssl_category() noexcept;
215+
200216
} // namespace boost::corosio
201217

202218
#endif

src/openssl/src/openssl_stream.cpp

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//
22
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3+
// Copyright (c) 2026 Michael Vandeberg
34
//
45
// Distributed under the Boost Software License, Version 1.0. (See accompanying
56
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -102,8 +103,45 @@ normalize_openssl_shutdown_read_error(std::error_code ec) noexcept
102103
return ec;
103104
}
104105

106+
class openssl_category_impl final : public std::error_category
107+
{
108+
char const*
109+
name() const noexcept override
110+
{
111+
return "corosio.openssl";
112+
}
113+
114+
std::string
115+
message(int value) const override
116+
{
117+
char buf[256];
118+
::ERR_error_string_n(
119+
static_cast<unsigned long>(value), buf, sizeof(buf));
120+
return buf;
121+
}
122+
};
123+
124+
// Convert a packed OpenSSL error (from ERR_get_error) into an error_code.
125+
// Codes from the ERR_LIB_SYS library carry a genuine errno reason and are
126+
// reported with the system category; everything else uses openssl_category.
127+
inline std::error_code
128+
make_openssl_error(unsigned long err) noexcept
129+
{
130+
if (ERR_GET_LIB(err) == ERR_LIB_SYS)
131+
return std::error_code(
132+
static_cast<int>(ERR_GET_REASON(err)), std::system_category());
133+
return std::error_code(static_cast<int>(err), openssl_category());
134+
}
135+
105136
} // namespace
106137

138+
std::error_category const&
139+
openssl_category() noexcept
140+
{
141+
static openssl_category_impl instance;
142+
return instance;
143+
}
144+
107145
//
108146
// Native context caching
109147
//
@@ -474,16 +512,13 @@ struct openssl_stream::impl
474512
if (ssl_err == 0)
475513
ec = make_error_code(capy::error::stream_truncated);
476514
else
477-
ec = std::error_code(
478-
static_cast<int>(ssl_err),
479-
std::system_category());
515+
ec = make_openssl_error(ssl_err);
480516
co_return {ec, total_read};
481517
}
482518
else
483519
{
484520
unsigned long ssl_err = ERR_get_error();
485-
ec = std::error_code(
486-
static_cast<int>(ssl_err), std::system_category());
521+
ec = make_openssl_error(ssl_err);
487522
co_return {ec, total_read};
488523
}
489524
}
@@ -544,8 +579,7 @@ struct openssl_stream::impl
544579
else
545580
{
546581
unsigned long ssl_err = ERR_get_error();
547-
ec = std::error_code(
548-
static_cast<int>(ssl_err), std::system_category());
582+
ec = make_openssl_error(ssl_err);
549583
co_return {ec, total_written};
550584
}
551585
}
@@ -600,8 +634,7 @@ struct openssl_stream::impl
600634
else
601635
{
602636
unsigned long ssl_err = ERR_get_error();
603-
ec = std::error_code(
604-
static_cast<int>(ssl_err), std::system_category());
637+
ec = make_openssl_error(ssl_err);
605638
co_return {ec};
606639
}
607640
}
@@ -667,8 +700,7 @@ struct openssl_stream::impl
667700
}
668701
else
669702
{
670-
ec = std::error_code(
671-
static_cast<int>(ssl_err), std::system_category());
703+
ec = make_openssl_error(ssl_err);
672704
}
673705
co_return {ec};
674706
}
@@ -683,16 +715,14 @@ struct openssl_stream::impl
683715
if (!native_ctx)
684716
{
685717
unsigned long err = ERR_get_error();
686-
return std::error_code(
687-
static_cast<int>(err), std::system_category());
718+
return make_openssl_error(err);
688719
}
689720

690721
ssl_ = SSL_new(native_ctx);
691722
if (!ssl_)
692723
{
693724
unsigned long err = ERR_get_error();
694-
return std::error_code(
695-
static_cast<int>(err), std::system_category());
725+
return make_openssl_error(err);
696726
}
697727

698728
BIO* int_bio = nullptr;
@@ -701,8 +731,7 @@ struct openssl_stream::impl
701731
unsigned long err = ERR_get_error();
702732
SSL_free(ssl_);
703733
ssl_ = nullptr;
704-
return std::error_code(
705-
static_cast<int>(err), std::system_category());
734+
return make_openssl_error(err);
706735
}
707736

708737
SSL_set_bio(ssl_, int_bio, int_bio);

src/wolfssl/src/wolfssl_stream.cpp

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//
22
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3+
// Copyright (c) 2026 Michael Vandeberg
34
//
45
// Distributed under the Boost Software License, Version 1.0. (See accompanying
56
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -96,8 +97,33 @@ has_peer_shutdown(WOLFSSL* ssl) noexcept
9697
return wolfSSL_get_shutdown(ssl) != 0;
9798
}
9899

100+
class wolfssl_category_impl final : public std::error_category
101+
{
102+
char const*
103+
name() const noexcept override
104+
{
105+
return "corosio.wolfssl";
106+
}
107+
108+
std::string
109+
message(int value) const override
110+
{
111+
char buf[WOLFSSL_MAX_ERROR_SZ];
112+
wolfSSL_ERR_error_string_n(
113+
static_cast<unsigned long>(value), buf, sizeof(buf));
114+
return buf;
115+
}
116+
};
117+
99118
} // namespace
100119

120+
std::error_category const&
121+
wolfssl_category() noexcept
122+
{
123+
static wolfssl_category_impl instance;
124+
return instance;
125+
}
126+
101127
//
102128
// Native context caching
103129
//
@@ -558,7 +584,7 @@ struct wolfssl_stream::impl
558584
// Other error
559585
current_op_ = nullptr;
560586
co_return {
561-
std::error_code(err, std::system_category()),
587+
std::error_code(err, wolfssl_category()),
562588
total_read};
563589
}
564590
}
@@ -686,7 +712,7 @@ struct wolfssl_stream::impl
686712
// Other error
687713
current_op_ = nullptr;
688714
co_return {
689-
std::error_code(err, std::system_category()),
715+
std::error_code(err, wolfssl_category()),
690716
total_written};
691717
}
692718
}
@@ -828,7 +854,7 @@ struct wolfssl_stream::impl
828854
else
829855
{
830856
// Other error
831-
ec = std::error_code(err, std::system_category());
857+
ec = std::error_code(err, wolfssl_category());
832858
break;
833859
}
834860
}
@@ -936,14 +962,14 @@ struct wolfssl_stream::impl
936962
else
937963
{
938964
// Unexpected error
939-
ec = std::error_code(err, std::system_category());
965+
ec = std::error_code(err, wolfssl_category());
940966
break;
941967
}
942968
}
943969
else
944970
{
945971
// SSL_FATAL_ERROR or negative return
946-
ec = std::error_code(err, std::system_category());
972+
ec = std::error_code(err, wolfssl_category());
947973
break;
948974
}
949975
}
@@ -966,7 +992,7 @@ struct wolfssl_stream::impl
966992
if (!native)
967993
{
968994
return std::error_code(
969-
wolfSSL_get_error(nullptr, 0), std::system_category());
995+
wolfSSL_get_error(nullptr, 0), wolfssl_category());
970996
}
971997

972998
// Select appropriate context based on role
@@ -977,15 +1003,15 @@ struct wolfssl_stream::impl
9771003
if (!native_ctx)
9781004
{
9791005
return std::error_code(
980-
wolfSSL_get_error(nullptr, 0), std::system_category());
1006+
wolfSSL_get_error(nullptr, 0), wolfssl_category());
9811007
}
9821008

9831009
// Create SSL session from the role-specific context
9841010
ssl_ = wolfSSL_new(native_ctx);
9851011
if (!ssl_)
9861012
{
9871013
int err = wolfSSL_get_error(nullptr, 0);
988-
return std::error_code(err, std::system_category());
1014+
return std::error_code(err, wolfssl_category());
9891015
}
9901016

9911017
// Set custom I/O callbacks

test/unit/openssl_stream.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//
22
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3+
// Copyright (c) 2026 Michael Vandeberg
34
//
45
// Distributed under the Boost Software License, Version 1.0. (See accompanying
56
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -103,6 +104,42 @@ struct openssl_stream_test
103104
}
104105
}
105106

107+
/** Test that OpenSSL errors carry the OpenSSL category (issue #223).
108+
109+
Errors from the OpenSSL error queue must render readable messages,
110+
not "Unknown error <packed code>" via the system category.
111+
*/
112+
void testErrorCategory()
113+
{
114+
using namespace test;
115+
116+
// The category exists, is named, and decodes packed codes rather
117+
// than treating them as errno values.
118+
BOOST_TEST(openssl_category().name() ==
119+
std::string_view("corosio.openssl"));
120+
121+
// 0x0A000086 is a packed SSL-routines error code (see issue #223).
122+
std::error_code ec(0x0A000086, openssl_category());
123+
std::string msg = ec.message();
124+
BOOST_TEST(!msg.empty());
125+
BOOST_TEST(msg.find("Unknown error") == std::string::npos);
126+
127+
// End-to-end: a certificate-validation failure surfaces an error
128+
// in the OpenSSL category, not the system category.
129+
{
130+
io_context ioc;
131+
auto client_ctx = make_untrusted_ca_client_context();
132+
auto server_ctx = make_server_context();
133+
std::error_code client_ec;
134+
run_tls_test_fail(ioc, client_ctx, server_ctx, make_stream,
135+
make_stream, &client_ec);
136+
BOOST_TEST(client_ec);
137+
BOOST_TEST(client_ec.category() == openssl_category());
138+
BOOST_TEST(client_ec.message().find("Unknown error") ==
139+
std::string::npos);
140+
}
141+
}
142+
106143
void run()
107144
{
108145
test::testHandshakeFuse(make_stream);
@@ -128,6 +165,7 @@ struct openssl_stream_test
128165
test::testResetFuse(make_stream);
129166

130167
testCertificateChain();
168+
testErrorCategory();
131169
testName();
132170
testNextLayer();
133171
}

0 commit comments

Comments
 (0)