Repository navigation
tls, httpclient, quic: BoringSSL ships in the packages and replaces the system OpenSSL - #373
Merged
Merged
Conversation
…into one library that needs only libc scripts/build-boringssl-native.sh links libssl and libcrypto whole into one -Bsymbolic object and packs it into ioxide's runtimes/linux-x64/native, like the ngtcp2, nghttp2 and nghttp3 bundles.
BoringSsl replaces OpenSsl. BoringSSL has real functions where OpenSSL used SSL_CTX_ctrl macros, and its lengths and error codes are size_t and uint32; the binding follows its headers. CipherSuites goes through SSL_CTX_set1_tls13_ciphers, refusing any name that is not a TLS 1.3 suite BoringSSL implements (#348); kTLS still pins AES-128-GCM. A PEM file with a UTF-8 BOM, which OpenSSL's reader skipped, goes through the in-memory route without it. A key that does not match its certificate says so the same way whichever check notices. Tests that matched OpenSSL's error wording match BoringSSL's; the identity test renders a 20 KB subject, since a larger client certificate no longer fits BoringSSL's TLS 1.3 tickets; the lifetime test no longer writes to a disposed session.
…ess is verified against its iPAddress names BoringSSL's SSL_set1_host checks DNS names only, where OpenSSL switched to the address check itself, so an address goes through X509_VERIFY_PARAM_set1_ip - and is not sent as SNI (RFC 6066 3). A refused certificate still reads 'certificate verify failed'. The alert after a failed handshake is best-effort: a peer that already closed refuses it, and that no longer replaces the handshake's own error.
…ng from the system picotls's OpenSSL backend is built on BoringSSL, linked in with its symbols hidden; the bundle no longer depends on libcrypto.so.3. The client keeps offering the groups it offered before: on BoringSSL, picotls's list starts with X25519MLKEM768, a 1.2 KB key share. Servers keep the full list, so a client that offers the post-quantum group gets it.
TlsSession.Dispose skipped its close_notify when the peer had sent one first. RFC 8446 6.1 (and 5246 7.2.1) has each side send close_notify before closing its write side, so a client that closed politely saw the server's stream end in a truncation.
…E says Env.OverrideTlsCert, called in the 26 samples that take a certificate. The README and four TLS samples documented the two variables and nothing read them.
…they are, so a read that waits allocates nothing The pass-through was an async method, so every read that had to wait allocated its state machine: 208 B per keep-alive request on a kTLS-RX pipe connection. The carry path keeps its async body.
…is refused Its bundle goes through AddTrustAnchorsPem, whose two refusals said ClientCaPem whatever the source.
…s measured again on it The TLS page, the Tls/OpenSsl sample and TlsOptions.KernelTx quoted OpenSSL measurements; they quote bench/tls-matrix.sh on this branch now (kTLS TX costs 5-29% more CPU per request than BoringSSL, never less). Comments that described OpenSSL's behaviour as the library's say what BoringSSL does, checked against its source; the ones that record how a bug was found under OpenSSL stay as they were. Test names that said OpenSSL for the default path say BoringSSL.
…eam data, rather than failing ngtcp2 probes the path at 1406 and then 1444 bytes once the handshake is confirmed, and a probe that is due takes the next write without the stream's data (consumed -1). Send wrote exactly once, so 'a send on a stream the peer already finished does not leak send retention' failed whenever its request met the second probe: run alone it failed 21 of 25 times on main and 25 of 25 here, and passes 25 of 25 on both with the probe sent first.
MDA2AV
force-pushed
the
feat/boringssl
branch
from
October 14, 2026 19:50
a5a45fd to
4b7596d
Compare
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ioxide's TLS runs on BoringSSL instead of the system OpenSSL, everywhere OpenSSL was used: the server (
ioxide.tls), the HTTP client, and the QUIC bundle's crypto. BoringSSL ships inside the packages, so TLS no longer needs anything installed. It is also cheaper: 15% less CPU per request on keep-alive TLS, 13% less per handshake.Fixes #348.
The native build
scripts/build-boringssl-native.shbuildslibioxide_boringssl.sofrom a pinned BoringSSL commit (5b83e5d, 2026-10-11), the same way the other native bundles are built: libssl and libcrypto linked whole into one stripped object (3.3 MB), packed intoioxide'sruntimes/linux-x64/native. Its only dependency is libc.-Bsymbolickeeps every call inside the library: .NET loads the system OpenSSL into the same process for its own crypto, under the same symbol names.scripts/build-ngtcp2-native.shbuilds picotls and ngtcp2 on BoringSSL's libcrypto, linked in statically with its symbols hidden.libioxide_ngtcp2.sono longer needslibcrypto.so.3(1.2 MB -> 2.9 MB).What changed in the code
OpenSslis nowBoringSsl(server and client bindings). BoringSSL has real functions where OpenSSL usedSSL_CTX_ctrlmacros (protocol bounds, SNI callback, extra chain certificates), and a few ABI types differ (size_tlengths, 32-bit error codes); the bindings follow BoringSSL's headers.CipherSuiteskeeps its meaning throughSSL_CTX_set1_tls13_ciphers. Every name has to be a TLS 1.3 suite BoringSSL implements, so a TLS 1.2 name or a typo is refused at startup. That is tls: a CipherSuites name that is not a TLS 1.3 suite is accepted, and the port then serves nobody #348, and the test tls: a CipherSuites name that is not a TLS 1.3 suite is refused at startup #367 added for it now passes as a regular test. kTLS still pins AES-128-GCM.SSL_set1_hostchecks DNS names only; OpenSSL switched to the address check itself.certificate verify failed (...)whatever the library calls it.Found along the way
TlsSession.Disposeskipped its close_notify when the peer had sent one first. RFC 8446 6.1 (and 5246 7.2.1) has each side send close_notify before closing its write side, so a client that closed politely saw the server's stream end without one. New test:tls truncation: a client's close_notify is answered with the server's own, which fails with the old guard put back.PLAYGROUND_TLS_CERT/PLAYGROUND_TLS_KEY, and no sample read them.Env.OverrideTlsCertnow does, in all 26 samples that take a certificate.quic: a send on a stream the peer already finished does not leak send retentionfailed whenever its request met the probe: run alone, 21 of 25 times on main and 25 of 25 here.Sendnow sends the probe and writes again; 25 of 25 pass on both.TlsProloguePipeReaderallocated 208 B per waiting read) is in here as its own commit.Behaviour that differs from OpenSSL
NO_SHARED_CIPHER,UNSUPPORTED_PROTOCOL,NO_START_LINE,BAD_END_LINE). The tests that matched OpenSSL's wording now match these.SessionLifetimeTestsused to callTlsSession.WriteafterDispose, which is a use-after-free. OpenSSL happened to return an error there; BoringSSL segfaults. The library contract is unchanged (use after dispose is not guarded); the test no longer commits it.MinimumVersion = 0on the client means BoringSSL's default floor, TLS 1.2, where OpenSSL read it as no floor.Tests
Every suite, Release, on the final commit:
#348's test, pending on main, passes here, and the close_notify test is new. Three
QuicEngineTestscases failed on the post-quantum key share (client write failed: 1444) until the client's group order was restored.Benchmarks
Loopback, main (
cda7e55, Ubuntu's OpenSSL 3.0.13) against this branch before its rebase onto #358-#370, none of which touch the request path; interleaved runs, server pinned to 2 cores.Keep-alive HTTP/1.1,
bench/ab.sh, 2 reactors, 64 connections, 5 rounds of 10 s, medians:Tls/OpenSslPipesTls/OpenSsl(raw ring)Tls/KtlsPipes(kernel crypto)Full TLS 1.3 handshakes,
Tls/OpenSsl, RSA-2048, 32 Python clients each closing right after its handshake, 3 rounds of 5 s: 505.6 -> 438.2 us of server CPU per handshake (-13.3%), 3,886 -> 4,467 handshakes/s, no failures. The exploration behind this PR measured -37% for an ECDSA P-256 certificate.bench/tls-matrix.shon the branch, 4 reactors, best of two passes, requests/s and the ratio to plaintext. The TLS page, theTls/OpenSslsample andTlsOptions.KernelTxquote this table now instead of the OpenSSL one:Over HTTP/2 the two stay within 5% of each other at 64 B and 8 KiB.
bench/file-matrix.shstill has the userspace library ahead of kTLS+slab at 64 KiB (52,925 vs 38,310 req/s), as the file sample's notes say.Docs
The TLS pages, READMEs, sample comments, generated panes, bench script comments and test names say BoringSSL where they described the library ioxide uses. Comments that record how a bug was found under OpenSSL keep saying OpenSSL. Claims about library behaviour were checked against BoringSSL's source: the SNI list parsing, the ALPN validation, the key/certificate check at load, the error-queue clearing and
PEM_def_callback.Not renamed here, as both would be breaking or noisy: the
Tls/OpenSsl,Tls/OpenSslPipesandTls/MtlsOpenSslPipessample directories (with the bench labels that follow them), and the publicOpenSslVersionsconstants class inioxide.httpclient.Rebased onto #358, #362, #366, #368, #369 and #370
What those PRs added, carried over to BoringSSL:
certificate verify failed: <reason> (<error>), since BoringSSL's own error for it is the identifierCERTIFICATE_VERIFY_FAILED.SSL_in_initis BoringSSL's.OPENSSL_sk_valuetakes asize_tindex there, so the binding does too. Its UTF-8 change relied on OpenSSL skipping a byte-order mark, which BoringSSL does not, soAddTrustAnchorsPemdrops a leading one itself.#367 is closed: this PR fixes #348 its own way and keeps that PR's test. #307 is the one open PR this conflicts with, and it already conflicts with main.