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>
This was referenced Oct 7, 2026
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.
Problem
HTTPClient.Configuration.tlsLocalIdentityNetworkFrameworkis one client-wide identity. On the Network.framework (NIOTS) backend every connection presented it to any server that requested a client certificate — including the target of a redirect to a different host. Apple'sURLSessionand browsers pick the certificate per challenge/origin instead, and.urlSessionin request-dl already follows that model; only the NIOTS path leaked.Change
tlsLocalIdentityProviderNetworkFramework: (@Sendable (_ host: String, _ port: Int) -> SecIdentity?)?. It is asked for the origin each connection is opened to and returns the identity to present, ornilfor none.tlsLocalIdentityNetworkFramework. The unscoped property is kept for source compatibility (it is already shipped onrelease) and its doc now carries a warning pointing at the provider.PR dependencies
This branch is
main+ the merge of #12 (mtls-network-framework-identity), which introduces the identity hook this change scopes. #12 must land first, or this supersedes it. The redirect PR (#9) is not required. The diff of this PR on top of #12 is the single commitdf259ec.Tests
LocalIdentityNetworkFrameworkTests(7 tests, all run, none skipped):127.0.0.1to an mTLS server atlocalhost, identity configured only for127.0.0.1→ handshake fails. Reverting the scoping makes this test fail, so it genuinely detects the leak.localhost→ 200 (positive control).While doing this I found the #12 identity tests were always skipped: the Keychain round-trip in
TestIdentityBuilderfailed witherrSecItemNotFound, and the client never trusted the self-signed server. They now build theSecIdentityfrom an in-memory PKCS#12 bundle (kSecImportToMemoryOnly, so macOS 15 / iOS 18+, otherwiseXCTSkip) and usecertificateVerification: .noneon the client. The Keychain builder, DER reader and the twoTestTLSDER helpers it needed are removed.Not covered: the
.nio(NIOSSL) backend still has one client-widecertificateChain/privateKeyand is a separate change. In the wider run (600 tests), the only failures weretestConnectTimeoutin HTTPClientTests and AsyncAwaitEndToEndTests ("connection reset by peer" instead of a timeout). They fail identically on a cleanmain(4c005f9), so they are pre-existing and unrelated to this change (most likely the local network environment).🤖 Generated with Claude Code