WIP: DO NOT REVIEW Cache SslStream certificate hashes#130615
Conversation
Avoid recomputing and reallocating the SHA-512 certificate hash for each TLS credential cache lookup when a certificate context is reused. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: e9b4cc84-3f86-481e-9c92-03023269817c
|
Tagging subscribers to this area: @dotnet/ncl, @bartonjs, @vcsjones |
There was a problem hiding this comment.
Pull request overview
This PR introduces an internal SHA-512 certificate-hash cache on SslStreamCertificateContext and uses it during credential-cache probes in SslStream to avoid repeated X509Certificate.GetCertHash(HashAlgorithmName.SHA512) work when the same reusable context is used across connections.
Changes:
- Add
SslStreamCertificateContext.GetCertificateHash()which caches/publishes{cert-handle, hash}usingVolatilefor safe cross-thread reuse. - Update
SslStream.Protocolcredential acquisition paths to prefer the cached hash when the selected certificate is the context’s target certificate. - Add functional tests for concurrent authentication using a reused certificate context and for hash caching behavior (via reflection).
Show a summary per file
| File | Description |
|---|---|
| src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamCredentialCacheTest.cs | Adds a concurrent multi-connection theory that reuses SslStreamCertificateContext (optionally with ALPN and mutual TLS). |
| src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamCertificateContextTests.cs | Adds a reflection-based test for GetCertificateHash() reuse and disposed-certificate behavior. |
| src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.cs | Implements cached certificate hash storage on the reusable context. |
| src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs | Uses the cached hash for credential-cache keying instead of re-hashing each probe. |
Copilot's findings
- Files reviewed: 4/4 changed files
- Comments generated: 2
| public static IEnumerable<object[]> CredentialReuseData() | ||
| { | ||
| foreach (SslProtocols protocol in SslProtocolSupport.EnumerateSupportedProtocols(SslProtocols.Tls12 | SslProtocols.Tls13, true)) | ||
| { | ||
| yield return new object[] { protocol, false, false }; | ||
| yield return new object[] { protocol, true, false }; | ||
| yield return new object[] { protocol, true, true }; | ||
| } | ||
| } | ||
|
|
|
|
||
| TargetInvocationException exception = Assert.Throws<TargetInvocationException>( | ||
| () => getCertificateHash.Invoke(context, null)); | ||
| Assert.IsAssignableFrom<CryptographicException>(exception.InnerException); |
|
Still WIP — DO NOT REVIEW. The exact-parent publication pass is complete, but this draft is intentionally not ready for review.
Frozen harness identity:
Raw artifact publication and CI review remain outstanding. Please do not review yet. Note This WIP status comment was generated with GitHub Copilot. |
I am running an experiment - full analysis to follow. Contact @artl93
This draft investigates repeated SHA-512 certificate digest work during
SslStreamcredential-cache probes when anSslStreamCertificateContextis reused.Exact-parent component evidence (local Debug build only)
Local environment: Apple M5 Pro (18 logical CPUs), macOS 26.5.2 arm64, repo-built .NET 11 Debug libraries testhost. Parent
7aa830a03599a8255c2c4abf2947afc5b346cc6f; candidated1518851d013212171c5255c85698c954296657c.A frozen external harness invokes the real private server method
SslStream.AcquireServerCredentials(ref byte[]?)after configuring reusable server authentication options. It does not merely compare direct and cached helper calls. Each revision used an isolated immutable testhost. The run alternated parent/candidate order across 8 process launches, with 1,000,000 warmup probes and 7 trials of 1,000,000 probes per launch (56 measured trials/revision).These magnitudes are not shipping/Release performance numbers. Attempts to create a coherent Release-libraries testhost in this checkout are currently blocked by pre-existing CoreLib/LibraryImport generation failures; the failed commands/logs are retained. The table is evidence for the repeated-work mechanism and Debug component impact only.
On Apple,
AcquireServerCredentialsreturnedcachedCreds == falsein all trials. This platform path has a cheap/null PAL credential handle at this stage; native TLS security-context construction occurs later during the handshake. The timed method still performs certificate selection, hash acquisition, credential-cache key lookup, and that PAL call. The result should not be read as native credential-construction time. The client credential path is changed too, but this canonical benchmark measures only the server path.A separate baseline trace attributed 19.29% inclusive samples to
X509Certificate.GetCertHash -> HashData -> Interop.AppleCrypto.DigestOneShotin an isolated process that also included certificate/setup work. That percentage validates stack attribution only; it is not the hash share ofAcquireServerCredentialsand is not used to derive the 10.71x table.The frozen harness is currently external to this PR. It self-identifies revision, source/binary hashes, loaded
System.Net.Security.dllhash, runtime/OS, full command, warmups, iterations, and trials. Canonical identities:37132680bc488327349e625a36cad7c000d308181390f2f89141e6ede6dc6deb6137092338f45c133d5dfc03c02945637914f79cf7c6c3bb7b0f353834e4bea9System.Net.Security.dll:3b3a00a5ac8e52d098151eed0456723cd9527f61ade48313fd62f4245379d123System.Net.Security.dll:2f4ee6e2d5c3cccf4a9fba3091fabb0226ef815766dd6646fde5046e83232450Raw harness/log publication is still pending; this remains WIP.
Why cache on
SslStreamCertificateContextThe credential-cache key includes a SHA-512 digest of the selected certificate. A reusable certificate context represents the same target certificate across many connections, but the previous code recomputed that digest and allocated a fresh array on every client/server credential probe.
The change lazily stores an immutable
{ certificate handle, hash }cache entry on the context and publishes it atomically. Reuse requires the current native certificate handle to match. On a miss, the digest is computed and published only if the handle is unchanged before/after hashing. Disposal/reset changes the handle and preserves the existingCryptographicExceptionbehavior instead of returning stale data. The byte array remains internal and is never mutated. No public API changes.This is managed cross-platform code used above Schannel, OpenSSL, Apple TLS, and Android. Local execution covers Apple only; all configured System.Net.Security target frameworks compile in the validated Debug build, and runtime behavior on other platforms depends on CI/platform validation.
Supporting handshake evidence — explicitly not a performance claim
Four interleaved launches per revision exercised TLS 1.2 with reused/fresh contexts, ALPN, mTLS, resumption enabled/disabled, and concurrency 1/16. All successful runs negotiated TLS 1.2; ALPN scenarios negotiated
h2. The internal resumed-session flag was observable and recorded zero resumed sessions on both client and server, even whenAllowTlsResume=true. TLS 1.3 timed out identically for parent/candidate on this Apple testhost.Whole-handshake elapsed deltas ranged from -2.7% to +3.1%, and CPU deltas changed sign across scenarios. These results are noisy and do not establish an HTTPS or TLS-handshake speedup. No request-throughput claim is made. The supported local claim is limited to the Debug server credential-probe component.
Validation
Targeted coverage includes atomic concurrent publication through 16 simultaneous reused-context connections, protocol negotiation, ALPN, mTLS/client certificates, certificate validation, encrypted data transfer, cached hash identity, and disposed-certificate invalidation.
Rejected variants
Context-level caching is smaller, aligns lifetime with the reusable certificate context, and avoids repeated digest work and per-probe array allocation.
Note
This draft PR description was generated with GitHub Copilot. The PR intentionally remains WIP and should not be reviewed yet.