Skip to content

Expose NIOSSL/Network.framework trust-verification and mTLS client-identity hooks - #932

Open
o-nnerb wants to merge 6 commits into
swift-server:mainfrom
request-dl:trust-and-mtls-hooks
Open

o-nnerb wants to merge 6 commits into
swift-server:mainfrom
request-dl:trust-and-mtls-hooks

Conversation

@o-nnerb

@o-nnerb o-nnerb commented Sep 27, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #910, which I closed after concluding that bundling a full SPKI-pinning implementation into AsyncHTTPClient was the wrong shape — URLSession doesn't do that either, it just hands callers a hook via its delegate and lets them build pinning (or anything else) on top. This PR is that hook, for both TLS backends this library uses:

  • HTTPClient.Configuration.tlsCustomVerification wires up NIOSSLCustomVerificationCallback, which NIOSSL already exposes on NIOSSLClientHandler — this just plumbs it through from HTTPClient.Configuration instead of requiring a caller to construct their own NIOSSLClientHandler.
  • tlsCustomVerificationNetworkFramework is the equivalent for direct (non-proxied) connections on Apple platforms that go through Network.framework instead of NIOSSL, which has no equivalent today.
  • tlsLocalIdentityNetworkFramework lets a caller present a client certificate (mTLS) over Network.framework by handing over a SecIdentity they've already obtained. Related to Not supported features at TLSConfiguration #696, where @Lukasa mentioned bridging support here would be welcome — this stays conservative and doesn't attempt the Keychain round-trip itself (tlsConfiguration.certificateChain/.privateKey remain the way to do this on the NIOSSL backend), it just accepts an identity the caller already has.

Both new hooks are additive and only apply to the Network.framework code path; NIOSSL behavior (including proxied connections on Apple platforms, which always go through NIOSSL) is unchanged.

o-nnerb and others added 6 commits September 8, 2026 18:16
Exposes HTTPClient.Configuration.tlsCustomVerification (NIOSSL backend)
and .tlsCustomVerificationNetworkFramework (Network.framework backend),
thin passthroughs into NIOSSLClientHandler's customVerificationCallback
and Network.framework's sec_protocol_options_set_verify_block
respectively. Neither hook carries any built-in pinning policy — the
goal is to let a caller (e.g. a TrustEvaluator implemented downstream)
fully own the accept/reject decision on whichever TLS backend actually
negotiates the connection, including inspecting the full presented
chain rather than just the leaf.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Exposes HTTPClient.Configuration.tlsLocalIdentityNetworkFramework: a
thin passthrough into Network.framework's
sec_protocol_options_set_local_identity, for direct (non-proxied)
connections on Apple platforms. tlsConfiguration.certificateChain and
.privateKey (the NIOSSL-shaped mTLS config) remain unsupported on this
backend, same as before -- there's no public API to build a
SecIdentity from raw bytes without a Keychain round-trip, so this hook
takes an already-built SecIdentity rather than AsyncHTTPClient
performing that round-trip itself.

Tests cover both that the client certificate is actually presented to
a server that requires one, and the negative control (connection
rejected without it). Synthesizing a Keychain-backed SecIdentity
inside an unsigned `swift test` process is itself unreliable -- the
positive test skips rather than flakes when that round-trip can't
complete, same limitation RequestDL's own RawBytesIdentityBuilder
test suite already works around by only unit-testing its DER-parsing
halves.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…oc comment

Three double-backtick links in an unconditional doc comment pointed at
symbols that don't exist in every build this repo's CI produces
documentation for: tlsCustomVerificationNetworkFramework is
canImport(Network)-gated (absent from the Linux symbol graph
entirely), and TLSConfiguration.certificateVerification /
NIOSSLCustomVerificationCallback both live in the NIOSSL module, which
DocC can't resolve an unqualified cross-module link against in this
build. Switched all three to plain code spans -- still readable,
without a resolution DocC can't perform.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant