You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
CertificateAuthenticationHandler performs its own X509Chain.Build on every request, but is never given the intermediate certificates the peer supplied during the TLS handshake. It therefore re-resolves over the network — via the certificate's AIA caIssuers extension — certificates the server already received on the connection and discarded. The handler also does not inherit the .NET 11 server-side default that disables those downloads for SslStream.
What is wrong
Broken invariant: a certificate that arrived as part of the connection's peer chain, and that the transport already used to complete its own chain build, should remain available to any subsequent validation of that same certificate within the same request. Today it does not.
Where the behavior originates, by component:
The runtime places every peer-supplied certificate into the handshake chain's ExtraStore (CertificateValidationPal.GetRemoteCertificate(..., retrieveChainCertificates: true, ...)), so the transport-side chain build completes without any network access.
Kestrel's static RemoteCertificateValidationCallback receives that X509Chain, but the only consumer is the optional app-supplied ClientCertificateValidation delegate. When that delegate is null — the default — the chain is never read, and the peer-supplied intermediates are dropped.
ITlsConnectionFeature carries a single X509Certificate2, so there is no place to surface the remainder of the peer chain even if Kestrel retained it.
CertificateAuthenticationHandler.BuildChainPolicy constructs an independent X509ChainPolicy whose ExtraStore is populated only from CertificateAuthenticationOptions.AdditionalChainCertificates. It never sets X509ChainPolicy.DisableCertificateDownloads and never sets UrlRetrievalTimeout.
A second, related inconsistency: dotnet/runtime#125049 set DisableCertificateDownloads = true for server-side SslStream client-certificate validation in .NET 11. The handler's independent chain build is unaffected by that change, so from .NET 11 onward the framework's own defaults disagree between the transport and the authentication handler. Transport.DirectTls's ClientCertificateValidator already applies both halves of the desired pattern.
Why it matters (defense in depth)
Correctness, independent of any actor. The server performs a network retrieval to obtain a certificate that was in memory microseconds earlier. The retrieval is bounded by UrlRetrievalTimeout, which the handler never sets; the platform default is 15 seconds per retrieval, and chain.Build is a synchronous call on the asynchronous request path.
Reliability. Authentication availability becomes coupled to the reachability of an HTTP endpoint named inside the presented certificate. On Linux there is no negative caching of failed retrievals, so an unresolvable issuer URL is re-attempted on every request rather than failing fast after the first attempt.
Hardening rationale. The fix reduces server-initiated outbound requests whose destination is derived from data received on the connection, and restores consistency with both the .NET 11 transport default and the existing Transport.DirectTls implementation. It strengthens the isolation between "data the connection carried" and "resources the server reaches for".
Affected code
Line numbers are against main.
src/Security/Authentication/Certificate/src/CertificateAuthenticationHandler.cs:118-160 — ValidateCertificateAsync; synchronous chain.Build() at line 148.
src/Security/Authentication/Certificate/src/CertificateAuthenticationHandler.cs:204-252 — BuildChainPolicy; ExtraStore populated only at lines 232 and 244; DisableCertificateDownloads and UrlRetrievalTimeout never set.
src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs:406-441 — static RemoteCertificateValidationCallback; the X509Chain? chain parameter (line 410) is consumed only at line 434 and is otherwise dropped.
src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs:443-447 — instance callback that forwards to the static method.
src/Http/Http.Features/src/ITlsConnectionFeature.cs — single-certificate surface; TryGetChannelBindingBytes is the existing default-interface-method precedent for extending it.
src/Servers/Kestrel/Transport.DirectTls/src/ClientCertificateValidator.cs:31,49,52 — already implements the desired pattern (DisableCertificateDownloads = true plus ExtraStore.AddRange(intermediates)).
Related existing issues: #53858 and #61900 describe the same missing-intermediate behavior from a functional angle. #49788 proposes exposing the chain policy to applications.
Recommended fix
Selected approach. Surface the peer-supplied intermediates on ITlsConnectionFeature as a new default-interface-method, have Kestrel capture them from the validation callback's X509Chain, and have BuildChainPolicy add them to the handler's ExtraStore. This removes the redundant retrieval for the common case with no behavioral change, because it supplies only certificates the connection already carried. Certificates must be cloned when captured, since chain elements are disposed with the handshake chain.
Alternatives considered.
Set DisableCertificateDownloads = true unconditionally, with an AppContext opt-out mirroring Disable AIA certificate downloads for server client-cert validation by default runtime#125049. Not chosen as the primary fix: it changes outcomes for clients that legitimately send a leaf only (RFC 8446 §4.4.2 makes intermediates a SHOULD, not a MUST), and for CertificateForwardingMiddleware deployments, where the header carries a single DER certificate and no chain is available at all. Reasonable as a follow-up once the plumbing fix removes the common dependency; would require a breaking-change notice and an opt-out switch.
Lower UrlRetrievalTimeout. Not chosen: the value is shared with revocation retrieval, so lowering it can introduce RevocationStatusUnknown outcomes for deployments that work today.
Compatibility, migration, and versioning.
The new member is additive via a default-interface-method; existing ITlsConnectionFeature implementations are unaffected. Requires API review and PublicAPI.Unshipped.txt entries.
Kestrel-only. HTTP.sys and IIS cannot participate — HTTP_SSL_CLIENT_CERT_INFO exposes a single encoded certificate — and the same applies to HTTP/3 and to forwarded certificates. The handler must behave exactly as today when peer intermediates are unavailable.
Public API changes cannot ship in a patch release per docs/Servicing.md; this targets main.
CertificateAuthenticationOptions.AdditionalChainCertificates (available since .NET 6) remains the supported mechanism for deployments that cannot supply a peer chain, and should be called out in the certificate authentication documentation.
Acceptance criteria
On a Kestrel mTLS connection where the client supplies leaf plus intermediate, and the intermediate is present in no local store, CertificateAuthenticationHandler completes validation without performing an AIA retrieval.
Behavior is unchanged when the client supplies a leaf only, and when the peer chain is unavailable (HTTP.sys, IIS, HTTP/3, forwarded certificates).
Peer-supplied certificates surfaced through the feature remain valid for the lifetime of the request and are not disposed together with the handshake chain.
Tests lock the property. Note that the existing assets in src/Shared/test/Certificates/Certificates.cs are all self-signed, with no real issuer relationship and no AIA extension, so a genuine test CA is required; see ClientCertificate expired causing test failures #39669 for the currently skipped chain-building tests.
ITlsConnectionFeature additions pass API review and are recorded in PublicAPI.Unshipped.txt.
Any subsequent change to the DisableCertificateDownloads default is documented as a breaking change and ships with an opt-out switch.
Summary
CertificateAuthenticationHandlerperforms its ownX509Chain.Buildon every request, but is never given the intermediate certificates the peer supplied during the TLS handshake. It therefore re-resolves over the network — via the certificate's AIAcaIssuersextension — certificates the server already received on the connection and discarded. The handler also does not inherit the .NET 11 server-side default that disables those downloads forSslStream.What is wrong
Broken invariant: a certificate that arrived as part of the connection's peer chain, and that the transport already used to complete its own chain build, should remain available to any subsequent validation of that same certificate within the same request. Today it does not.
Where the behavior originates, by component:
ExtraStore(CertificateValidationPal.GetRemoteCertificate(..., retrieveChainCertificates: true, ...)), so the transport-side chain build completes without any network access.RemoteCertificateValidationCallbackreceives thatX509Chain, but the only consumer is the optional app-suppliedClientCertificateValidationdelegate. When that delegate isnull— the default — the chain is never read, and the peer-supplied intermediates are dropped.ITlsConnectionFeaturecarries a singleX509Certificate2, so there is no place to surface the remainder of the peer chain even if Kestrel retained it.CertificateAuthenticationHandler.BuildChainPolicyconstructs an independentX509ChainPolicywhoseExtraStoreis populated only fromCertificateAuthenticationOptions.AdditionalChainCertificates. It never setsX509ChainPolicy.DisableCertificateDownloadsand never setsUrlRetrievalTimeout.A second, related inconsistency: dotnet/runtime#125049 set
DisableCertificateDownloads = truefor server-sideSslStreamclient-certificate validation in .NET 11. The handler's independent chain build is unaffected by that change, so from .NET 11 onward the framework's own defaults disagree between the transport and the authentication handler.Transport.DirectTls'sClientCertificateValidatoralready applies both halves of the desired pattern.Why it matters (defense in depth)
Correctness, independent of any actor. The server performs a network retrieval to obtain a certificate that was in memory microseconds earlier. The retrieval is bounded by
UrlRetrievalTimeout, which the handler never sets; the platform default is 15 seconds per retrieval, andchain.Buildis a synchronous call on the asynchronous request path.Reliability. Authentication availability becomes coupled to the reachability of an HTTP endpoint named inside the presented certificate. On Linux there is no negative caching of failed retrievals, so an unresolvable issuer URL is re-attempted on every request rather than failing fast after the first attempt.
Hardening rationale. The fix reduces server-initiated outbound requests whose destination is derived from data received on the connection, and restores consistency with both the .NET 11 transport default and the existing
Transport.DirectTlsimplementation. It strengthens the isolation between "data the connection carried" and "resources the server reaches for".Affected code
Line numbers are against
main.src/Security/Authentication/Certificate/src/CertificateAuthenticationHandler.cs:118-160—ValidateCertificateAsync; synchronouschain.Build()at line 148.src/Security/Authentication/Certificate/src/CertificateAuthenticationHandler.cs:204-252—BuildChainPolicy;ExtraStorepopulated only at lines 232 and 244;DisableCertificateDownloadsandUrlRetrievalTimeoutnever set.src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs:406-441— staticRemoteCertificateValidationCallback; theX509Chain? chainparameter (line 410) is consumed only at line 434 and is otherwise dropped.src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs:443-447— instance callback that forwards to the static method.src/Http/Http.Features/src/ITlsConnectionFeature.cs— single-certificate surface;TryGetChannelBindingBytesis the existing default-interface-method precedent for extending it.src/Servers/Kestrel/Transport.DirectTls/src/ClientCertificateValidator.cs:31,49,52— already implements the desired pattern (DisableCertificateDownloads = trueplusExtraStore.AddRange(intermediates)).Related existing issues: #53858 and #61900 describe the same missing-intermediate behavior from a functional angle. #49788 proposes exposing the chain policy to applications.
Recommended fix
Selected approach. Surface the peer-supplied intermediates on
ITlsConnectionFeatureas a new default-interface-method, have Kestrel capture them from the validation callback'sX509Chain, and haveBuildChainPolicyadd them to the handler'sExtraStore. This removes the redundant retrieval for the common case with no behavioral change, because it supplies only certificates the connection already carried. Certificates must be cloned when captured, since chain elements are disposed with the handshake chain.Alternatives considered.
DisableCertificateDownloads = trueunconditionally, with anAppContextopt-out mirroring Disable AIA certificate downloads for server client-cert validation by default runtime#125049. Not chosen as the primary fix: it changes outcomes for clients that legitimately send a leaf only (RFC 8446 §4.4.2 makes intermediates aSHOULD, not aMUST), and forCertificateForwardingMiddlewaredeployments, where the header carries a single DER certificate and no chain is available at all. Reasonable as a follow-up once the plumbing fix removes the common dependency; would require a breaking-change notice and an opt-out switch.UrlRetrievalTimeout. Not chosen: the value is shared with revocation retrieval, so lowering it can introduceRevocationStatusUnknownoutcomes for deployments that work today.Compatibility, migration, and versioning.
ITlsConnectionFeatureimplementations are unaffected. Requires API review andPublicAPI.Unshipped.txtentries.HTTP_SSL_CLIENT_CERT_INFOexposes a single encoded certificate — and the same applies to HTTP/3 and to forwarded certificates. The handler must behave exactly as today when peer intermediates are unavailable.docs/Servicing.md; this targetsmain.CertificateAuthenticationOptions.AdditionalChainCertificates(available since .NET 6) remains the supported mechanism for deployments that cannot supply a peer chain, and should be called out in the certificate authentication documentation.Acceptance criteria
CertificateAuthenticationHandlercompletes validation without performing an AIA retrieval.src/Shared/test/Certificates/Certificates.csare all self-signed, with no real issuer relationship and no AIA extension, so a genuine test CA is required; see ClientCertificate expired causing test failures #39669 for the currently skipped chain-building tests.ITlsConnectionFeatureadditions pass API review and are recorded inPublicAPI.Unshipped.txt.DisableCertificateDownloadsdefault is documented as a breaking change and ships with an opt-out switch.correlation: r#285731