Skip to content

CertificateAuthenticationHandler discards TLS-supplied intermediates and re-resolves them via AIA #69351

Description

@rokonec

Summary

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-160ValidateCertificateAsync; synchronous chain.Build() at line 148.
  • src/Security/Authentication/Certificate/src/CertificateAuthenticationHandler.cs:204-252BuildChainPolicy; 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.
  • Expose the chain policy for application configuration (Enable Automatic Rotation of Trusted Client Certificate Chain Components #49788). Complementary rather than a substitute — it leaves the default behavior unchanged and requires every application to opt in.
  • 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.

correlation: r#285731

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-proposalarea-authIncludes: authentication, authorization, OAuth, OIDC, and access token validation

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions