Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ internal static class SslStreamPal
// API is supported since Windows 10 1809 (17763) but there is no reason to use at the moment.
Environment.OSVersion.Version.Major >= 10 && Environment.OSVersion.Version.Build >= 18836;

// On Windows Server 2022 (build 20348) and older, Schannel has a race condition where
// ApplyControlToken(SSL_SESSION_DISABLE_RECONNECTS) doesn't reliably prevent the session
// cache from being repopulated. The workaround is to delete the context and retry
// InitializeSecurityContext after ApplyControlToken. This follows the same pattern used by
// Schannel's own webcli.c test and http.sys. The issue was fixed in newer Schannel builds
// shipping with Windows 11+ (build 22000+).
private static readonly bool NeedsDisableTlsResumeWorkaround =
Environment.OSVersion.Version.Build < 22000;

private const string SecurityPackage = "Microsoft Unified Security Protocol Provider";

private const Interop.SspiCli.ContextFlags RequiredFlags =
Expand Down Expand Up @@ -188,13 +197,6 @@ public static ProtocolToken InitializeSecurityContext(

token.Status = SecurityStatusAdapterPal.GetSecurityStatusPalFromNativeInt(errorCode);

consumed = inputBuffer.Length;
if (inputBuffers._item1.Type == SecurityBufferType.SECBUFFER_EXTRA)
{
// not all data were consumed
consumed -= inputBuffers._item1.Token.Length;
}

bool allowTlsResume = sslAuthenticationOptions.AllowTlsResume && !LocalAppContextSwitches.DisableTlsResume;

if (!allowTlsResume && newContext && context != null)
Expand All @@ -206,11 +208,44 @@ public static ProtocolToken InitializeSecurityContext(
ref context,
in securityBuffer));


if (result.ErrorCode != SecurityStatusPalErrorCode.OK)
{
token.Status = result;
}
else if (NeedsDisableTlsResumeWorkaround)
{
// On affected builds, Schannel's internal LookupCacheByName finds a fresh
// resumable entry and embeds the session ID in the ClientHello before
// ApplyControlToken can expire it. Deleting the context and retrying ISC
// ensures the new ClientHello is generated without a stale session ID.
// We can reuse inputBuffers since this only runs on the very first ISC call
// (newContext == true) where the input is empty.
context?.Dispose();
context = null;
token.ReleasePayload();
token = default;
token.RentBuffer = true;

errorCode = SSPIWrapper.InitializeSecurityContext(
GlobalSSPI.SSPISecureChannel,
ref credentialsHandle,
ref context,
targetName,
RequiredFlags | Interop.SspiCli.ContextFlags.InitManualCredValidation,
Interop.SspiCli.Endianness.SECURITY_NATIVE_DREP,
ref inputBuffers,
ref token,
ref unusedAttributes);

token.Status = SecurityStatusAdapterPal.GetSecurityStatusPalFromNativeInt(errorCode);
}
}

consumed = inputBuffer.Length;
if (inputBuffers._item1.Type == SecurityBufferType.SECBUFFER_EXTRA)
{
// not all data were consumed
consumed -= inputBuffers._item1.Token.Length;
}

return token;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ private bool CheckResumeFlag(SslStream ssl)
[ConditionalTheory]
[InlineData(true)]
[InlineData(false)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/103449", TestPlatforms.Windows)]
public async Task ClientDisableTlsResume_Succeeds(bool testClient)
{
SslServerAuthenticationOptions serverOptions = new SslServerAuthenticationOptions
Expand Down
Loading