feat(tls): add a libssl TLS transport and an https:// HTTP/1.1 stack

HTTP/1.1 was plaintext-only, which left `https://` to either an HTTP/3
listener or a terminating proxy in front. Neither helps the callers this
stack exists for — curl scripts, CI tooling, old proxies — so wrap the
transport in libssl instead.

Two new partitions:

  :Stream  a ByteStream with per-call deadlines on both directions, plus
           the plaintext socket implementation. The HTTP/1.1 client and
           listener now hold a ByteStream& and never learn which
           transport they have, which is what lets one code path serve
           both schemes.
  :TLS     TLSContext/TLSStream over OpenSSL 3, with credentials for both
           roles: chain and hostname verification on by default, private
           trust anchors, client certificates, mutual TLS, ALPN, and an
           in-process self-signed certificate for development.

Both descriptors go non-blocking and every read and write is driven by
poll() against a deadline. That is required for TLS — a blocking
descriptor cannot express a handshake timeout — and it means a plaintext
write can now time out too, instead of parking forever against a peer
that stopped reading.

ClientHTTP1 and ListenerHTTP1 gain credential-taking constructors; the
existing ones still speak http://. The listener handshakes on the
connection's own thread, so a peer that stalls mid-handshake costs one
thread rather than the accept loop, and a failed handshake is counted
rather than logged — on a public port it is ordinary traffic.

MessageParser gains SetDefaultScheme so origin-form targets report the
scheme the transport actually used; handlers shared with ListenerHTTP now
see the same "https" they would over HTTP/3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
catbot 2026-07-28 20:13:30 +00:00
commit 9c22cbe09e
11 changed files with 1299 additions and 99 deletions

View file

@ -7,7 +7,7 @@ namespace fs = std::filesystem;
using namespace Crafter;
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> args) {
constexpr std::array<std::string_view, 13> networkInterfaces = {
constexpr std::array<std::string_view, 15> networkInterfaces = {
"interfaces/Crafter.Network",
"interfaces/Crafter.Network-ClientTCP",
"interfaces/Crafter.Network-ListenerTCP",
@ -15,6 +15,8 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
"interfaces/Crafter.Network-ListenerHTTP",
"interfaces/Crafter.Network-HTTP",
"interfaces/Crafter.Network-HTTP1",
"interfaces/Crafter.Network-Stream",
"interfaces/Crafter.Network-TLS",
"interfaces/Crafter.Network-ClientHTTP1",
"interfaces/Crafter.Network-ListenerHTTP1",
"interfaces/Crafter.Network-HTTP3",
@ -72,11 +74,13 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
return cfg;
}
constexpr std::array<std::string_view, 9> networkImplementations = {
constexpr std::array<std::string_view, 11> networkImplementations = {
"implementations/Crafter.Network-ClientTCP",
"implementations/Crafter.Network-ListenerTCP",
"implementations/Crafter.Network-ClientHTTP",
"implementations/Crafter.Network-ListenerHTTP",
"implementations/Crafter.Network-Stream",
"implementations/Crafter.Network-TLS",
"implementations/Crafter.Network-ClientHTTP1",
"implementations/Crafter.Network-ListenerHTTP1",
"implementations/Crafter.Network-ClientQUIC",
@ -108,9 +112,18 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
// linker at the actual output location.
msquic.libDirs = { "bin/Release" };
msquic.libs = { "msquic" };
std::array<fs::path, 13> ifaces;
// libssl/libcrypto — the TLS transport behind :TLS, i.e. https:// on the
// HTTP/1.1 client and listener. A system package rather than a built
// external: OpenSSL 3 is on every platform we target, and building it here
// would mean shipping a second TLS stack alongside the one msquic already
// links (quictls, which keeps its symbols to itself inside libmsquic.so).
cfg.linkFlags.push_back("-lssl");
cfg.linkFlags.push_back("-lcrypto");
std::array<fs::path, 15> ifaces;
std::ranges::copy(networkInterfaces, ifaces.begin());
std::array<fs::path, 9> impls;
std::array<fs::path, 11> impls;
std::ranges::copy(networkImplementations, impls.begin());
cfg.GetInterfacesAndImplementations(ifaces, impls);