< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 537
Coverable lines: 537
Total lines: 833
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 172
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Collections.Generic;
 5using System.ComponentModel;
 6using System.Diagnostics;
 7using System.Runtime.CompilerServices;
 8using System.Runtime.InteropServices;
 9using System.Security.Authentication;
 10using System.Security.Authentication.ExtendedProtection;
 11using System.Security.Cryptography.X509Certificates;
 12using System.Security.Principal;
 13using Microsoft.Win32.SafeHandles;
 14
 15namespace System.Net.Security
 16{
 17    internal static class SslStreamPal
 18    {
 019        private static readonly byte[] s_http1 = Interop.Sec_Application_Protocols.ToByteArray(new List<SslApplicationPr
 020        private static readonly byte[] s_http2 = Interop.Sec_Application_Protocols.ToByteArray(new List<SslApplicationPr
 021        private static readonly byte[] s_http12 = Interop.Sec_Application_Protocols.ToByteArray(new List<SslApplicationP
 022        private static readonly byte[] s_http21 = Interop.Sec_Application_Protocols.ToByteArray(new List<SslApplicationP
 23
 024        private static readonly bool UseNewCryptoApi =
 025            // On newer Windows version we use new API to get TLS1.3.
 026            // API is supported since Windows 10 1809 (17763) but there is no reason to use at the moment.
 027            Environment.OSVersion.Version.Major >= 10 && Environment.OSVersion.Version.Build >= 18836;
 28
 29        // On Windows Server 2022 (build 20348) and older, Schannel has a race condition where
 30        // ApplyControlToken(SSL_SESSION_DISABLE_RECONNECTS) doesn't reliably prevent the session
 31        // cache from being repopulated. The workaround is to delete the context and retry
 32        // InitializeSecurityContext after ApplyControlToken. This follows the same pattern used by
 33        // Schannel's own webcli.c test and http.sys. The issue was fixed in newer Schannel builds
 34        // shipping with Windows 11+ (build 22000+).
 035        private static readonly bool NeedsDisableTlsResumeWorkaround =
 036            Environment.OSVersion.Version.Build < 22000;
 37
 38        private const string SecurityPackage = "Microsoft Unified Security Protocol Provider";
 39
 40        private const Interop.SspiCli.ContextFlags RequiredFlags =
 41            Interop.SspiCli.ContextFlags.ReplayDetect |
 42            Interop.SspiCli.ContextFlags.SequenceDetect |
 43            Interop.SspiCli.ContextFlags.Confidentiality |
 44            Interop.SspiCli.ContextFlags.AllocateMemory;
 45
 46        private const Interop.SspiCli.ContextFlags ServerRequiredFlags =
 47            RequiredFlags | Interop.SspiCli.ContextFlags.AcceptStream | Interop.SspiCli.ContextFlags.AcceptExtendedError
 48
 49        public static Exception GetException(SecurityStatusPal status)
 050        {
 051            int win32Code = (int)SecurityStatusAdapterPal.GetInteropFromSecurityStatusPal(status);
 052            return new Win32Exception(win32Code);
 053        }
 54
 55        internal const bool StartMutualAuthAsAnonymous = true;
 56        internal const bool CertValidationInCallback = false;
 57        internal const bool CanEncryptEmptyMessage = true;
 58        internal const bool CanGenerateCustomAlerts = true;
 59
 60        internal static bool CanGenerateCustomAlertsForContext(SafeDeleteContext? _)
 061        {
 062            return CanGenerateCustomAlerts;
 063        }
 64
 065        private static readonly byte[] s_sessionTokenBuffer = InitSessionTokenBuffer();
 66
 67        private static byte[] InitSessionTokenBuffer()
 068        {
 069            var schannelSessionToken = new Interop.SChannel.SCHANNEL_SESSION_TOKEN()
 070            {
 071                dwTokenType = Interop.SChannel.SCHANNEL_SESSION,
 072                dwFlags = Interop.SChannel.SSL_SESSION_DISABLE_RECONNECTS,
 073            };
 074            return MemoryMarshal.AsBytes(new ReadOnlySpan<Interop.SChannel.SCHANNEL_SESSION_TOKEN>(in schannelSessionTok
 075        }
 76
 77        public static void VerifyPackageInfo()
 78        {
 79            SSPIWrapper.GetVerifyPackageInfo(GlobalSSPI.SSPISecureChannel, SecurityPackage, true);
 80        }
 81
 82        private static unsafe void SetAlpn(ref InputSecurityBuffers inputBuffers, List<SslApplicationProtocol> alpn, Spa
 083        {
 084            if (alpn.Count == 1 && alpn[0] == SslApplicationProtocol.Http11)
 085            {
 086                inputBuffers.SetNextBuffer(new InputSecurityBuffer(s_http1, SecurityBufferType.SECBUFFER_APPLICATION_PRO
 087            }
 088            else if (alpn.Count == 1 && alpn[0] == SslApplicationProtocol.Http2)
 089            {
 090                inputBuffers.SetNextBuffer(new InputSecurityBuffer(s_http2, SecurityBufferType.SECBUFFER_APPLICATION_PRO
 091            }
 092            else if (alpn.Count == 2 && alpn[0] == SslApplicationProtocol.Http11 && alpn[1] == SslApplicationProtocol.Ht
 093            {
 094                inputBuffers.SetNextBuffer(new InputSecurityBuffer(s_http12, SecurityBufferType.SECBUFFER_APPLICATION_PR
 095            }
 096            else if (alpn.Count == 2 && alpn[0] == SslApplicationProtocol.Http2 && alpn[1] == SslApplicationProtocol.Htt
 097            {
 098                inputBuffers.SetNextBuffer(new InputSecurityBuffer(s_http21, SecurityBufferType.SECBUFFER_APPLICATION_PR
 099            }
 100            else
 0101            {
 0102                int protocolLength = Interop.Sec_Application_Protocols.GetProtocolLength(alpn);
 0103                int bufferLength = sizeof(Interop.Sec_Application_Protocols) + protocolLength;
 104
 0105                Span<byte> alpnBuffer = bufferLength <= localBuffer.Length ? localBuffer : new byte[bufferLength];
 0106                Interop.Sec_Application_Protocols.SetProtocols(alpnBuffer, alpn, protocolLength);
 0107                inputBuffers.SetNextBuffer(new InputSecurityBuffer(alpnBuffer, SecurityBufferType.SECBUFFER_APPLICATION_
 0108            }
 0109        }
 110
 111        public static SecurityStatusPal SelectApplicationProtocol(
 112            SafeFreeCredentials? credentialsHandle,
 113            SafeDeleteSslContext? context,
 114            SslAuthenticationOptions sslAuthenticationOptions,
 115            ReadOnlySpan<byte> clientProtocols)
 0116        {
 0117            throw new PlatformNotSupportedException(nameof(SelectApplicationProtocol));
 118        }
 119
 120        public static unsafe ProtocolToken AcceptSecurityContext(
 121            ref SafeFreeCredentials? credentialsHandle,
 122            ref SafeDeleteSslContext? context,
 123            ReadOnlySpan<byte> inputBuffer,
 124            out int consumed,
 125            SslAuthenticationOptions sslAuthenticationOptions)
 0126        {
 0127            Interop.SspiCli.ContextFlags unusedAttributes = default;
 128
 0129            scoped InputSecurityBuffers inputBuffers = default;
 0130            inputBuffers.SetNextBuffer(new InputSecurityBuffer(inputBuffer, SecurityBufferType.SECBUFFER_TOKEN));
 0131            inputBuffers.SetNextBuffer(new InputSecurityBuffer(default, SecurityBufferType.SECBUFFER_EMPTY));
 0132            if (context == null && sslAuthenticationOptions.ApplicationProtocols != null && sslAuthenticationOptions.App
 0133            {
 0134                Span<byte> localBuffer = stackalloc byte[64];
 0135                SetAlpn(ref inputBuffers, sslAuthenticationOptions.ApplicationProtocols, localBuffer);
 0136            }
 137
 0138            ProtocolToken token = default;
 0139            token.RentBuffer = true;
 140
 0141            int errorCode = SSPIWrapper.AcceptSecurityContext(
 0142                GlobalSSPI.SSPISecureChannel,
 0143                credentialsHandle,
 0144                ref context,
 0145                ServerRequiredFlags | (sslAuthenticationOptions.RemoteCertRequired ? Interop.SspiCli.ContextFlags.Mutual
 0146                Interop.SspiCli.Endianness.SECURITY_NATIVE_DREP,
 0147                ref inputBuffers,
 0148                ref token,
 0149                ref unusedAttributes);
 150
 0151            consumed = inputBuffer.Length;
 0152            if (inputBuffers._item1.Type == SecurityBufferType.SECBUFFER_EXTRA)
 0153            {
 154                // not all data were consumed
 0155                consumed -= inputBuffers._item1.Token.Length;
 0156            }
 157
 0158            token.Status = SecurityStatusAdapterPal.GetSecurityStatusPalFromNativeInt(errorCode);
 0159            return token;
 0160        }
 161
 162        public static bool TryUpdateClintCertificate(
 163            SafeFreeCredentials? _1,
 164            SafeDeleteSslContext? _2,
 165            SslAuthenticationOptions _3)
 0166        {
 167            // We will need to allocate new credential handle
 0168            return false;
 0169        }
 170
 171        public static unsafe ProtocolToken InitializeSecurityContext(
 172            ref SafeFreeCredentials? credentialsHandle,
 173            ref SafeDeleteSslContext? context,
 174            string? targetName,
 175            ReadOnlySpan<byte> inputBuffer,
 176            out int consumed,
 177            SslAuthenticationOptions sslAuthenticationOptions)
 0178        {
 0179            bool newContext = context == null;
 0180            Interop.SspiCli.ContextFlags unusedAttributes = default;
 181
 0182            scoped InputSecurityBuffers inputBuffers = default;
 0183            inputBuffers.SetNextBuffer(new InputSecurityBuffer(inputBuffer, SecurityBufferType.SECBUFFER_TOKEN));
 0184            inputBuffers.SetNextBuffer(new InputSecurityBuffer(default, SecurityBufferType.SECBUFFER_EMPTY));
 0185            if (context == null && sslAuthenticationOptions.ApplicationProtocols != null && sslAuthenticationOptions.App
 0186            {
 0187                Span<byte> localBuffer = stackalloc byte[64];
 0188                SetAlpn(ref inputBuffers, sslAuthenticationOptions.ApplicationProtocols, localBuffer);
 0189            }
 190
 0191            ProtocolToken token = default;
 0192            token.RentBuffer = true;
 0193            int errorCode = SSPIWrapper.InitializeSecurityContext(
 0194                                GlobalSSPI.SSPISecureChannel,
 0195                                ref credentialsHandle,
 0196                                ref context,
 0197                                targetName,
 0198                                RequiredFlags | Interop.SspiCli.ContextFlags.InitManualCredValidation,
 0199                                Interop.SspiCli.Endianness.SECURITY_NATIVE_DREP,
 0200                                ref inputBuffers,
 0201                                ref token,
 0202                                ref unusedAttributes);
 203
 0204            token.Status = SecurityStatusAdapterPal.GetSecurityStatusPalFromNativeInt(errorCode);
 205
 0206            bool allowTlsResume = sslAuthenticationOptions.AllowTlsResume && !LocalAppContextSwitches.DisableTlsResume;
 207
 0208            if (!allowTlsResume && newContext && context != null)
 0209            {
 0210                var securityBuffer = new SecurityBuffer(s_sessionTokenBuffer, SecurityBufferType.SECBUFFER_TOKEN);
 211
 0212                SecurityStatusPal result = SecurityStatusAdapterPal.GetSecurityStatusPalFromNativeInt(SSPIWrapper.ApplyC
 0213                    GlobalSSPI.SSPISecureChannel,
 0214                    ref context,
 0215                    in securityBuffer));
 216
 0217                if (result.ErrorCode != SecurityStatusPalErrorCode.OK)
 0218                {
 0219                    token.Status = result;
 0220                }
 0221                else if (NeedsDisableTlsResumeWorkaround)
 0222                {
 223                    // On affected builds, Schannel's internal LookupCacheByName finds a fresh
 224                    // resumable entry and embeds the session ID in the ClientHello before
 225                    // ApplyControlToken can expire it. Deleting the context and retrying ISC
 226                    // ensures the new ClientHello is generated without a stale session ID.
 227                    // We can reuse inputBuffers since this only runs on the very first ISC call
 228                    // (newContext == true) where the input is empty.
 0229                    context?.Dispose();
 0230                    context = null;
 0231                    token.ReleasePayload();
 0232                    token = default;
 0233                    token.RentBuffer = true;
 234
 0235                    errorCode = SSPIWrapper.InitializeSecurityContext(
 0236                                    GlobalSSPI.SSPISecureChannel,
 0237                                    ref credentialsHandle,
 0238                                    ref context,
 0239                                    targetName,
 0240                                    RequiredFlags | Interop.SspiCli.ContextFlags.InitManualCredValidation,
 0241                                    Interop.SspiCli.Endianness.SECURITY_NATIVE_DREP,
 0242                                    ref inputBuffers,
 0243                                    ref token,
 0244                                    ref unusedAttributes);
 245
 0246                    token.Status = SecurityStatusAdapterPal.GetSecurityStatusPalFromNativeInt(errorCode);
 0247                }
 0248            }
 249
 0250            consumed = inputBuffer.Length;
 0251            if (inputBuffers._item1.Type == SecurityBufferType.SECBUFFER_EXTRA)
 0252            {
 253                // not all data were consumed
 0254                consumed -= inputBuffers._item1.Token.Length;
 0255            }
 256
 0257            return token;
 0258        }
 259
 260        public static ProtocolToken Renegotiate(
 261            ref SafeFreeCredentials? credentialsHandle,
 262            ref SafeDeleteSslContext? context,
 263            SslAuthenticationOptions sslAuthenticationOptions)
 0264        {
 0265            return AcceptSecurityContext(ref credentialsHandle, ref context, ReadOnlySpan<byte>.Empty, out _, sslAuthent
 0266        }
 267
 268        public static SafeFreeCredentials AcquireCredentialsHandle(SslAuthenticationOptions sslAuthenticationOptions, bo
 0269        {
 0270            SslStreamCertificateContext? certificateContext = sslAuthenticationOptions.CertificateContext;
 271
 272            try
 0273            {
 0274                EncryptionPolicy policy = sslAuthenticationOptions.EncryptionPolicy;
 275
 276                // New crypto API supports TLS1.3 but it does not allow to force NULL encryption.
 277#pragma warning disable SYSLIB0040 // NoEncryption and AllowNoEncryption are obsolete
 0278                SafeFreeCredentials cred = !UseNewCryptoApi || policy == EncryptionPolicy.NoEncryption ?
 0279                    AcquireCredentialsHandleSchannelCred(sslAuthenticationOptions) :
 0280                    AcquireCredentialsHandleSchCredentials(sslAuthenticationOptions);
 281#pragma warning restore SYSLIB0040
 282
 0283                if (certificateContext != null && certificateContext.Trust != null && certificateContext.Trust._sendTrus
 0284                {
 0285                    AttachCertificateStore(cred, certificateContext.Trust._store!);
 0286                }
 287
 288                // Windows can fail to get local credentials in case of TLS Resume.
 289                // We will store associated certificate in credentials and use it in case
 290                // of TLS resume. It will be disposed when the credentials are.
 0291                if (newCredentialsRequested && sslAuthenticationOptions.CertificateContext != null)
 0292                {
 0293                    SafeFreeCredential_SECURITY handle = (SafeFreeCredential_SECURITY)cred;
 0294                    handle.HasLocalCertificate = true;
 0295                }
 296
 0297                return cred;
 298            }
 0299            catch (Win32Exception e) when (e.NativeErrorCode == (int)Interop.SECURITY_STATUS.NoCredentials && certificat
 0300            {
 0301                Debug.Assert(certificateContext.TargetCertificate.HasPrivateKey);
 0302                using SafeCertContextHandle safeCertContextHandle = Interop.Crypt32.CertDuplicateCertificateContext(cert
 303                // on Windows we do not support ephemeral keys.
 0304                throw new AuthenticationException(safeCertContextHandle.HasEphemeralPrivateKey ? SR.net_auth_ephemeral :
 305            }
 0306            catch (Win32Exception e)
 0307            {
 0308                throw new AuthenticationException(SR.net_auth_SSPI, e);
 309            }
 0310        }
 311
 312        private static unsafe void AttachCertificateStore(SafeFreeCredentials cred, X509Store store)
 0313        {
 0314            Interop.SspiCli.SecPkgCred_ClientCertPolicy clientCertPolicy = default;
 0315            fixed (char* ptr = store.Name)
 0316            {
 0317                clientCertPolicy.pwszSslCtlStoreName = ptr;
 0318                Interop.SECURITY_STATUS errorCode = Interop.SspiCli.SetCredentialsAttributesW(
 0319                            cred._handle,
 0320                            (uint)Interop.SspiCli.ContextAttribute.SECPKG_ATTR_CLIENT_CERT_POLICY,
 0321                            clientCertPolicy,
 0322                            (uint)sizeof(Interop.SspiCli.SecPkgCred_ClientCertPolicy));
 323
 0324                if (errorCode != Interop.SECURITY_STATUS.OK)
 0325                {
 0326                    throw new Win32Exception((int)errorCode);
 327                }
 0328            }
 329
 0330            return;
 0331        }
 332
 333        // This is legacy crypto API used on older Windows versions.
 334        // It only supports TLS up to 1.2
 335        public static unsafe SafeFreeCredentials AcquireCredentialsHandleSchannelCred(SslAuthenticationOptions authOptio
 0336        {
 0337            X509Certificate2? certificate = authOptions.CertificateContext?.TargetCertificate;
 0338            bool isServer = authOptions.IsServer;
 0339            int protocolFlags = GetProtocolFlagsFromSslProtocols(authOptions.EnabledSslProtocols, isServer);
 0340            Interop.SspiCli.SCHANNEL_CRED.Flags flags = Interop.SspiCli.SCHANNEL_CRED.Flags.SCH_CRED_CACHE_ONLY_URL_RETR
 341            Interop.SspiCli.CredentialUse direction;
 342
 0343            bool allowTlsResume = authOptions.AllowTlsResume && !LocalAppContextSwitches.DisableTlsResume;
 344
 0345            if (!isServer)
 0346            {
 0347                direction = Interop.SspiCli.CredentialUse.SECPKG_CRED_OUTBOUND;
 0348                flags |=
 0349                    Interop.SspiCli.SCHANNEL_CRED.Flags.SCH_CRED_MANUAL_CRED_VALIDATION |
 0350                    Interop.SspiCli.SCHANNEL_CRED.Flags.SCH_CRED_NO_DEFAULT_CREDS |
 0351                    Interop.SspiCli.SCHANNEL_CRED.Flags.SCH_SEND_AUX_RECORD;
 352
 353                // Request OCSP Stapling from the server
 0354                if (authOptions.CertificateRevocationCheckMode != X509RevocationMode.NoCheck)
 0355                {
 0356                    flags |=
 0357                        Interop.SspiCli.SCHANNEL_CRED.Flags.SCH_CRED_REVOCATION_CHECK_END_CERT |
 0358                        Interop.SspiCli.SCHANNEL_CRED.Flags.SCH_CRED_IGNORE_NO_REVOCATION_CHECK |
 0359                        Interop.SspiCli.SCHANNEL_CRED.Flags.SCH_CRED_IGNORE_REVOCATION_OFFLINE;
 0360                }
 0361            }
 362            else
 0363            {
 0364                direction = Interop.SspiCli.CredentialUse.SECPKG_CRED_INBOUND;
 0365                flags |=
 0366                    Interop.SspiCli.SCHANNEL_CRED.Flags.SCH_SEND_AUX_RECORD |
 0367                    Interop.SspiCli.SCHANNEL_CRED.Flags.SCH_CRED_NO_SYSTEM_MAPPER;
 0368                if (!allowTlsResume)
 0369                {
 370                    // Works only on server
 0371                    flags |= Interop.SspiCli.SCHANNEL_CRED.Flags.SCH_CRED_DISABLE_RECONNECTS;
 0372                }
 0373            }
 374
 0375            EncryptionPolicy policy = authOptions.EncryptionPolicy;
 376
 377#pragma warning disable SYSLIB0040 // NoEncryption and AllowNoEncryption are obsolete
 378            // Always opt-in SCH_USE_STRONG_CRYPTO for TLS.
 0379            if (((protocolFlags == 0) ||
 0380                    (protocolFlags & ~(Interop.SChannel.SP_PROT_SSL2 | Interop.SChannel.SP_PROT_SSL3)) != 0)
 0381                    && (policy != EncryptionPolicy.AllowNoEncryption) && (policy != EncryptionPolicy.NoEncryption))
 0382            {
 0383                flags |= Interop.SspiCli.SCHANNEL_CRED.Flags.SCH_USE_STRONG_CRYPTO;
 0384            }
 385#pragma warning restore SYSLIB0040
 386
 0387            if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, $"flags=({flags}), ProtocolFlags=({protocolFla
 0388            Interop.SspiCli.SCHANNEL_CRED secureCredential = CreateSecureCredential(
 0389                flags,
 0390                protocolFlags,
 0391                policy);
 392
 0393            if (!isServer && !allowTlsResume)
 0394            {
 0395                secureCredential.dwSessionLifespan = -1;
 0396            }
 397
 398            Interop.Crypt32.CERT_CONTEXT* certificateHandle;
 0399            if (certificate != null)
 0400            {
 0401                secureCredential.cCreds = 1;
 0402                certificateHandle = (Interop.Crypt32.CERT_CONTEXT*)certificate.Handle;
 0403                secureCredential.paCred = &certificateHandle;
 0404            }
 405
 0406            return AcquireCredentialsHandle(direction, &secureCredential);
 0407        }
 408
 409        // This function uses new crypto API to support TLS 1.3 and beyond.
 410        public static unsafe SafeFreeCredentials AcquireCredentialsHandleSchCredentials(SslAuthenticationOptions authOpt
 0411        {
 0412            X509Certificate2? certificate = authOptions.CertificateContext?.TargetCertificate;
 0413            bool isServer = authOptions.IsServer;
 0414            int protocolFlags = GetProtocolFlagsFromSslProtocols(authOptions.EnabledSslProtocols, isServer);
 0415            Interop.SspiCli.SCH_CREDENTIALS.Flags flags = Interop.SspiCli.SCH_CREDENTIALS.Flags.SCH_CRED_CACHE_ONLY_URL_
 416            Interop.SspiCli.CredentialUse direction;
 417
 0418            bool allowTlsResume = authOptions.AllowTlsResume && !LocalAppContextSwitches.DisableTlsResume;
 419
 0420            if (isServer)
 0421            {
 0422                direction = Interop.SspiCli.CredentialUse.SECPKG_CRED_INBOUND;
 0423                flags |=
 0424                    Interop.SspiCli.SCH_CREDENTIALS.Flags.SCH_SEND_AUX_RECORD |
 0425                    Interop.SspiCli.SCH_CREDENTIALS.Flags.SCH_CRED_NO_SYSTEM_MAPPER;
 0426                if (!allowTlsResume)
 0427                {
 428                    // Works only on server
 0429                    flags |= Interop.SspiCli.SCH_CREDENTIALS.Flags.SCH_CRED_DISABLE_RECONNECTS;
 0430                }
 0431            }
 432            else
 0433            {
 0434                direction = Interop.SspiCli.CredentialUse.SECPKG_CRED_OUTBOUND;
 0435                flags |=
 0436                    Interop.SspiCli.SCH_CREDENTIALS.Flags.SCH_CRED_MANUAL_CRED_VALIDATION |
 0437                    Interop.SspiCli.SCH_CREDENTIALS.Flags.SCH_CRED_NO_DEFAULT_CREDS |
 0438                    Interop.SspiCli.SCH_CREDENTIALS.Flags.SCH_SEND_AUX_RECORD;
 439
 440                // Request OCSP Stapling from the server
 0441                if (authOptions.CertificateRevocationCheckMode != X509RevocationMode.NoCheck)
 0442                {
 0443                    flags |=
 0444                        Interop.SspiCli.SCH_CREDENTIALS.Flags.SCH_CRED_REVOCATION_CHECK_END_CERT |
 0445                        Interop.SspiCli.SCH_CREDENTIALS.Flags.SCH_CRED_IGNORE_NO_REVOCATION_CHECK |
 0446                        Interop.SspiCli.SCH_CREDENTIALS.Flags.SCH_CRED_IGNORE_REVOCATION_OFFLINE;
 0447                }
 0448            }
 449
 0450            EncryptionPolicy policy = authOptions.EncryptionPolicy;
 451
 0452            if (policy == EncryptionPolicy.RequireEncryption)
 0453            {
 454                // Always opt-in SCH_USE_STRONG_CRYPTO for TLS.
 0455                if ((protocolFlags & Interop.SChannel.SP_PROT_SSL3) == 0)
 0456                {
 0457                    flags |= Interop.SspiCli.SCH_CREDENTIALS.Flags.SCH_USE_STRONG_CRYPTO;
 0458                }
 0459            }
 460#pragma warning disable SYSLIB0040 // NoEncryption and AllowNoEncryption are obsolete
 0461            else if (policy == EncryptionPolicy.AllowNoEncryption)
 0462            {
 463                // Allow null encryption cipher in addition to other ciphers.
 0464                flags |= Interop.SspiCli.SCH_CREDENTIALS.Flags.SCH_ALLOW_NULL_ENCRYPTION;
 0465            }
 466#pragma warning restore SYSLIB0040
 467            else
 0468            {
 0469                throw new ArgumentException(SR.Format(SR.net_invalid_enum, "EncryptionPolicy"), nameof(policy));
 470            }
 471
 0472            Interop.SspiCli.SCH_CREDENTIALS credential = default;
 0473            credential.dwVersion = Interop.SspiCli.SCH_CREDENTIALS.CurrentVersion;
 0474            credential.dwFlags = flags;
 0475            if (!isServer && !allowTlsResume)
 0476            {
 0477                credential.dwSessionLifespan = -1;
 0478            }
 479
 480            Interop.Crypt32.CERT_CONTEXT* certificateHandle;
 0481            if (certificate != null)
 0482            {
 0483                credential.cCreds = 1;
 0484                certificateHandle = (Interop.Crypt32.CERT_CONTEXT*)certificate.Handle;
 0485                credential.paCred = &certificateHandle;
 0486            }
 487
 0488            if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, $"flags=({flags}), ProtocolFlags=({protocolFla
 489
 0490            Interop.SspiCli.TLS_PARAMETERS tlsParameters = default;
 0491            credential.cTlsParameters = 1;
 0492            credential.pTlsParameters = &tlsParameters;
 493
 0494            if (protocolFlags != 0)
 0495            {
 0496                tlsParameters.grbitDisabledProtocols = (uint)protocolFlags ^ uint.MaxValue;
 0497            }
 498
 0499            Span<Interop.SspiCli.CRYPTO_SETTINGS> cryptoSettings = stackalloc Interop.SspiCli.CRYPTO_SETTINGS[2];
 500
 501            // init to null ptrs to prevent freeing uninitialized memory in finally block
 0502            Span<IntPtr> algIdPtrs = stackalloc IntPtr[2] { IntPtr.Zero, IntPtr.Zero };
 0503            int cryptoSettingsCount = 0;
 504
 505            try
 0506            {
 0507                if (!authOptions.AllowRsaPkcs1Padding)
 0508                {
 0509                    algIdPtrs[cryptoSettingsCount] = Marshal.StringToHGlobalUni("SCH_RSA_PKCS_PAD");
 510
 0511                    cryptoSettings[cryptoSettingsCount] = new()
 0512                    {
 0513                        eAlgorithmUsage = Interop.SspiCli.CRYPTO_SETTINGS.TlsAlgorithmUsage.TlsParametersCngAlgUsageCert
 0514                    };
 515
 0516                    Interop.NtDll.RtlInitUnicodeString(out cryptoSettings[cryptoSettingsCount].strCngAlgId, algIdPtrs[cr
 0517                    cryptoSettingsCount++;
 0518                }
 519
 0520                if (!authOptions.AllowRsaPssPadding)
 0521                {
 0522                    algIdPtrs[cryptoSettingsCount] = Marshal.StringToHGlobalUni("SCH_RSA_PSS_PAD");
 523
 0524                    cryptoSettings[cryptoSettingsCount] = new()
 0525                    {
 0526                        eAlgorithmUsage = Interop.SspiCli.CRYPTO_SETTINGS.TlsAlgorithmUsage.TlsParametersCngAlgUsageCert
 0527                    };
 0528                    Interop.NtDll.RtlInitUnicodeString(out cryptoSettings[cryptoSettingsCount].strCngAlgId, algIdPtrs[cr
 0529                    cryptoSettingsCount++;
 0530                }
 531
 0532                tlsParameters.pDisabledCrypto = (Interop.SspiCli.CRYPTO_SETTINGS*)Unsafe.AsPointer(ref MemoryMarshal.Get
 0533                tlsParameters.cDisabledCrypto = cryptoSettingsCount;
 534
 0535                return AcquireCredentialsHandle(direction, &credential);
 536            }
 537            finally
 0538            {
 0539                foreach (IntPtr algIdPtr in algIdPtrs.Slice(0, cryptoSettingsCount))
 0540                {
 0541                    if (algIdPtr != IntPtr.Zero)
 0542                    {
 0543                        Marshal.FreeHGlobal(algIdPtr);
 0544                    }
 0545                }
 0546            }
 0547        }
 548
 549        public static unsafe ProtocolToken EncryptMessage(SafeDeleteSslContext securityContext, ReadOnlyMemory<byte> inp
 0550        {
 0551            ProtocolToken token = default;
 0552            token.RentBuffer = true;
 553
 554            // Ensure that there is sufficient space for the message output.
 0555            int bufferSizeNeeded = checked(input.Length + headerSize + trailerSize);
 0556            token.EnsureAvailableSpace(bufferSizeNeeded);
 557            // Copy the input into the output buffer to prepare for SCHANNEL's expectations
 0558            input.Span.CopyTo(token.AvailableSpan.Slice(headerSize, input.Length));
 559
 560            const int NumSecBuffers = 4; // header + data + trailer + empty
 0561            Span<Interop.SspiCli.SecBuffer> unmanagedBuffers = stackalloc Interop.SspiCli.SecBuffer[NumSecBuffers];
 0562            Interop.SspiCli.SecBufferDesc sdcInOut = new Interop.SspiCli.SecBufferDesc(NumSecBuffers)
 0563            {
 0564                pBuffers = Unsafe.AsPointer(ref MemoryMarshal.GetReference(unmanagedBuffers))
 0565            };
 0566            fixed (byte* outputPtr = token.Payload)
 0567            {
 0568                ref Interop.SspiCli.SecBuffer headerSecBuffer = ref unmanagedBuffers[0];
 0569                headerSecBuffer.BufferType = SecurityBufferType.SECBUFFER_STREAM_HEADER;
 0570                headerSecBuffer.pvBuffer = (IntPtr)outputPtr;
 0571                headerSecBuffer.cbBuffer = headerSize;
 572
 0573                ref Interop.SspiCli.SecBuffer dataSecBuffer = ref unmanagedBuffers[1];
 0574                dataSecBuffer.BufferType = SecurityBufferType.SECBUFFER_DATA;
 0575                dataSecBuffer.pvBuffer = (IntPtr)(outputPtr + headerSize);
 0576                dataSecBuffer.cbBuffer = input.Length;
 577
 0578                ref Interop.SspiCli.SecBuffer trailerSecBuffer = ref unmanagedBuffers[2];
 0579                trailerSecBuffer.BufferType = SecurityBufferType.SECBUFFER_STREAM_TRAILER;
 0580                trailerSecBuffer.pvBuffer = (IntPtr)(outputPtr + headerSize + input.Length);
 0581                trailerSecBuffer.cbBuffer = trailerSize;
 582
 0583                ref Interop.SspiCli.SecBuffer emptySecBuffer = ref unmanagedBuffers[3];
 0584                emptySecBuffer.BufferType = SecurityBufferType.SECBUFFER_EMPTY;
 0585                emptySecBuffer.cbBuffer = 0;
 0586                emptySecBuffer.pvBuffer = IntPtr.Zero;
 587
 0588                int errorCode = GlobalSSPI.SSPISecureChannel.EncryptMessage(securityContext, ref sdcInOut, 0);
 589
 0590                if (errorCode != 0)
 0591                {
 0592                    if (NetEventSource.Log.IsEnabled())
 0593                        NetEventSource.Info(securityContext, $"Encrypt ERROR {errorCode:X}");
 0594                    token.Size = 0;
 0595                    token.Status = SecurityStatusAdapterPal.GetSecurityStatusPalFromNativeInt(errorCode);
 0596                    return token;
 597                }
 598
 0599                Debug.Assert(headerSecBuffer.cbBuffer >= 0 && dataSecBuffer.cbBuffer >= 0 && trailerSecBuffer.cbBuffer >
 0600                Debug.Assert(checked(headerSecBuffer.cbBuffer + dataSecBuffer.cbBuffer + trailerSecBuffer.cbBuffer) <= t
 601
 0602                token.Size = checked(headerSecBuffer.cbBuffer + dataSecBuffer.cbBuffer + trailerSecBuffer.cbBuffer);
 0603                token.Status = new SecurityStatusPal(SecurityStatusPalErrorCode.OK);
 0604            }
 605
 0606            return token;
 0607        }
 608
 609        public static unsafe SecurityStatusPal DecryptMessage(
 610            SafeDeleteSslContext? securityContext,
 611            Span<byte> encrypted,
 612            Span<byte> destination,
 613            out int bytesWritten,
 614            out int leftoverOffset,
 615            out int leftoverLength)
 0616        {
 617            // SChannel always decrypts in-place; the caller-provided `destination` is unused.
 0618            _ = destination;
 0619            bytesWritten = 0;
 620            const int NumSecBuffers = 4; // data + empty + empty + empty
 621
 0622            Span<Interop.SspiCli.SecBuffer> unmanagedBuffers = stackalloc Interop.SspiCli.SecBuffer[NumSecBuffers];
 0623            for (int i = 1; i < NumSecBuffers; i++)
 0624            {
 0625                ref Interop.SspiCli.SecBuffer emptyBuffer = ref unmanagedBuffers[i];
 0626                emptyBuffer.BufferType = SecurityBufferType.SECBUFFER_EMPTY;
 0627                emptyBuffer.pvBuffer = IntPtr.Zero;
 0628                emptyBuffer.cbBuffer = 0;
 0629            }
 630
 0631            fixed (byte* bufferPtr = encrypted)
 0632            {
 0633                ref Interop.SspiCli.SecBuffer dataBuffer = ref unmanagedBuffers[0];
 0634                dataBuffer.BufferType = SecurityBufferType.SECBUFFER_DATA;
 0635                dataBuffer.pvBuffer = (IntPtr)bufferPtr;
 0636                dataBuffer.cbBuffer = encrypted.Length;
 637
 0638                Interop.SspiCli.SecBufferDesc sdcInOut = new Interop.SspiCli.SecBufferDesc(NumSecBuffers)
 0639                {
 0640                    pBuffers = Unsafe.AsPointer(ref MemoryMarshal.GetReference(unmanagedBuffers))
 0641                };
 0642                Interop.SECURITY_STATUS errorCode = (Interop.SECURITY_STATUS)GlobalSSPI.SSPISecureChannel.DecryptMessage
 643
 644                // Decrypt may repopulate the sec buffers, likely with header + data + trailer + empty.
 645                // We need to find the data.
 0646                leftoverLength = 0;
 0647                leftoverOffset = 0;
 0648                for (int i = 0; i < NumSecBuffers; i++)
 0649                {
 650                    // Successfully decoded data and placed it at the following position in the buffer,
 0651                    if ((errorCode == Interop.SECURITY_STATUS.OK && unmanagedBuffers[i].BufferType == SecurityBufferType
 0652                        // or we failed to decode the data, here is the encoded data.
 0653                        || (errorCode != Interop.SECURITY_STATUS.OK && unmanagedBuffers[i].BufferType == SecurityBufferT
 0654                    {
 0655                        leftoverOffset = (int)((byte*)unmanagedBuffers[i].pvBuffer - bufferPtr);
 0656                        leftoverLength = unmanagedBuffers[i].cbBuffer;
 657
 658                        // destination is ignored on Windows. We always decrypt in place and we set leftoverOffset to in
 0659                        Debug.Assert(leftoverOffset >= 0 && leftoverLength >= 0, $"Expected offset and length greater th
 0660                        Debug.Assert(checked(leftoverOffset + leftoverLength) <= encrypted.Length, $"Expected offset+len
 661
 0662                        break;
 663                    }
 0664                }
 665
 0666                return SecurityStatusAdapterPal.GetSecurityStatusPalFromInterop(errorCode);
 667            }
 0668        }
 669
 670        public static SecurityStatusPal ApplyAlertToken(SafeDeleteSslContext? securityContext, TlsAlertType alertType, T
 0671        {
 0672            var alertToken = new Interop.SChannel.SCHANNEL_ALERT_TOKEN
 0673            {
 0674                dwTokenType = Interop.SChannel.SCHANNEL_ALERT,
 0675                dwAlertType = (uint)alertType,
 0676                dwAlertNumber = (uint)alertMessage
 0677            };
 0678            byte[] buffer = MemoryMarshal.AsBytes(new ReadOnlySpan<Interop.SChannel.SCHANNEL_ALERT_TOKEN>(in alertToken)
 0679            var securityBuffer = new SecurityBuffer(buffer, SecurityBufferType.SECBUFFER_TOKEN);
 680
 0681            var errorCode = (Interop.SECURITY_STATUS)SSPIWrapper.ApplyControlToken(
 0682                GlobalSSPI.SSPISecureChannel,
 0683                ref securityContext,
 0684                in securityBuffer);
 685
 0686            return SecurityStatusAdapterPal.GetSecurityStatusPalFromInterop(errorCode, attachException: true);
 0687        }
 688
 0689        private static readonly byte[] s_schannelShutdownBytes = BitConverter.GetBytes(Interop.SChannel.SCHANNEL_SHUTDOW
 690
 691        public static SecurityStatusPal ApplyShutdownToken(SafeDeleteSslContext? securityContext)
 0692        {
 0693            var securityBuffer = new SecurityBuffer(s_schannelShutdownBytes, SecurityBufferType.SECBUFFER_TOKEN);
 694
 0695            var errorCode = (Interop.SECURITY_STATUS)SSPIWrapper.ApplyControlToken(
 0696                GlobalSSPI.SSPISecureChannel,
 0697                ref securityContext,
 0698                in securityBuffer);
 699
 0700            return SecurityStatusAdapterPal.GetSecurityStatusPalFromInterop(errorCode, attachException: true);
 0701        }
 702
 703        public static SafeFreeContextBufferChannelBinding? QueryContextChannelBinding(SafeDeleteContext securityContext,
 0704        {
 0705            return SSPIWrapper.QueryContextChannelBinding(GlobalSSPI.SSPISecureChannel, securityContext, (Interop.SspiCl
 0706        }
 707
 708        public static void QueryContextStreamSizes(SafeDeleteContext securityContext, out StreamSizes streamSizes)
 0709        {
 0710            SecPkgContext_StreamSizes interopStreamSizes = default;
 0711            bool success = SSPIWrapper.QueryBlittableContextAttributes(GlobalSSPI.SSPISecureChannel, securityContext, In
 0712            Debug.Assert(success);
 0713            streamSizes = new StreamSizes(interopStreamSizes);
 0714        }
 715
 716        public static void QueryContextConnectionInfo(SafeDeleteContext securityContext, ref SslConnectionInfo connectio
 0717        {
 0718            connectionInfo.UpdateSslConnectionInfo(securityContext);
 0719        }
 720
 721        private static int GetProtocolFlagsFromSslProtocols(SslProtocols protocols, bool isServer)
 0722        {
 0723            int protocolFlags = (int)protocols;
 724
 0725            if (isServer)
 0726            {
 0727                protocolFlags &= Interop.SChannel.ServerProtocolMask;
 0728            }
 729            else
 0730            {
 0731                protocolFlags &= Interop.SChannel.ClientProtocolMask;
 0732            }
 733
 0734            return protocolFlags;
 0735        }
 736
 737        private static unsafe Interop.SspiCli.SCHANNEL_CRED CreateSecureCredential(
 738            Interop.SspiCli.SCHANNEL_CRED.Flags flags,
 739            int protocols, EncryptionPolicy policy)
 0740        {
 0741            var credential = new Interop.SspiCli.SCHANNEL_CRED()
 0742            {
 0743                hRootStore = IntPtr.Zero,
 0744                aphMappers = IntPtr.Zero,
 0745                palgSupportedAlgs = IntPtr.Zero,
 0746                paCred = null,
 0747                cCreds = 0,
 0748                cMappers = 0,
 0749                cSupportedAlgs = 0,
 0750                dwSessionLifespan = 0,
 0751                reserved = 0,
 0752                dwVersion = Interop.SspiCli.SCHANNEL_CRED.CurrentVersion
 0753            };
 754
 0755            if (policy == EncryptionPolicy.RequireEncryption)
 0756            {
 757                // Prohibit null encryption cipher.
 0758                credential.dwMinimumCipherStrength = 0;
 0759                credential.dwMaximumCipherStrength = 0;
 0760            }
 761#pragma warning disable SYSLIB0040 // NoEncryption and AllowNoEncryption are obsolete
 0762            else if (policy == EncryptionPolicy.AllowNoEncryption)
 0763            {
 764                // Allow null encryption cipher in addition to other ciphers.
 0765                credential.dwMinimumCipherStrength = -1;
 0766                credential.dwMaximumCipherStrength = 0;
 0767            }
 0768            else if (policy == EncryptionPolicy.NoEncryption)
 0769            {
 770                // Suppress all encryption and require null encryption cipher only
 0771                credential.dwMinimumCipherStrength = -1;
 0772                credential.dwMaximumCipherStrength = -1;
 0773            }
 774#pragma warning restore SYSLIB0040
 775            else
 0776            {
 0777                throw new ArgumentException(SR.Format(SR.net_invalid_enum, "EncryptionPolicy"), nameof(policy));
 778            }
 779
 0780            credential.dwFlags = flags;
 0781            credential.grbitEnabledProtocols = protocols;
 782
 0783            return credential;
 0784        }
 785
 786        //
 787        // Security: we temporarily reset thread token to open the handle under process account.
 788        //
 789        private static unsafe SafeFreeCredentials AcquireCredentialsHandle(Interop.SspiCli.CredentialUse credUsage, Inte
 0790        {
 791            // First try without impersonation, if it fails, then try the process account.
 792            // I.E. We don't know which account the certificate context was created under.
 793            try
 0794            {
 795                //
 796                // For app-compat we want to ensure the credential are accessed under >>process<< account.
 797                //
 0798                using SafeAccessTokenHandle invalidHandle = SafeAccessTokenHandle.InvalidHandle;
 0799                return WindowsIdentity.RunImpersonated<SafeFreeCredentials>(invalidHandle, () =>
 0800                {
 0801                    return SSPIWrapper.AcquireCredentialsHandle(GlobalSSPI.SSPISecureChannel, SecurityPackage, credUsage
 0802                });
 803            }
 0804            catch
 0805            {
 0806                return SSPIWrapper.AcquireCredentialsHandle(GlobalSSPI.SSPISecureChannel, SecurityPackage, credUsage, se
 807            }
 0808        }
 809
 810        private static unsafe SafeFreeCredentials AcquireCredentialsHandle(Interop.SspiCli.CredentialUse credUsage, Inte
 0811        {
 812            // First try without impersonation, if it fails, then try the process account.
 813            // I.E. We don't know which account the certificate context was created under.
 814            try
 0815            {
 816                //
 817                // For app-compat we want to ensure the credential are accessed under >>process<< account.
 818                //
 0819                using SafeAccessTokenHandle invalidHandle = SafeAccessTokenHandle.InvalidHandle;
 0820                return WindowsIdentity.RunImpersonated<SafeFreeCredentials>(invalidHandle, () =>
 0821                {
 0822                    return SSPIWrapper.AcquireCredentialsHandle(GlobalSSPI.SSPISecureChannel, SecurityPackage, credUsage
 0823                });
 824            }
 0825            catch
 0826            {
 0827                return SSPIWrapper.AcquireCredentialsHandle(GlobalSSPI.SSPISecureChannel, SecurityPackage, credUsage, se
 828            }
 0829        }
 830
 831    }
 832}
 833

Methods/Properties

.cctor()
GetException(System.Net.SecurityStatusPal)
CanGenerateCustomAlertsForContext(System.Net.Security.SafeDeleteContext)
InitSessionTokenBuffer()
SetAlpn(System.Net.Security.InputSecurityBuffers&,System.Collections.Generic.List`1<System.Net.Security.SslApplicationProtocol>,System.Span`1<System.Byte>)
SelectApplicationProtocol(System.Net.Security.SafeFreeCredentials,System.Net.Security.SafeDeleteSslContext,System.Net.Security.SslAuthenticationOptions,System.ReadOnlySpan`1<System.Byte>)
AcceptSecurityContext(System.Net.Security.SafeFreeCredentials&,System.Net.Security.SafeDeleteSslContext&,System.ReadOnlySpan`1<System.Byte>,System.Int32&,System.Net.Security.SslAuthenticationOptions)
TryUpdateClintCertificate(System.Net.Security.SafeFreeCredentials,System.Net.Security.SafeDeleteSslContext,System.Net.Security.SslAuthenticationOptions)
InitializeSecurityContext(System.Net.Security.SafeFreeCredentials&,System.Net.Security.SafeDeleteSslContext&,System.String,System.ReadOnlySpan`1<System.Byte>,System.Int32&,System.Net.Security.SslAuthenticationOptions)
Renegotiate(System.Net.Security.SafeFreeCredentials&,System.Net.Security.SafeDeleteSslContext&,System.Net.Security.SslAuthenticationOptions)
AcquireCredentialsHandle(System.Net.Security.SslAuthenticationOptions,System.Boolean)
AttachCertificateStore(System.Net.Security.SafeFreeCredentials,System.Security.Cryptography.X509Certificates.X509Store)
AcquireCredentialsHandleSchannelCred(System.Net.Security.SslAuthenticationOptions)
AcquireCredentialsHandleSchCredentials(System.Net.Security.SslAuthenticationOptions)
EncryptMessage(System.Net.Security.SafeDeleteSslContext,System.ReadOnlyMemory`1<System.Byte>,System.Int32,System.Int32)
DecryptMessage(System.Net.Security.SafeDeleteSslContext,System.Span`1<System.Byte>,System.Span`1<System.Byte>,System.Int32&,System.Int32&,System.Int32&)
ApplyAlertToken(System.Net.Security.SafeDeleteSslContext,System.Net.Security.TlsAlertType,System.Net.Security.TlsAlertMessage)
ApplyShutdownToken(System.Net.Security.SafeDeleteSslContext)
QueryContextChannelBinding(System.Net.Security.SafeDeleteContext,System.Security.Authentication.ExtendedProtection.ChannelBindingKind)
QueryContextStreamSizes(System.Net.Security.SafeDeleteContext,System.Net.StreamSizes&)
QueryContextConnectionInfo(System.Net.Security.SafeDeleteContext,System.Net.Security.SslConnectionInfo&)
GetProtocolFlagsFromSslProtocols(System.Security.Authentication.SslProtocols,System.Boolean)
CreateSecureCredential(Interop/SspiCli/SCHANNEL_CRED/Flags,System.Int32,System.Net.Security.EncryptionPolicy)
AcquireCredentialsHandle(Interop/SspiCli/CredentialUse,Interop/SspiCli/SCHANNEL_CRED*)
AcquireCredentialsHandle(Interop/SspiCli/CredentialUse,Interop/SspiCli/SCH_CREDENTIALS*)