Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
2e82102
TlsSession PoC: non-blocking TLS state machine over OpenSSL PAL
wfurt May 27, 2026
183694a
Wire SslStream to TlsSession on Linux/FreeBSD
wfurt May 27, 2026
6bc9d64
TlsSession: add Shutdown API for TLS close_notify
wfurt May 27, 2026
c9acc84
TlsSession: add GetChannelBinding
wfurt May 27, 2026
783ebb3
TlsSession: add LocalCertificate and RequestClientCertificate
wfurt May 27, 2026
a98d43e
TlsSession: wire remote certificate validation hook
wfurt May 27, 2026
3ed7000
TlsSession: drop Encrypt/Decrypt wedge
wfurt May 27, 2026
aaa7105
TlsSession: add standalone-handshake tests (in-memory + as-client)
wfurt May 27, 2026
aad66dd
TlsSession: enable TLS 1.3 in-memory test + add non-blocking socket test
wfurt May 27, 2026
db70a0a
TlsSession: Tier 1 Linux gaps (SNI cert selection, RequestRenegotiati…
wfurt May 27, 2026
45ab519
TlsSession: handle SChannel SEC_I_RENEGOTIATE post-handshake messages
wfurt May 29, 2026
5ac0dbe
TlsSession wedge: refresh credential cache after CredentialsNeeded
wfurt May 29, 2026
9599ca8
checkpoint
wfurt May 29, 2026
b821de9
TlsSession: defer cert validation via SSL_set_retry_verify on OpenSSL…
wfurt May 29, 2026
0857c66
TlsSession: add deferred server options (NeedsServerOptions / SetServ…
wfurt May 29, 2026
00bcafe
opensslshim: undef OpenSSL 3.0 SSL_set_retry_verify macro
wfurt May 29, 2026
15d3c26
TlsSession: keep X509Chain off the public surface; expose peer interm…
wfurt May 29, 2026
932a0c3
TlsSession: remove RequestRenegotiation; keep RequestClientCertificate
wfurt May 29, 2026
2d417c1
TlsSession: rename consumed/produced -> bytesConsumed/bytesWritten
wfurt Jun 1, 2026
004fa59
TlsSession: drop SSL_set_retry_verify; validate post-handshake on all…
wfurt Jun 1, 2026
407100a
TlsSession tests: add standalone explicit-reject regression test
wfurt Jun 1, 2026
5dbee90
TlsSession: clone options per session so TlsContext is reusable
wfurt Jun 1, 2026
6a54994
TlsSession: always suspend for cert validation on server when peer ce…
wfurt Jun 1, 2026
b243dc1
TlsSession: add socket-bound mode with Handshake/Read/Write
wfurt Jun 1, 2026
79b01e2
Add TlsSession.SetClientCertificateContext for resuming after WantCre…
wfurt Jun 1, 2026
1a58921
TlsSessionTests: fix flake + Tls12/Tls13 coverage
wfurt Jun 1, 2026
964f9fa
TlsSession: add GetAcceptableIssuers() for client-side CA hints
wfurt Jun 1, 2026
e0791ef
TlsSession: AcceptWithDefaultValidation rejects when callback returns…
wfurt Jun 1, 2026
d172dd2
Merge branch 'main' of https://github.com/dotnet/runtime into TlsSession
wfurt Jun 1, 2026
bba7690
Enable TlsSession/TlsContext on macOS (legacy SecureTransport)
wfurt Jun 2, 2026
9971a81
TlsContext: bind to OpenSSL SSL_CTX so sessions reuse one native context
wfurt Jun 2, 2026
5e89ef9
Merge remote-tracking branch 'upstream/main' into TlsSession
wfurt Jun 2, 2026
e797903
TlsSession: bind socket fd via SSL_set_fd for socket-bound mode
wfurt Jun 2, 2026
a87a528
TlsSession fd-mode: pass SafeSocketHandle through interop
wfurt Jun 2, 2026
4d7e205
TlsSession: split OpenSSL fd-mode into TlsSession.OpenSsl.cs partial
wfurt Jun 2, 2026
78f8da9
Add TlsHandshakeBench: SslStream vs TlsSession (buffered/fd) handshak…
wfurt Jun 2, 2026
c90f056
Honor AllowTlsResume on TlsContext-owned SSL_CTX
wfurt Jun 3, 2026
c8f6ca7
Fix TlsSession.Decrypt to drain PAL-buffered plaintext after partial …
wfurt Jun 3, 2026
94b1717
Add functional test for TlsSession AllowTlsResume
wfurt Jun 3, 2026
66de39b
TlsHandshakeBench: AllowResume param, dedicated-thread driver, pooled…
wfurt Jun 3, 2026
34227f9
Fix Unit.Tests linux build: include SslAuthenticationOptions.OpenSsl.cs
wfurt Jun 3, 2026
6b463b5
Enable shared TlsSession on macOS legacy (SecureTransport) PAL
wfurt Jun 3, 2026
44d2a3a
NW PAL: transparent fallback, UDP listener refactor, EOF handling, Di…
wfurt Jun 3, 2026
5dea025
NW PAL: per-session serial dispatch queue targeting concurrent root
wfurt Jun 3, 2026
7a05cf2
NW PAL: server cert-selection NotSupported parity + skip NW-incompati…
wfurt Jun 3, 2026
b223e2f
Merge remote-tracking branch 'upstream/main' into TlsSession
wfurt Jun 11, 2026
1d16554
TlsSession/TlsContext: mark experimental (SYSLIB5007)
wfurt Jun 30, 2026
3482bec
TlsSession: enable OpenSSL fd-mode fast path for deferred SetServerOp…
wfurt Jul 1, 2026
45674f4
TlsSession: dormant retry-verify plumbing + external-validation tests
wfurt Jul 4, 2026
e7fd058
Merge branch 'TlsSession' of https://github.com/wfurt/runtime into Tl…
wfurt Jul 4, 2026
da1ecd0
TlsHandshakeBench: add TlsSession_Fd_Deferred_Server benchmark
wfurt Jul 5, 2026
4c2e897
TlsSession: add SetServerContext for caller-managed context pools
wfurt Jul 5, 2026
be3d274
TlsSession: add native BioReadTlsFrame primitive
wfurt Jul 5, 2026
0b438ff
TlsSession: SafeSslHandle.Create accepts a pre-existing read BIO
wfurt Jul 5, 2026
7dda587
TlsSession: native ClientHello peek for the deferred-fd flow
wfurt Jul 5, 2026
c104e8d
TlsSession: drop SetServerOptions(SslServerAuthenticationOptions) in …
wfurt Jul 5, 2026
071130e
TlsHandshakeBench: rename Fd_Deferred_Pool -> Fd_Deferred, expose All…
wfurt Jul 6, 2026
3d869f2
TlsSession: always capture ClientHello bytes; add GetClientHelloBytes…
wfurt Jul 6, 2026
28b2e20
TlsSession: fix buffered ClientHello capture + add GetClientHelloByte…
wfurt Jul 6, 2026
7f24c4f
TlsHandshakeBench: add CaptureClientHello param toggling LocalAppCont…
wfurt Jul 6, 2026
cbbf7a3
TlsSession: session-local credentials handle for SetClientCertificate…
wfurt Jul 6, 2026
a129ec2
TlsContext: collapse _ownsOptions + _shareOptions into single _isWedg…
wfurt Jul 7, 2026
fdd0fa4
TlsSession: use session-local credentials in SetServerContext too
wfurt Jul 7, 2026
1ce3573
TlsSession: rename TlsOperationStatus values per API review
wfurt Jul 7, 2026
31f9ec5
TlsSession: TlsContext factories -> CreateServer/CreateClient, drop p…
wfurt Jul 7, 2026
4cd75aa
TlsSession: SetServerContext -> SetContext per API review
wfurt Jul 7, 2026
ecb757d
TlsSession: Phase 2-6 API review compliance
wfurt Jul 8, 2026
a0575b4
TlsSession: fix Copilot review issues (handle leaks, volatile, guards)
wfurt Jul 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
870 changes: 870 additions & 0 deletions TlsSession-proposal.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/project/list-of-diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,3 +319,4 @@ Diagnostic id values for experimental APIs must not be recycled, as that could s
| __`SYSLIB5004`__ | .NET 9 | TBD | `X86Base.DivRem` is experimental since performance is not as optimized as `T.DivRem` |
| __`SYSLIB5005`__ | .NET 9 | .NET 10 | `System.Formats.Nrbf` is experimental |
| __`SYSLIB5006`__ | .NET 10 | TBD | Types for Post-Quantum Cryptography (PQC) are experimental. |
| __`SYSLIB5007`__ | .NET 10 | TBD | Low-level TLS engine types (`TlsContext`, `TlsSession`) in `System.Net.Security` are experimental. |
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ internal static unsafe partial bool Init(

// Create a new connection context
[LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_NwConnectionCreate", StringMarshalling = StringMarshalling.Utf8)]
internal static unsafe partial SafeNwHandle NwConnectionCreate([MarshalAs(UnmanagedType.I4)] bool isServer, IntPtr context, string targetName, byte* alpnBuffer, int alpnLength, SslProtocols minTlsProtocol, SslProtocols maxTlsProtocol, uint* cipherSuites, int cipherSuitesLength);
internal static unsafe partial SafeNwHandle NwConnectionCreate([MarshalAs(UnmanagedType.I4)] bool isServer, IntPtr context, string targetName, byte* alpnBuffer, int alpnLength, SslProtocols minTlsProtocol, SslProtocols maxTlsProtocol, uint* cipherSuites, int cipherSuitesLength, IntPtr serverIdentity);

// Start the TLS handshake, notifications are received via the status callback (potentially from a different thread).
[LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_NwConnectionStart")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,13 +189,13 @@ private static SslProtocols CalculateEffectiveProtocols(SslAuthenticationOptions
return protocols;
}

internal static SafeSslContextHandle GetOrCreateSslContextHandle(SslAuthenticationOptions sslAuthenticationOptions, bool allowCached)
internal static SafeSslContextHandle GetOrCreateSslContextHandle(SslAuthenticationOptions sslAuthenticationOptions, bool allowCached, bool enableResume)
{
SslProtocols protocols = CalculateEffectiveProtocols(sslAuthenticationOptions);

if (!allowCached)
{
return AllocateSslContext(sslAuthenticationOptions, protocols, allowCached);
return AllocateSslContext(sslAuthenticationOptions, protocols, enableResume);
}

bool hasAlpn = sslAuthenticationOptions.ApplicationProtocols != null && sslAuthenticationOptions.ApplicationProtocols.Count != 0;
Expand All @@ -208,9 +208,9 @@ internal static SafeSslContextHandle GetOrCreateSslContextHandle(SslAuthenticati
sslAuthenticationOptions.CertificateContext);
return s_sslContexts.GetOrCreate(key, static (args) =>
{
var (sslAuthOptions, protocols, allowCached) = args;
return AllocateSslContext(sslAuthOptions, protocols, allowCached);
}, (sslAuthenticationOptions, protocols, allowCached));
var (sslAuthOptions, protocols, enableResume) = args;
return AllocateSslContext(sslAuthOptions, protocols, enableResume);
}, (sslAuthenticationOptions, protocols, enableResume));
}

// This essentially wraps SSL_CTX* aka SSL_CTX_new + setting
Expand Down Expand Up @@ -361,7 +361,16 @@ internal static void UpdateClientCertificate(SafeSslHandle ssl, SslAuthenticatio
internal static unsafe SafeSslHandle AllocateSslHandle(SslAuthenticationOptions sslAuthenticationOptions)
{
SafeSslHandle? sslHandle = null;
bool cacheSslContext = sslAuthenticationOptions.AllowTlsResume && !LocalAppContextSwitches.DisableTlsResume && sslAuthenticationOptions.EncryptionPolicy == EncryptionPolicy.RequireEncryption && sslAuthenticationOptions.CipherSuitesPolicy == null;
// When a TlsContext owns a long-lived SSL_CTX (set via PreallocatedSslContext)
// we bypass the global SslContextCacheKey lookup and the TLS-resume cache hung
// off it: the TlsContext is the resume scope. The handle is borrowed here, not
// owned, so the conditional Dispose() in the finally below skips it.
SafeSslContextHandle? preallocatedSslCtx = sslAuthenticationOptions.PreallocatedSslContext;
bool cacheSslContext = preallocatedSslCtx is null
&& sslAuthenticationOptions.AllowTlsResume
&& !LocalAppContextSwitches.DisableTlsResume
&& sslAuthenticationOptions.EncryptionPolicy == EncryptionPolicy.RequireEncryption
&& sslAuthenticationOptions.CipherSuitesPolicy == null;

if (cacheSslContext)
{
Expand Down Expand Up @@ -398,9 +407,13 @@ internal static unsafe SafeSslHandle AllocateSslHandle(SslAuthenticationOptions
// For uncached SafeSslContextHandles, the handle will be disposed and closed.
// Cached SafeSslContextHandles are returned with increaset rent count so that
// Dispose() here will not close the handle.
using SafeSslContextHandle sslCtxHandle = GetOrCreateSslContextHandle(sslAuthenticationOptions, cacheSslContext);

sslHandle = SafeSslHandle.Create(sslCtxHandle, sslAuthenticationOptions);
// When a preallocated SSL_CTX is provided (TlsContext-owned), we borrow it
// for the duration of this method without disposing — the TlsContext keeps
// it alive across every TlsSession it produces.
SafeSslContextHandle sslCtxHandle = preallocatedSslCtx ?? GetOrCreateSslContextHandle(sslAuthenticationOptions, cacheSslContext, cacheSslContext);
try
{
sslHandle = SafeSslHandle.Create(sslCtxHandle, sslAuthenticationOptions);
Debug.Assert(sslHandle != null, "Expected non-null return value from SafeSslHandle.Create");
if (sslHandle.IsInvalid)
{
Expand Down Expand Up @@ -524,6 +537,14 @@ internal static unsafe SafeSslHandle AllocateSslHandle(SslAuthenticationOptions
}
}
}
}
finally
{
if (preallocatedSslCtx is null)
{
sslCtxHandle.Dispose();
}
}

return sslHandle;
}
Expand Down Expand Up @@ -735,6 +756,15 @@ internal static unsafe SecurityStatusPalErrorCode DoSslHandshake(SafeSslHandle c
return SecurityStatusPalErrorCode.CredentialsNeeded;
}

if (errorCode == Ssl.SslErrorCode.SSL_ERROR_WANT_RETRY_VERIFY)
{
// OpenSSL 3.0+ retry-verify: the certificate verification
// callback paused the handshake. The application owns
// certificate validation and must resume the handshake
// (by calling DoSslHandshake again) once it has a verdict.
return SecurityStatusPalErrorCode.CertValidationNeeded;
}

if (errorCode == Ssl.SslErrorCode.SSL_ERROR_SSL && context.CertificateValidationException is Exception ex)
{
// Clear the OpenSSL error queue since we are using our own
Expand Down Expand Up @@ -1007,7 +1037,29 @@ internal static int CertVerifyCallback(IntPtr storeCtx, IntPtr arg)
.TryGetTarget(out SslAuthenticationOptions? options);
Debug.Assert(options != null, "Expected to get SslAuthenticationOptions from GCHandle");

sslHandle = (SafeSslHandle)options!.SslStream!._securityContext!;
sslHandle = options!.SafeSslHandle as SafeSslHandle;
Debug.Assert(sslHandle is not null, "Expected SslAuthenticationOptions.SafeSslHandle to be set by SafeSslHandle.Create");

// No in-callback validator (TlsSession path): accept the certificate here so
// the TLS handshake completes, then surface the peer cert to the caller via
// NeedsCertificateValidation on the next ProcessHandshake. Any subsequent
// Encrypt/Decrypt blocks until the caller posts a verdict.
//
// Ideally we would pause the handshake via SSL_set_retry_verify on OpenSSL 3.0+
// so a caller's reject can emit a fatal TLS alert to the peer mid-handshake.
// In practice SSL_set_retry_verify is not honored for peer-cert verification
// on either client or server SSLs in current upstream OpenSSL (the callback is
// not re-entered after SSL_do_handshake resumes). The native shim
// CryptoNative_SslSetRetryVerify, the SafeSslHandle.RetryVerifyAttempted /
// ExternalValidationAccepted fields, and TlsSession's PushExternalValidation-
// VerdictToPalIfRetryVerify are kept in place as dormant infrastructure; once
// upstream OpenSSL honors retry-verify, gate the SSL_set_retry_verify path in
// this branch behind a version check (or feature probe) to opt into it.
if (options.RemoteCertificateValidator is null)
{
Ssl.X509StoreCtxSetError(storeCtx, (int)Interop.Crypto.X509VerifyStatusCodeUniversal.X509_V_OK);
return 1;
}

// We need to note the number of certs in ExtraStore that were
// provided (by the user), we will add more from the received peer
Expand All @@ -1021,7 +1073,9 @@ internal static int CertVerifyCallback(IntPtr storeCtx, IntPtr arg)
try
{
ProtocolToken alertToken = default;
if (options.SslStream!.VerifyRemoteCertificate(certificate, chain, options.CertificateContext?.Trust, ref alertToken, out SslPolicyErrors sslPolicyErrors, out X509ChainStatusFlags chainStatus))
SslAuthenticationOptions.VerifyRemoteCertificateCallback? validator = options.RemoteCertificateValidator;
Debug.Assert(validator is not null, "Expected SslAuthenticationOptions.RemoteCertificateValidator to be set by SslStream or TlsSession");
if (validator!(certificate, chain, options.CertificateContext?.Trust, ref alertToken, out SslPolicyErrors sslPolicyErrors, out X509ChainStatusFlags chainStatus))
{
Ssl.X509StoreCtxSetError(storeCtx, (int)Interop.Crypto.X509VerifyStatusCodeUniversal.X509_V_OK);
return 1;
Expand Down
Loading
Loading