Skip to content
Closed
Show file tree
Hide file tree
Changes from 13 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
757 changes: 757 additions & 0 deletions TlsSession-proposal.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -874,7 +874,8 @@ 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");

// 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 @@ -888,7 +889,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
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,10 @@ public static SafeSslHandle Create(SafeSslContextHandle context, SslAuthenticati
handle._authOptionsHandle = new WeakGCHandle<SslAuthenticationOptions>(options);
Interop.Ssl.SslSetData(handle, WeakGCHandle<SslAuthenticationOptions>.ToIntPtr(handle._authOptionsHandle));

// CertVerifyCallback needs the SafeSslHandle to stash a
// CertificateValidationException; expose it via the options.
options.SafeSslHandle = handle;
Comment on lines +579 to +581

Comment on lines 575 to +582
Comment on lines 575 to +582
Comment on lines 575 to +582
// SslSetBio will transfer ownership of the BIO handles to the SSL context
try
{
Expand Down
45 changes: 45 additions & 0 deletions src/libraries/System.Net.Security/ref/System.Net.Security.cs
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,51 @@ public enum TlsCipherSuite : ushort
TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256 = (ushort)53251,
TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256 = (ushort)53253,
}
public enum TlsOperationStatus
{
Complete = 0,
WantRead = 1,
WantWrite = 2,
Closed = 3,
WantCredentials = 4,
NeedsCertificateValidation = 5,
}
Comment on lines +691 to +700
Comment on lines +691 to +700
Comment on lines +691 to +700
Comment on lines +691 to +700
public sealed partial class TlsContext : System.IDisposable
{
internal TlsContext() { }
public bool IsServer { get { throw null; } }
public bool UseExternalCertificateValidation { get { throw null; } set { } }
public static System.Net.Security.TlsContext Create(System.Net.Security.SslServerAuthenticationOptions options) { throw null; }
public static System.Net.Security.TlsContext Create(System.Net.Security.SslClientAuthenticationOptions options) { throw null; }
public void Dispose() { }
Comment on lines +691 to +707
}
public sealed partial class TlsSession : System.IDisposable
{
internal TlsSession() { }
public bool IsServer { get { throw null; } }
public bool IsHandshakeComplete { get { throw null; } }
public bool HasPendingOutput { get { throw null; } }
public string? TargetHostName { get { throw null; } set { } }
public System.Security.Authentication.SslProtocols NegotiatedProtocol { get { throw null; } }
[System.CLSCompliantAttribute(false)]
public System.Net.Security.TlsCipherSuite NegotiatedCipherSuite { get { throw null; } }
public System.Net.Security.SslApplicationProtocol NegotiatedApplicationProtocol { get { throw null; } }
public static System.Net.Security.TlsSession Create(System.Net.Security.TlsContext context) { throw null; }
public System.Net.Security.TlsOperationStatus ProcessHandshake(System.ReadOnlySpan<byte> input, System.Span<byte> output, out int consumed, out int produced) { throw null; }
public System.Net.Security.TlsOperationStatus Encrypt(System.ReadOnlySpan<byte> plaintext, System.Span<byte> ciphertext, out int consumed, out int produced) { throw null; }
public System.Net.Security.TlsOperationStatus Decrypt(System.ReadOnlySpan<byte> ciphertext, System.Span<byte> plaintext, out int consumed, out int produced) { throw null; }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the outs should be bytesConsumed, bytesWritten

public System.Net.Security.TlsOperationStatus Shutdown(System.Span<byte> ciphertext, out int produced) { throw null; }
public System.Net.Security.TlsOperationStatus DrainPendingOutput(System.Span<byte> ciphertext, out int produced) { throw null; }
public System.Security.Cryptography.X509Certificates.X509Certificate2? GetRemoteCertificate() { throw null; }
public System.Security.Cryptography.X509Certificates.X509Chain? GetRemoteCertificateChain() { throw null; }
public System.Net.Security.SslPolicyErrors AcceptWithDefaultValidation() { throw null; }
public void SetRemoteCertificateValidationResult(System.Net.Security.SslPolicyErrors errors) { }
public System.Security.Cryptography.X509Certificates.X509Certificate2? LocalCertificate { get { throw null; } }
public System.Security.Authentication.ExtendedProtection.ChannelBinding? GetChannelBinding(System.Security.Authentication.ExtendedProtection.ChannelBindingKind kind) { throw null; }
public System.Net.Security.TlsOperationStatus RequestClientCertificate(System.Span<byte> ciphertext, out int produced) { throw null; }
public System.Net.Security.TlsOperationStatus RequestRenegotiation(System.Span<byte> ciphertext, out int produced) { throw null; }
public void Dispose() { }
}
Comment on lines +691 to +733
}
namespace System.Security.Authentication
{
Expand Down
10 changes: 10 additions & 0 deletions src/libraries/System.Net.Security/src/System.Net.Security.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,16 @@
<Compile Include="System\Net\Security\SslStream.cs" />
<Compile Include="System\Net\Security\SslStream.IO.cs" />
<Compile Include="System\Net\Security\SslStream.Protocol.cs" />
<Compile Include="System\Net\Security\SslStream.TlsSessionWedge.cs"
Condition="'$(TargetPlatformIdentifier)' == 'linux' or '$(TargetPlatformIdentifier)' == 'freebsd' or '$(TargetPlatformIdentifier)' == 'windows'" />
Comment on lines +70 to +71

@am11 am11 Jun 1, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be just !TargetsOSX since other Unices also use libssl or its API compatible flavor.

<Compile Include="System\Net\Security\SslStream.NotUnix.cs"
Condition="'$(TargetPlatformIdentifier)' != 'linux' and '$(TargetPlatformIdentifier)' != 'freebsd' and '$(TargetPlatformIdentifier)' != 'windows'" />
<Compile Include="System\Net\Security\TlsContext.cs" />
<Compile Include="System\Net\Security\TlsOperationStatus.cs" />
<Compile Include="System\Net\Security\TlsSession.cs"
Condition="'$(TargetPlatformIdentifier)' == 'linux' or '$(TargetPlatformIdentifier)' == 'freebsd' or '$(TargetPlatformIdentifier)' == 'windows'" />
<Compile Include="System\Net\Security\TlsSession.Stub.cs"
Condition="'$(TargetPlatformIdentifier)' != 'linux' and '$(TargetPlatformIdentifier)' != 'freebsd' and '$(TargetPlatformIdentifier)' != 'windows'" />
<Compile Include="System\Net\Security\SslStreamCertificateContext.cs" />
<Compile Include="System\Net\Security\SslConnectionInfo.cs" />
<Compile Include="System\Net\Security\StreamSizes.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,32 +208,32 @@ public void SspiSelectedCipherSuite(
#pragma warning restore SYSLIB0058 // Use NegotiatedCipherSuite.

[NonEvent]
public void RemoteCertificateError(SslStream SslStream, string message) =>
RemoteCertificateError(GetHashCode(SslStream), message);
public void RemoteCertificateError(object sender, string message) =>
RemoteCertificateError(GetHashCode(sender), message);

[Event(RemoteCertificateErrorId, Level = EventLevel.Verbose)]
private void RemoteCertificateError(int sslStreamHash, string message) =>
WriteEvent(RemoteCertificateErrorId, sslStreamHash, message);

[NonEvent]
public void RemoteCertDeclaredValid(SslStream SslStream) =>
RemoteCertDeclaredValid(GetHashCode(SslStream));
public void RemoteCertDeclaredValid(object sender) =>
RemoteCertDeclaredValid(GetHashCode(sender));

[Event(RemoteVertificateValidId, Level = EventLevel.Verbose)]
private void RemoteCertDeclaredValid(int sslStreamHash) =>
WriteEvent(RemoteVertificateValidId, sslStreamHash);

[NonEvent]
public void RemoteCertHasNoErrors(SslStream SslStream) =>
RemoteCertHasNoErrors(GetHashCode(SslStream));
public void RemoteCertHasNoErrors(object sender) =>
RemoteCertHasNoErrors(GetHashCode(sender));

[Event(RemoteCertificateSuccessId, Level = EventLevel.Verbose)]
private void RemoteCertHasNoErrors(int sslStreamHash) =>
WriteEvent(RemoteCertificateSuccessId, sslStreamHash);

[NonEvent]
public void RemoteCertUserDeclaredInvalid(SslStream SslStream) =>
RemoteCertUserDeclaredInvalid(GetHashCode(SslStream));
public void RemoteCertUserDeclaredInvalid(object sender) =>
RemoteCertUserDeclaredInvalid(GetHashCode(sender));

[Event(RemoteCertificateInvalidId, Level = EventLevel.Verbose)]
private void RemoteCertUserDeclaredInvalid(int sslStreamHash) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,25 @@ internal void SetCertificateContextFromCert(X509Certificate2 certificate, bool?

#if !TARGET_WINDOWS && !SYSNETSECURITY_NO_OPENSSL
internal SslStream? SslStream { get; set; }

// Set by SafeSslHandle.Create so OpenSSL's CertVerifyCallback can stash
// a CertificateValidationException on the handle when validation fails.
// Typed as the base SafeHandle so this file compiles in test projects
// that don't include the OpenSSL interop sources.
internal System.Runtime.InteropServices.SafeHandle? SafeSslHandle { get; set; }

// Hook invoked by OpenSSL's CertVerifyCallback to drive remote
// certificate validation. Set by SslStream and by standalone TlsSession
// so both flows share the same callback plumbing.
internal delegate bool VerifyRemoteCertificateCallback(
X509Certificate2? certificate,
X509Chain? chain,
SslCertificateTrust? trust,
ref ProtocolToken alertToken,
out SslPolicyErrors sslPolicyErrors,
out X509ChainStatusFlags chainStatus);

internal VerifyRemoteCertificateCallback? RemoteCertificateValidator { get; set; }
#endif

public void Dispose()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,15 +114,14 @@ public bool Equals(SslCredKey other)
bool allowRsaPssPadding,
bool allowRsaPkcs1Padding)
{
var key = new SslCredKey(thumbPrint, (int)sslProtocols, isServer, encryptionPolicy, sendTrustList, checkRevocation, allowTlsResume, allowRsaPssPadding, allowRsaPkcs1Padding);

if (s_cachedCreds.IsEmpty)
{
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, $"Not found, Current Cache Count = {s_cachedCreds.Count}");
return null;
}

var key = new SslCredKey(thumbPrint, (int)sslProtocols, isServer, encryptionPolicy, sendTrustList, checkRevocation, allowTlsResume, allowRsaPssPadding, allowRsaPkcs1Padding);

//SafeCredentialReference? cached;
SafeFreeCredentials? credentials = GetCachedCredential(key);
if (credentials == null || credentials.IsClosed || credentials.IsInvalid || credentials.Expiry < DateTime.UtcNow)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Net.Security
{
// Stub partial impls for non-Linux/FreeBSD platforms. Always returns false so
// SslStream's existing PAL paths run unchanged.
public partial class SslStream
{
#pragma warning disable CA1822 // partial method signature must match the Unix impl which is non-static
private partial bool TryNextMessageViaTlsSession(ReadOnlySpan<byte> incomingBuffer, out ProtocolToken token, out int consumed)
{
token = default;
consumed = 0;
return false;
}
#pragma warning restore CA1822
}
}
Loading
Loading