Repository navigation
Conversation
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>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… into claude/mtls-identity-per-request-1f4542
`tlsLocalIdentityNetworkFramework` is a single client-wide identity, so every connection opened by a client that uses Network.framework presented it to any server that asked for a client certificate, including the target of a redirect to a different host. Apple's own URLSession (and browsers) choose the certificate per challenge/origin instead. Add `tlsLocalIdentityProviderNetworkFramework`, a closure that receives the host and port of the origin a connection is opened to and returns the identity to present (or nil for none). Connections are per origin, so a redirect to another host asks the provider again with that host and an identity meant for the original host is never sent to it. The provider takes precedence over the unscoped property, which is kept for source compatibility and documented as not origin-scoped. The tests build the SecIdentity from an in-memory PKCS#12 bundle (kSecImportToMemoryOnly) rather than a Keychain round-trip, which failed to find the key it had just added and made every identity test skip, and configure the client not to verify the self-signed test server so the handshake can complete. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Setting `certificateChain`/`privateKey` on `tlsConfiguration` (or on a request's own TLS configuration, which redirects preserve) presents that identity to every server the client connects to, including the target of a redirect to a different host. URLSession and browsers choose the certificate per challenge/origin. Add `tlsLocalIdentityProviderNIOSSL`, a closure that receives the host and port of the origin a connection is opened to and returns the identity to present (or nil for none). When it is set, it is the only source of the client identity: its answer replaces the certificate chain and private key of the TLS configuration used for the connection, for both direct and proxy-tunnelled connections. Without a provider behaviour is unchanged. Share the origin computation with the Network.framework provider through `ConnectionPool.Key.origin`. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The proxy-tunnel TLS setup is a separate call site from the direct-connection one. Cover that the provider is asked for the destination origin (not the proxy) and that the identity it chooses is, or is not, presented through the tunnel. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…OSSL's doc comment TLSConfiguration is NIOSSL's type, so DocC can't resolve it as a symbol link of this module. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
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.
Companion to #18, which does this for the Network.framework backend. Same problem, same model, for connections that use NIOSSL: every connection on platforms without Network.framework (Linux, the default executor there), on Apple platforms when the event loop is not a Network.framework one, and proxied connections everywhere.
Problem
An identity configured through
certificateChain/privateKey— onHTTPClient.Configuration.tlsConfiguration, or on a request's own TLS configuration (which redirects preserve since swift-server#928) — is presented to every server the client connects to that asks for a client certificate, including the target of a redirect to a different host.URLSessionand browsers choose the certificate per challenge/origin instead.Change
tlsLocalIdentityProviderNIOSSL: (@Sendable (_ host: String, _ port: Int) -> NIOSSLClientIdentity?)?with a smallNIOSSLClientIdentity(certificateChain+privateKey) value type. It is asked for the origin each connection is opened to;nilpresents none.certificateChain/privateKeyin the TLS configuration used for the connection (cleared when it returnsnil), whether that configuration came from the client or from the request. This is what actually closes the leak. Without a provider nothing changes, so existing users are unaffected; moving to the provider is a caller-side opt-in.TLSConfiguration.certificateChain's precondition there).ConnectionPool.Key.originwith the Network.framework provider, and the NWF helper now takes that origin.Dependencies
Stacked on #18 (which is itself
main+ #12): this branch is that one plus the single commit on top. Land #18 first, or this supersedes it.Tests
LocalIdentityNIOSSLTests(11, new) always run on aMultiThreadedEventLoopGroup, which selects NIOSSL on Apple platforms too:127.0.0.1to an mTLS server atlocalhost, identity only for127.0.0.1→ handshake fails; with the identity configured forlocalhost→ 200.nilbeats an identity set intlsConfigurationand one in a request's own TLS configuration.tlsConfigurationstill works (unchanged behaviour); an mTLS server without any certificate is rejected (negative control).Disabling the provider logic makes the redirect and origin tests fail. Full suite: 608 tests, the only failures are the two
testConnectTimeout("connection reset by peer"), which fail identically on a cleanmain(see #18).Proxy tunnel: three tests go through a simulated
CONNECTproxy that terminates TLS and demands a client certificate: the provider is asked for the destination origin (not the proxy), and the identity is presented or withheld accordingly. Removing the tunnel call site makes two of them fail.🤖 Generated with Claude Code