< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 1310
Coverable lines: 1310
Total lines: 2536
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 552
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor()100%110%
AttachSocket(...)0%220%
SetSessionCertificateContext(...)0%880%
InitializeFromContext(...)100%110%
OnContextInitialized()100%110%
GetRemoteCertificate()0%10100%
GetRemoteCertificates()100%110%
AcceptWithDefaultValidation()0%10100%
SetRemoteCertificateValidationResult(...)0%20200%
GetClientHelloLength()0%440%
TryGetClientHelloBytes(...)0%660%
SetContext(...)0%12120%
SetClientCertificateContext(...)0%440%
GetAcceptableIssuers()0%880%
ThrowIfPendingExternalValidation()0%440%
DisposeExternalRemoteCertificates()0%440%
GetChannelBinding(...)0%440%
HandshakeBufferedCore(...)0%1001000%
WriteBufferedCore(...)0%14140%
ReadBufferedCore(...)0%26260%
TryDrainBufferedPlaintext(...)0%14140%
RequestClientCertificateBufferedCore(...)0%22220%
ShutdownBufferedCore(...)0%14140%
DrainPendingOutputCore(...)0%220%
AppendPending(...)0%220%
DrainTo(...)0%220%
EnsureDecryptScratch(...)0%660%
ThrowIfDisposed()100%110%
ThrowIfContextNotSet()0%220%
TryParseClientHello(...)0%660%
ResolveServerCertificateFromClientHello(...)0%14140%
ActiveCredentialsRef()0%220%
HandshakeStepForSslStream(...)0%440%
OnHandshakeCompleted()0%660%
CaptureRemoteCertificateForExternalValidation()0%24240%
EnsureCredentialsAcquired()0%880%
ProcessPostHandshakeMessage(...)0%660%
ThrowIfNotSocketBound()0%220%
TryDrainPendingToSocket(...)0%660%
HandshakeSocketCore()0%36360%
ReadSocketCore(...)0%26260%
WriteSocketCore(...)0%28280%
DriveBufferedOpOverSocket(...)0%14140%
ShutdownSocketCore()100%110%
RequestClientCertificateSocketCore()0%220%
Dispose()0%20200%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Net.Security/src/System/Net/Security/TlsSession.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.Buffers;
 5using System.Collections.Generic;
 6using System.Diagnostics;
 7using System.Diagnostics.CodeAnalysis;
 8using System.IO;
 9using System.Net.Sockets;
 10using System.Security.Authentication;
 11using System.Security.Authentication.ExtendedProtection;
 12using System.Security.Cryptography.X509Certificates;
 13// macOS PAL has two SafeDelete* derivatives (SecureTransport + Network.framework)
 14// and surfaces the base type in ref parameters. Use the base type for the security-context
 15// field on macOS so it lines up with the PAL ref signatures; other platforms keep the
 16// derived SafeDeleteSslContext.
 17#if TARGET_APPLE
 18using TlsSecurityContext = System.Net.Security.SafeDeleteContext;
 19#else
 20using TlsSecurityContext = System.Net.Security.SafeDeleteSslContext;
 21#endif
 22
 23namespace System.Net.Security
 24{
 25    /// <summary>
 26    /// Non-blocking TLS state machine that drives handshake and record
 27    /// processing from caller-supplied byte spans.
 28    /// </summary>
 29    /// <remarks>
 30    /// <para>
 31    /// Support is provided by the underlying platform, such as SChannel on Windows
 32    /// and OpenSSL on Linux.
 33    /// </para>
 34    /// <para>
 35    /// The session never performs any I/O. The caller drives ciphertext in/out
 36    /// via byte spans. Any ciphertext the TLS layer needs to send (handshake
 37    /// records, alerts, encrypted application data) is staged in an internal
 38    /// pending-output buffer and drained via <see cref="TlsBufferSession.DrainPendingOutput"/>.
 39    /// </para>
 40    /// <para>
 41    /// Contract: any operation may return <see cref="TlsOperationStatus.DestinationTooSmall"/>
 42    /// to indicate the caller must drain pending output before further progress
 43    /// is possible. The session does not consume new input while pending output
 44    /// is non-empty.
 45    /// </para>
 46    /// </remarks>
 47    [Experimental(Experimentals.LowLevelTlsDiagId, UrlFormat = Experimentals.SharedUrlFormat)]
 48    public abstract partial class TlsSession : IDisposable
 49    {
 50        // Matches StreamSizes.Default on Unix; conservative upper bound for a
 51        // single TLS record's plaintext payload.
 52        internal const int MaxRecordPlaintext = 16354;
 53
 54        // Nullable until SetContext is called. All operations that depend on a
 55        // configured context validate this at entry.
 56        private TlsContext? _context;
 057        private SslAuthenticationOptions _options = null!;
 58        private bool _ownsOptions;
 59        private bool _hasServerOptions;
 60        private TlsSecurityContext? _securityContext;
 61
 062        private ArrayBuffer _pendingBuffer = new ArrayBuffer(initialSize: 0, usePool: true);
 63
 64        // Server-side only: SNI-resolved host name captured from the client's
 65        // ClientHello. Kept session-local so parallel sessions built from a
 66        // deferred TlsContext (SNI-dispatching bootstrap) cannot race on the
 67        // shared _options bag. Empty until the first ClientHello has parsed.
 68        // On client-side sessions the target host lives on _options.TargetHost
 69        // (immutable after SetContext, set by the caller via
 70        // SslClientAuthenticationOptions).
 071        private string _sessionTargetHost = string.Empty;
 72
 73        private byte[]? _decryptScratch;
 74
 75        private bool _isHandshakeComplete;
 76        private bool _suppressInternalCertificateValidation;
 77        private bool _externalValidationPending;
 78        private bool _externalValidationResolved;
 79        // Server-side post-handshake client authentication. Set by
 80        // RequestClientCertificate once the renegotiation / TLS 1.3 CertificateRequest
 81        // has been staged; it re-arms the handshake state machine so the caller drives
 82        // the second handshake to completion through Handshake(). Reset once the staged
 83        // request bytes have been fully drained to the caller (so a DestinationTooSmall
 84        // drain-continuation re-enters RequestClientCertificate without re-initiating).
 85#if !TARGET_APPLE
 86        private bool _postHandshakeAuthActive;
 87#endif
 88        // Set by SetClientCertificateContext after a WantCredentials suspension; consumed by
 89        // the next ProcessHandshake to allow an empty-input re-entry past the frame guard.
 90        private bool _resumeAfterCredentials;
 91        // Set by SetRemoteCertificateValidationResult when the PAL paused mid-handshake
 92        // pending external certificate validation (SecureTransport on macOS). Consumed by
 93        // the next ProcessHandshake to allow an empty-input re-entry past the frame guard
 94        // so the PAL can produce the next handshake flight (or a fatal alert on reject).
 95        private bool _resumeAfterCertValidation;
 96        // Intermediate certs the peer sent (chain elements minus the leaf). The platform-built
 97        // X509Chain itself is never surfaced to TlsSession callers; AcceptWithDefaultValidation
 98        // rebuilds a fresh chain from this collection at validation time.
 99        private X509Certificate2Collection? _externalRemoteCertificates;
 100        private X509Certificate2? _externalPendingCert;
 101        private Exception? _externalValidationFault;
 102        // Set when the caller explicitly rejected the peer certificate via
 103        // SetRemoteCertificateValidationResult / AcceptWithDefaultValidation. Once set,
 104        // GetRemoteCertificate must not surface the refused cert even though the underlying
 105        // PAL security context may still hold it (SChannel keeps the peer cert on the context).
 106        private bool _remoteCertificateRejected;
 107        private SslClientHelloInfo? _clientHelloInfo;
 108        private byte[]? _clientHelloBytesBuffered;
 109        // Session-local credentials handle. Non-null once SetClientCertificateContext
 110        // has been called; from that point on, this session's PAL calls route through
 111        // ActiveCredentialsRef() and never touch the shared TlsContext.CredentialsHandle.
 112        // Disposed when the session is disposed.
 113        private SafeFreeCredentials? _sessionCredentialsHandle;
 114        // Session-local view of the CertificateContext. Initialized from _options at
 115        // SetContext time and every mutation (SetClientCertificateContext, the
 116        // server-side selector path) routes through SessionCertificateContext so parallel
 117        // sessions built from the same TlsContext template never race on the shared cert
 118        // slot. _ownsSessionCertificateContext tracks whether the session itself built
 119        // this context (only true when constructed via SslStreamCertificateContext.Create
 120        // in the server-cert-selector path); Dispose releases it iff owned. The PAL
 121        // signature still reads _options.CertificateContext, so the setter mirrors the
 122        // new value onto the per-session cloned options bag; that mirror is the single
 123        // point that goes away when the PAL is later reshaped to consume the session
 124        // directly.
 125        private SslStreamCertificateContext? _sessionCertificateContext;
 126        private bool _ownsSessionCertificateContext;
 127        private bool _disposed;
 128        private SslConnectionInfo _connectionInfo;
 129        private X509Certificate2? _remoteCertificate;
 130        private int _headerSize;
 131        private int _trailerSize;
 0132        private int _maxDataSize = MaxRecordPlaintext;
 133
 134        // Socket-bound mode (optional). When set, the session performs its own
 135        // non-blocking I/O via Handshake/Read/Write. The session takes ownership
 136        // of the supplied socket handle and disposes it with the session.
 137        private SafeSocketHandle? _socketHandle;
 138        private Socket? _socket;
 0139        private ArrayBuffer _socketInBuffer = new ArrayBuffer(initialSize: 0, usePool: true);
 140
 0141        private protected TlsSession()
 0142        {
 0143        }
 144
 145        // Called from TlsSocketSession.OnContextInitialized (right after the base
 146        // InitializeFromContext runs) to bind a socket handle for the socket-bound I/O
 147        // path. Must be called exactly once per session, and only before any
 148        // Handshake / Read / Write / Shutdown call has touched the PAL. The socket is
 149        // taken to ownership and disposed with the session. Platforms with a native
 150        // fd-binding fast path (OpenSSL) take the socket directly; otherwise the
 151        // socket is wrapped in a managed Socket for the buffered I/O path.
 152        private protected void AttachSocket(SafeSocketHandle socket)
 0153        {
 0154            Debug.Assert(socket != null);
 0155            Debug.Assert(!_disposed, "AttachSocket called on a disposed session");
 0156            Debug.Assert(_socketHandle is null, "AttachSocket called twice on the same session");
 0157            Debug.Assert(_socket is null, "AttachSocket called after the managed Socket wrapper was already created");
 0158            Debug.Assert(_securityContext is null, "AttachSocket called after the PAL security context was already alloc
 0159            _socketHandle = socket;
 160
 0161            bool nativeBindingEnabled = false;
 162            EnableNativeSocketBinding(socket, ref nativeBindingEnabled);
 0163            if (!nativeBindingEnabled)
 0164            {
 0165                _socket = new Socket(socket);
 0166            }
 0167        }
 168
 169        internal SafeSocketHandle? SocketHandle => _socketHandle;
 170
 0171        private SslStreamCertificateContext? SessionCertificateContext => _sessionCertificateContext;
 172
 173        private void SetSessionCertificateContext(SslStreamCertificateContext? context, bool takeOwnership)
 0174        {
 0175            if (_ownsSessionCertificateContext && _sessionCertificateContext is not null && !ReferenceEquals(_sessionCer
 0176            {
 0177                _sessionCertificateContext.ReleaseResources();
 0178            }
 179
 0180            _sessionCertificateContext = context;
 0181            _ownsSessionCertificateContext = takeOwnership && context is not null;
 182
 183            // Mirror onto the per-session cloned options bag so the PAL (which reads
 184            // _options.CertificateContext directly) sees the effective value. Also flip
 185            // the bag's own ownership bit off â€” session now owns the disposal decision.
 0186            _options.CertificateContext = context;
 0187            _options.OwnsCertificateContext = false;
 0188        }
 189
 190        private void InitializeFromContext(TlsContext context)
 0191        {
 0192            Debug.Assert(_context is null);
 0193            Debug.Assert(context is not null);
 0194            _context = context;
 0195            _ownsOptions = !context.ShareOptions;
 0196            _options = context.CreateSessionOptions();
 0197            _hasServerOptions = context.TemplateHasServerOptions;
 198
 199            // Transfer CertificateContext ownership from the per-session options clone
 200            // to the session so subsequent mutations (SetClientCertificateContext,
 201            // server-selector build) live entirely on TlsSession's own fields. The bag
 202            // itself never owns after this point; TlsSession.Dispose is the sole releaser.
 0203            _sessionCertificateContext = _options.CertificateContext;
 0204            _ownsSessionCertificateContext = _options.OwnsCertificateContext;
 0205            _options.OwnsCertificateContext = false;
 206
 0207            OnContextInitialized();
 0208        }
 209
 210        internal virtual void OnContextInitialized()
 0211        {
 0212        }
 213
 214
 215        // â”€â”€ State â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
 216
 0217        public bool IsHandshakeComplete => _isHandshakeComplete;
 218
 0219        public bool HasPendingOutput => _pendingBuffer.ActiveLength > 0;
 220
 221        /// <summary>
 222        /// Target host name for this session. On the client this is the value the
 223        /// caller supplied via <see cref="SslClientAuthenticationOptions.TargetHost"/>
 224        /// (used for SNI and hostname validation). On the server this is the SNI value
 225        /// parsed from the peer's ClientHello, or <see langword="null"/> if no
 226        /// ClientHello has been processed yet or the ClientHello carried no SNI
 227        /// extension. Setting the value on either side overrides the current value;
 228        /// setting to <see langword="null"/> clears it.
 229        /// </summary>
 230        public string? TargetHostName
 231        {
 232            get
 0233            {
 0234                ThrowIfContextNotSet();
 0235                if (_context!.IsServer)
 0236                {
 0237                    return string.IsNullOrEmpty(_sessionTargetHost) ? null : _sessionTargetHost;
 238                }
 0239                return string.IsNullOrEmpty(_options.TargetHost) ? null : _options.TargetHost;
 0240            }
 241            set
 0242            {
 0243                ThrowIfContextNotSet();
 0244                if (_context!.IsServer)
 0245                {
 0246                    _sessionTargetHost = value ?? string.Empty;
 0247                }
 248                else
 0249                {
 0250                    _options.TargetHost = value ?? string.Empty;
 0251                }
 0252            }
 253        }
 254
 255        public SslProtocols NegotiatedProtocol
 256        {
 257            get
 0258            {
 0259                if (!_isHandshakeComplete || _connectionInfo.Protocol == 0)
 0260                {
 0261                    return SslProtocols.None;
 262                }
 263
 264                // On Windows (SChannel), the reported protocol value carries
 265                // client/server direction bits (SP_PROT_TLS1_2_CLIENT == 0x800,
 266                // SP_PROT_TLS1_2_SERVER == 0x400, etc.). Canonicalize to the
 267                // managed SslProtocols enum values, matching SslStream.
 0268                SslProtocols proto = (SslProtocols)_connectionInfo.Protocol;
 0269                SslProtocols ret = SslProtocols.None;
 270#pragma warning disable 0618
 0271                if ((proto & SslProtocols.Ssl2) != 0) ret |= SslProtocols.Ssl2;
 0272                if ((proto & SslProtocols.Ssl3) != 0) ret |= SslProtocols.Ssl3;
 273#pragma warning restore
 274#pragma warning disable SYSLIB0039
 0275                if ((proto & SslProtocols.Tls) != 0) ret |= SslProtocols.Tls;
 0276                if ((proto & SslProtocols.Tls11) != 0) ret |= SslProtocols.Tls11;
 277#pragma warning restore SYSLIB0039
 0278                if ((proto & SslProtocols.Tls12) != 0) ret |= SslProtocols.Tls12;
 0279                if ((proto & SslProtocols.Tls13) != 0) ret |= SslProtocols.Tls13;
 0280                return ret;
 0281            }
 282        }
 283
 284        [System.CLSCompliant(false)]
 285        public TlsCipherSuite NegotiatedCipherSuite =>
 0286            _isHandshakeComplete ? (TlsCipherSuite)_connectionInfo.TlsCipherSuite : default;
 287
 288        public SslApplicationProtocol NegotiatedApplicationProtocol
 289        {
 290            get
 0291            {
 0292                if (!_isHandshakeComplete || _connectionInfo.ApplicationProtocol == null)
 0293                {
 0294                    return default;
 295                }
 0296                return new SslApplicationProtocol(_connectionInfo.ApplicationProtocol);
 0297            }
 298        }
 299
 300        public X509Certificate2? GetRemoteCertificate()
 0301        {
 0302            if (_remoteCertificate is not null)
 0303            {
 0304                return _remoteCertificate;
 305            }
 306
 0307            if (_externalPendingCert is not null)
 0308            {
 0309                return _externalPendingCert;
 310            }
 311
 312            // The caller rejected the peer certificate; do not fall back to the PAL, which would
 313            // re-surface the refused cert still held on the underlying security context (SChannel).
 0314            if (_remoteCertificateRejected)
 0315            {
 0316                return null;
 317            }
 318
 0319            if (_securityContext == null || _securityContext.IsInvalid)
 0320            {
 0321                return null;
 322            }
 0323            return CertificateValidationPal.GetRemoteCertificate(_securityContext);
 0324        }
 325
 326        /// <summary>
 327        /// Returns the intermediate certificates the peer sent alongside its leaf certificate
 328        /// (the leaf itself is available via <see cref="GetRemoteCertificate"/>), or <c>null</c>
 329        /// if no intermediates were received. Only meaningful while the session is awaiting an
 330        /// external validation result (after <see cref="TlsBufferSession.Handshake"/> returned
 331        /// <see cref="TlsOperationStatus.NeedsCertificateValidation"/>). The certificates are
 332        /// owned by the session and disposed when the session is disposed or when the validation
 333        /// result is recorded; callers that need to retain them must clone the instances.
 334        /// </summary>
 335        public X509Certificate2Collection? GetRemoteCertificates()
 0336        {
 0337            ThrowIfDisposed();
 0338            return _externalRemoteCertificates;
 0339        }
 340
 341        /// <summary>
 342        /// Runs the same validation <see cref="SslStream"/> performs (default chain
 343        /// build plus any user-supplied <see cref="SslClientAuthenticationOptions.RemoteCertificateValidationCallback"/
 344        /// on the underlying options), records the result on the session, and returns
 345        /// the effective <see cref="SslPolicyErrors"/>. Intended for callers that want
 346        /// <see cref="SslStream"/>-compatible semantics without writing their own
 347        /// validation logic.
 348        /// </summary>
 349        /// <remarks>
 350        /// Must be called only after <see cref="TlsBufferSession.Handshake"/> returned
 351        /// <see cref="TlsOperationStatus.NeedsCertificateValidation"/> and before
 352        /// <see cref="SetRemoteCertificateValidationResult"/> is called.
 353        /// </remarks>
 354        public SslPolicyErrors AcceptWithDefaultValidation()
 0355        {
 0356            ThrowIfDisposed();
 0357            if (!_externalValidationPending)
 0358            {
 0359                throw new InvalidOperationException(
 0360                    SR.Format(SR.net_tlssession_validation_not_pending, nameof(AcceptWithDefaultValidation)));
 361            }
 362
 363            // Build a fresh X509Chain locally. VerifyRemoteCertificateCore applies the configured
 364            // chain policy before adding the peer-sent intermediates captured by this session.
 0365            using X509Chain chain = new X509Chain();
 366
 0367            ProtocolToken alertToken = default;
 0368            SslPolicyErrors sslPolicyErrors = SslPolicyErrors.None;
 369            bool ok;
 370            try
 0371            {
 372                // Pass _externalPendingCert as the candidate cert and an empty _remoteCertificate slot.
 373                // VerifyRemoteCertificateCore assigns the slot to the candidate on success; the renegotiation
 374                // shortcut at the top of that method would otherwise dispose our cert if the slot were already
 375                // populated with the same instance.
 0376                ok = SslStream.VerifyRemoteCertificateCore(
 0377                    this,
 0378                    // The external certificate is being (re)validated after the handshake, so the
 0379                    // resumption shortcut in VerifyRemoteCertificateCore must not apply here.
 0380                    isInitialHandshake: false,
 0381                    _options,
 0382                    _securityContext,
 0383                    ref _remoteCertificate,
 0384                    ref _connectionInfo,
 0385                    _externalPendingCert,
 0386                    chain,
 0387                    trust: null,
 0388                    ref alertToken,
 0389                    ref sslPolicyErrors,
 0390                    out _,
 0391                    out _,
 0392                    _externalRemoteCertificates,
 0393                    cloneCertificateChainPolicy: true);
 0394            }
 395            finally
 0396            {
 397                // Dispose the certificates that chain.Build() populated into ChainElements so
 398                // they don't linger until GC. Mirrors SslStream.VerifyRemoteCertificate's cleanup
 399                // when no user callback is provided. ExtraStore entries were supplied by the caller
 400                // in _externalRemoteCertificates and are intentionally left alone.
 0401                int elementsCount = chain.ChainElements.Count;
 0402                for (int i = 0; i < elementsCount; i++)
 0403                {
 0404                    chain.ChainElements[i].Certificate.Dispose();
 0405                }
 0406            }
 407
 408            // A user RemoteCertificateValidationCallback can reject an otherwise-clean chain
 409            // by returning false with sslPolicyErrors == None. Synthesize a non-None failure
 410            // so SetRemoteCertificateValidationResult takes the reject branch instead of accepting.
 0411            if (!ok && sslPolicyErrors == SslPolicyErrors.None)
 0412            {
 0413                sslPolicyErrors = SslPolicyErrors.RemoteCertificateChainErrors;
 0414            }
 415
 416            // On success VerifyRemoteCertificateCore set _remoteCertificate = _externalPendingCert, so
 417            // SetRemoteCertificateValidationResult below leaves it alone. On failure we must dispose the
 418            // pending cert ourselves because no one adopted it.
 0419            SetRemoteCertificateValidationResult(ok ? SslPolicyErrors.None : sslPolicyErrors);
 0420            return sslPolicyErrors;
 0421        }
 422
 423        /// <summary>
 424        /// Records the caller's external certificate-validation result.
 425        /// <see cref="SslPolicyErrors.None"/> means accept; any other value causes
 426        /// subsequent calls to <see cref="TlsBufferSession.Handshake"/>, <see cref="TlsBufferSession.Write"/>,
 427        /// and <see cref="TlsBufferSession.Read"/> to throw <see cref="AuthenticationException"/>.
 428        /// Must be called exactly once between
 429        /// <see cref="TlsOperationStatus.NeedsCertificateValidation"/> and the next
 430        /// session operation.
 431        /// </summary>
 432        public void SetRemoteCertificateValidationResult(SslPolicyErrors errors)
 0433        {
 0434            ThrowIfDisposed();
 0435            if (!_externalValidationPending)
 0436            {
 0437                throw new InvalidOperationException(
 0438                    SR.Format(SR.net_tlssession_validation_not_pending, nameof(SetRemoteCertificateValidationResult)));
 439            }
 440
 0441            _externalValidationPending = false;
 0442            _externalValidationResolved = true;
 443
 444            // If the PAL paused mid-handshake pending external validation (SecureTransport
 445            // on macOS returns errSSL{Server,Client}AuthCompleted before any handshake
 446            // response bytes are produced), the next ProcessHandshake call must be allowed
 447            // to re-enter the PAL with an empty input to drive the handshake forward
 448            // (produce ClientKeyExchange/Finished on accept, or a fatal alert on reject).
 449            // On OpenSSL and SChannel the suspension only fires after _isHandshakeComplete
 450            // is already true, so this resume flag is a no-op for those PALs.
 0451            if (!_isHandshakeComplete)
 0452            {
 0453                _resumeAfterCertValidation = true;
 0454            }
 455
 456#if !TARGET_WINDOWS && !SYSNETSECURITY_NO_OPENSSL
 457            // OpenSSL 3.0+ retry-verify path: the handshake paused inside the CertVerifyCallback.
 458            // Push the verdict to the SafeSslHandle so the next SSL_do_handshake call (driven by
 459            // the caller's next ProcessHandshake) re-invokes the callback and either accepts the
 460            // peer cert (Finished is emitted) or rejects it (a fatal alert is emitted).
 461            PushExternalValidationVerdictToPalIfRetryVerify(errors);
 462#endif
 463
 0464            if (errors == SslPolicyErrors.None)
 0465            {
 466                // Caller accepted. Promote the pending cert to the canonical remote-cert slot
 467                // (unless AcceptWithDefaultValidation already did so).
 0468                if (_remoteCertificate is null)
 0469                {
 0470                    _remoteCertificate = _externalPendingCert;
 0471                    _externalPendingCert = null;
 0472                }
 473                else
 0474                {
 475                    // VerifyRemoteCertificateCore adopted the cert into _remoteCertificate. Drop our copy.
 0476                    _externalPendingCert = null;
 0477                }
 0478            }
 479            else
 0480            {
 481                // The caller refused the peer certificate. Record the rejection so
 482                // GetRemoteCertificate does not later re-surface it from the PAL security context.
 0483                _remoteCertificateRejected = true;
 484
 485                // Post-hoc rejection (handshake already wire-complete on OpenSSL 1.1.x or Schannel):
 486                // surface the fault immediately so subsequent Encrypt/Decrypt throw. For the
 487                // retry-verify path the handshake is still incomplete and the fault is set when
 488                // ProcessHandshake drives SSL_do_handshake to failure (so any pending alert bytes
 489                // are drained to the caller first).
 0490                if (_isHandshakeComplete)
 0491                {
 0492                    _externalValidationFault = new AuthenticationException(SR.Format(SR.net_ssl_io_cert_validation, erro
 0493                }
 0494                else if (_resumeAfterCertValidation)
 0495                {
 496                    // Mid-handshake rejection. On OpenSSL 1.1.x / SecureTransport (macOS)
 497                    // the peer-verify callback took the accept-and-defer path, so the
 498                    // handshake will still complete on the wire; the caller's Write / Read
 499                    // must throw AuthenticationException afterwards. Set the fault now, but
 500                    // do NOT throw it from HandshakeBufferedCore -- let the PAL drive the
 501                    // handshake to completion silently so the peer doesn't hang waiting
 502                    // for our Finished. Write / Read guard on ThrowIfPendingExternalValidation
 503                    // which checks _externalValidationFault. On OpenSSL 3.0+ retry-verify the
 504                    // fault will instead be set by the natural token-failed branch when
 505                    // SSL_do_handshake emits the fatal alert.
 0506                    _externalValidationFault = new AuthenticationException(SR.Format(SR.net_ssl_io_cert_validation, erro
 0507                }
 508
 509                // VerifyRemoteCertificateCore assigns _remoteCertificate to the candidate before it
 510                // knows whether the chain validates, so on the reject path the rejected leaf is sitting
 511                // in the canonical slot. Drop it so GetRemoteCertificate cannot surface a cert the caller
 512                // explicitly refused. Either _remoteCertificate or _externalPendingCert owns it, not both.
 0513                if (_remoteCertificate is not null && ReferenceEquals(_remoteCertificate, _externalPendingCert))
 0514                {
 0515                    _remoteCertificate = null;
 0516                }
 517                else
 0518                {
 0519                    _remoteCertificate?.Dispose();
 0520                    _remoteCertificate = null;
 0521                }
 0522                _externalPendingCert?.Dispose();
 0523                _externalPendingCert = null;
 0524            }
 525
 0526            DisposeExternalRemoteCertificates();
 0527        }
 528
 529#if !TARGET_WINDOWS && !SYSNETSECURITY_NO_OPENSSL
 530        // Client-side only path. When CertVerifyCallback paused the handshake via
 531        // SSL_set_retry_verify, RetryVerifyAttempted is set on the SafeSslHandle. Stamp
 532        // the caller's verdict onto the handle so the next SSL_do_handshake (driven by
 533        // the caller's next ProcessHandshake) re-enters the callback and either accepts
 534        // the peer cert or emits a fatal alert. No-op on server sessions and on 1.1.x
 535        // where CertVerifyCallback took the accept-and-defer branch instead of retrying.
 536        private void PushExternalValidationVerdictToPalIfRetryVerify(SslPolicyErrors errors)
 537        {
 538            if (_securityContext is not Microsoft.Win32.SafeHandles.SafeSslHandle sslHandle ||
 539                !sslHandle.RetryVerifyAttempted)
 540            {
 541                return;
 542            }
 543
 544            sslHandle.ExternalValidationAccepted = errors == SslPolicyErrors.None;
 545        }
 546#endif
 547
 548        /// <summary>
 549        /// Server-side only. The parsed ClientHello information, populated once the
 550        /// ClientHello has been received and stays populated for the lifetime of the
 551        /// session. Returns <see langword="null"/> before the ClientHello arrives,
 552        /// on client-side sessions, and on server sessions where ClientHello capture
 553        /// was disabled via the <c>System.Net.Security.CaptureClientHello</c> AppContext
 554        /// switch AND options were supplied at <see cref="TlsContext"/> creation time.
 555        /// </summary>
 556        public SslClientHelloInfo? ClientHelloInfo
 557        {
 558            get
 0559            {
 0560                ThrowIfDisposed();
 0561                return _clientHelloInfo;
 0562            }
 563        }
 564
 565        /// <summary>
 566        /// Server-side only. Returns the number of bytes in the captured raw ClientHello
 567        /// record (5-byte TLS record header plus the ClientHello handshake message), or
 568        /// 0 if unavailable. Callers use this to size a destination buffer for
 569        /// <see cref="TryGetClientHelloBytes"/>.
 570        /// </summary>
 571        /// <remarks>
 572        /// The ClientHello is only captured on server-side sessions and requires the
 573        /// <c>System.Net.Security.CaptureClientHello</c> AppContext switch to be enabled
 574        /// (default true). Returns 0 on client-side sessions, before the ClientHello has
 575        /// been received, or when capture has been disabled.
 576        /// </remarks>
 577        public int GetClientHelloLength()
 0578        {
 0579            ThrowIfDisposed();
 580
 0581            ReadOnlySpan<byte> native = default;
 582            TryGetNativeClientHelloBytes(ref native);
 0583            if (!native.IsEmpty)
 0584            {
 0585                return native.Length;
 586            }
 587
 0588            return _clientHelloBytesBuffered?.Length ?? 0;
 0589        }
 590
 591        /// <summary>
 592        /// Server-side only. Copies the captured raw ClientHello record into
 593        /// <paramref name="destination"/>. Returns <see langword="true"/> when the full
 594        /// record was written; <see langword="false"/> if the destination is too small
 595        /// or the ClientHello is not available.
 596        /// </summary>
 597        /// <param name="destination">Buffer that receives the ClientHello bytes.</param>
 598        /// <param name="bytesWritten">Number of bytes copied. Zero when the method returns false.</param>
 599        public bool TryGetClientHelloBytes(Span<byte> destination, out int bytesWritten)
 0600        {
 0601            ThrowIfDisposed();
 602
 0603            ReadOnlySpan<byte> source = default;
 604            TryGetNativeClientHelloBytes(ref source);
 0605            if (source.IsEmpty)
 0606            {
 0607                if (_clientHelloBytesBuffered is null)
 0608                {
 0609                    bytesWritten = 0;
 0610                    return false;
 611                }
 0612                source = _clientHelloBytesBuffered;
 0613            }
 614
 0615            if (destination.Length < source.Length)
 0616            {
 0617                bytesWritten = 0;
 0618                return false;
 619            }
 620
 0621            source.CopyTo(destination);
 0622            bytesWritten = source.Length;
 0623            return true;
 0624        }
 625
 626        /// <summary>
 627        /// Assigns a <see cref="TlsContext"/> to this session. Must be called at least
 628        /// once before <see cref="TlsBufferSession.Handshake"/> or its socket-bound
 629        /// equivalent can make forward progress. May also be called on a server-side
 630        /// session that suspended with <see cref="TlsOperationStatus.NeedsTlsContext"/>
 631        /// to steer it onto the resolved per-tenant context.
 632        /// </summary>
 633        /// <param name="context">A fully-configured <see cref="TlsContext"/>.</param>
 634        /// <exception cref="ArgumentNullException">Thrown when <paramref name="context"/> is null.</exception>
 635        /// <exception cref="ArgumentException">
 636        /// Thrown when supplying a resolved context after
 637        /// <see cref="TlsOperationStatus.NeedsTlsContext"/> and the passed context is
 638        /// not server-side.
 639        /// </exception>
 640        /// <exception cref="InvalidOperationException">
 641        /// Thrown when the session already has a context and is not currently awaiting
 642        /// server options (i.e., the caller tried to swap a context that was already
 643        /// fully configured).
 644        /// </exception>
 645        public void SetContext(TlsContext context)
 0646        {
 0647            ArgumentNullException.ThrowIfNull(context);
 0648            ThrowIfDisposed();
 649
 0650            if (_context is null)
 0651            {
 0652                InitializeFromContext(context);
 0653                return;
 654            }
 655
 0656            if (!_context!.IsServer)
 0657            {
 0658                throw new InvalidOperationException(SR.net_tlssession_setcontext_server_only);
 659            }
 0660            if (!context.IsServer)
 0661            {
 0662                throw new ArgumentException(SR.net_tlssession_context_must_be_server, nameof(context));
 663            }
 0664            if (_hasServerOptions)
 0665            {
 0666                throw new InvalidOperationException(SR.net_tlssession_server_options_already_supplied);
 667            }
 0668            if (_clientHelloInfo is null)
 0669            {
 0670                throw new InvalidOperationException(SR.net_tlssession_setcontext_needs_context_first);
 671            }
 672
 673            // Ask the supplied context for a session-options bag â€” this allocates its
 674            // long-lived SSL_CTX (if not already) and stamps PreallocatedSslContext on
 675            // the returned bag. Copy those fields (including PreallocatedSslContext) into
 676            // our session's options so subsequent AllocateSslHandle picks up the passed
 677            // context's SSL_CTX instead of falling back to the per-session cache path.
 0678            SslAuthenticationOptions serverOpts = context.CreateSessionOptions();
 0679            _options.CopyFrom(serverOpts);
 680#if !TARGET_WINDOWS && !SYSNETSECURITY_NO_OPENSSL
 681            _options.PreallocatedSslContext = serverOpts.PreallocatedSslContext;
 682#endif
 683
 684            // CopyFrom sets _options.OwnsCertificateContext = false and copies the
 685            // template's CertificateContext reference into the bag. Re-seat our session
 686            // ownership from the freshly-copied serverOpts (the new template may have
 687            // brought its own owned context via ServerCertificate), releasing any prior
 688            // session-owned context. serverOpts itself is a per-session clone that Owns
 689            // = false, so its live-owner is the source TlsContext template that stays
 690            // alive across sessions â€” hence takeOwnership: false here.
 0691            SetSessionCertificateContext(_options.CertificateContext, takeOwnership: false);
 692
 0693            _hasServerOptions = true;
 694
 695            // The per-tenant options differ from the bootstrap context's template, so
 696            // credentials must be session-local. Otherwise EnsureCredentialsAcquired
 697            // would stamp this session's SChannel cred handle into the shared bootstrap
 698            // TlsContext.CredentialsHandle, and every subsequent session on the same
 699            // bootstrap would inherit those credentials regardless of which tenant it
 700            // resolved to (SChannel-only; OpenSSL routes per-tenant SSL_CTX via
 701            // PreallocatedSslContext on the session-local options bag). Acquire eagerly
 702            // so any AcquireCredentialsHandle failure surfaces from SetContext,
 703            // not from an opaque PAL call downstream.
 0704            _sessionCredentialsHandle?.Dispose();
 0705            _sessionCredentialsHandle = SslStreamPal.AcquireCredentialsHandle(_options, false);
 706
 707            OnServerContextSet();
 0708        }
 709
 710        /// <summary>
 711        /// Client-side only. Supplies the certificate context the session should send
 712        /// in response to the server's CertificateRequest, or <see langword="null"/> to
 713        /// decline. Intended to resolve a session suspended on
 714        /// <see cref="TlsOperationStatus.CertificateRequested"/>: callers that need to
 715        /// fetch a certificate from an out-of-process source (e.g. a key vault) do so
 716        /// outside the session, then resume the handshake. May also be called before
 717        /// the first handshake call to seed the client credential when the
 718        /// <see cref="TlsContext"/> was created without one.
 719        /// </summary>
 720        /// <exception cref="InvalidOperationException">
 721        /// Thrown on a server-side session, or before <see cref="SetContext"/> has been
 722        /// called.
 723        /// </exception>
 724        public void SetClientCertificateContext(SslStreamCertificateContext? context)
 0725        {
 0726            ThrowIfDisposed();
 0727            ThrowIfContextNotSet();
 728
 0729            if (_context!.IsServer)
 0730            {
 0731                throw new InvalidOperationException(SR.net_tlssession_setclientcert_client_only);
 732            }
 0733            SetSessionCertificateContext(context, takeOwnership: false);
 734
 735            // Acquire a session-local credentials handle so we don't touch the shared
 736            // TlsContext.CredentialsHandle, which is used by any concurrent session on
 737            // the same context (racing/disposing it can cause handshake failures or
 738            // deliver the wrong certificate on SChannel). ActiveCredentialsRef() will
 739            // return this session-local handle for subsequent PAL calls. Acquire eagerly
 740            // so any AcquireCredentialsHandle failure surfaces here, not from an opaque
 741            // PAL call downstream.
 0742            _sessionCredentialsHandle?.Dispose();
 0743            _sessionCredentialsHandle = SslStreamPal.AcquireCredentialsHandle(_options, false);
 0744            _resumeAfterCredentials = true;
 0745        }
 746
 747        /// <summary>
 748        /// Client-side only. Returns the distinguished names of the certificate authorities
 749        /// the server listed in its TLS 1.2 <c>CertificateRequest</c> or TLS 1.3
 750        /// <c>certificate_authorities</c> extension. Intended to be called while the session
 751        /// is suspended on <see cref="TlsOperationStatus.CertificateRequested"/> so the caller can
 752        /// pick a client certificate that chains to one of the listed CAs. Returns
 753        /// <see langword="null"/> when no security context exists yet, when the peer sent no
 754        /// hints, or on a server-side session.
 755        /// </summary>
 756        public IReadOnlyList<string>? GetAcceptableIssuers()
 0757        {
 0758            ThrowIfDisposed();
 759
 0760            if (_context is null || _context.IsServer || _securityContext is null)
 0761            {
 0762                return null;
 763            }
 764
 0765            string[] issuers = CertificateValidationPal.GetRequestCertificateAuthorities(_securityContext);
 0766            return issuers.Length == 0 ? null : issuers;
 0767        }
 768
 769        private void ThrowIfPendingExternalValidation()
 0770        {
 0771            if (_externalValidationFault is not null)
 0772            {
 0773                throw _externalValidationFault;
 774            }
 0775            if (_externalValidationPending)
 0776            {
 0777                throw new InvalidOperationException(
 0778                    SR.net_tlssession_validation_result_not_recorded);
 779            }
 0780        }
 781
 782        private void DisposeExternalRemoteCertificates()
 0783        {
 0784            X509Certificate2Collection? certs = _externalRemoteCertificates;
 0785            _externalRemoteCertificates = null;
 0786            if (certs is null)
 0787            {
 0788                return;
 789            }
 0790            foreach (X509Certificate2 c in certs)
 0791            {
 0792                c.Dispose();
 0793            }
 0794        }
 795
 796        /// <summary>
 797        /// Returns the local certificate sent to the peer, or <c>null</c> if no
 798        /// local certificate was negotiated. For a server session this is the
 799        /// server certificate; for a client session this is the client
 800        /// certificate selected during handshake (which may be <c>null</c> if
 801        /// the server did not request a client certificate or the client did
 802        /// not supply one).
 803        /// </summary>
 804        public X509Certificate2? LocalCertificate
 805        {
 806            get
 0807            {
 0808                ThrowIfDisposed();
 0809                if (_context!.IsServer)
 0810                {
 0811                    return SessionCertificateContext?.TargetCertificate;
 812                }
 813
 0814                if (_securityContext == null || _securityContext.IsInvalid)
 0815                {
 0816                    return null;
 817                }
 818
 0819                if (!CertificateValidationPal.IsLocalCertificateUsed(ActiveCredentialsRef(), _securityContext))
 0820                {
 0821                    return null;
 822                }
 823
 0824                return SessionCertificateContext?.TargetCertificate;
 0825            }
 826        }
 827
 828        /// <summary>
 829        /// Returns a <see cref="ChannelBinding"/> for the requested
 830        /// <paramref name="kind"/> derived from the current TLS session, or
 831        /// <c>null</c> if the binding is unavailable (e.g. handshake not yet
 832        /// complete, or unsupported binding kind).
 833        /// </summary>
 834        public ChannelBinding? GetChannelBinding(ChannelBindingKind kind)
 0835        {
 0836            ThrowIfDisposed();
 0837            if (_securityContext == null || _securityContext.IsInvalid)
 0838            {
 0839                return null;
 840            }
 0841            return SslStreamPal.QueryContextChannelBinding(_securityContext, kind);
 0842        }
 843
 844        // â”€â”€ Handshake â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
 845
 846        private protected TlsOperationStatus HandshakeBufferedCore(
 847            ReadOnlySpan<byte> input,
 848            Span<byte> output,
 849            out int bytesConsumed,
 850            out int bytesWritten)
 0851        {
 0852            ThrowIfDisposed();
 0853            ThrowIfContextNotSet();
 0854            bytesConsumed = 0;
 0855            bytesWritten = 0;
 856
 0857            if (_externalValidationFault is not null)
 0858            {
 859                // Mid-handshake external-validation reject: on OpenSSL 1.1.x and SecureTransport
 860                // the peer-verify callback took the accept-and-defer path (no retry-verify), so
 861                // the wire handshake still needs to complete before the fault surfaces on the
 862                // caller's Write / Read. Suppress the throw here while the handshake is still
 863                // in-flight so the PAL can silently drive it to completion; the fault will still
 864                // fire on Write / Read via ThrowIfPendingExternalValidation. On OpenSSL 3.0+
 865                // retry-verify the natural PAL failure produces a fatal alert to the peer.
 0866                if (_isHandshakeComplete || !_externalValidationResolved)
 0867                {
 0868                    throw _externalValidationFault;
 869                }
 0870            }
 871
 0872            if (_externalValidationPending)
 0873            {
 0874                return TlsOperationStatus.NeedsCertificateValidation;
 875            }
 876
 0877            if (_clientHelloInfo is not null && !_hasServerOptions)
 0878            {
 879                // The caller previously saw NeedsServerOptions but hasn't supplied options yet.
 0880                return TlsOperationStatus.NeedsTlsContext;
 881            }
 882
 0883            if (_isHandshakeComplete)
 0884            {
 885                // Once the caller has resolved external validation, subsequent
 886                // ProcessHandshake calls on an already-complete session are a
 887                // no-op signal that the handshake is done (one-call window).
 0888                if (_externalValidationResolved)
 0889                {
 0890                    return TlsOperationStatus.Complete;
 891                }
 892
 0893                throw new InvalidOperationException(SR.net_tlssession_handshake_already_complete);
 894            }
 895
 896            // Drain pending first; do not consume new input while output is owed.
 0897            if (_pendingBuffer.ActiveLength > 0)
 0898            {
 0899                bytesWritten = DrainTo(output);
 0900                return _pendingBuffer.ActiveLength > 0 ? TlsOperationStatus.DestinationTooSmall : TlsOperationStatus.Com
 901            }
 902
 903            // The PAL state machine â€” SChannel in particular â€” must only be handed
 904            // complete TLS records. SChannel's PAL wrapper reports consumed=input.Length
 905            // when it returns SEC_E_INCOMPLETE_MESSAGE, which would silently swallow
 906            // bytes it actually still needs. OpenSSL's BIO accepts partial bytes, but
 907            // pre-checking the frame here costs nothing extra and keeps the state
 908            // machine identical across platforms.
 909            //
 910            // The only call that legitimately runs with empty input is the very first
 911            // client-side ISC, which produces the ClientHello, or a client resume after
 912            // SetClientCertificateContext resolved a prior WantCredentials suspension.
 0913            bool isInitialClientCall = !_context!.IsServer && _securityContext is null;
 0914            bool isCredentialResume = _resumeAfterCredentials;
 0915            _resumeAfterCredentials = false;
 0916            bool isCertValidationResume = _resumeAfterCertValidation;
 0917            _resumeAfterCertValidation = false;
 0918            if (!isInitialClientCall && !isCredentialResume && !isCertValidationResume)
 0919            {
 0920                if (input.Length < TlsFrameHelper.HeaderSize)
 0921                {
 0922                    return TlsOperationStatus.NeedMoreData;
 923                }
 924
 0925                TlsFrameHeader frameHeader = default;
 0926                if (!TlsFrameHelper.TryGetFrameHeader(input, ref frameHeader))
 0927                {
 0928                    throw new IOException(SR.net_io_decrypt);
 929                }
 930
 0931                if (input.Length < frameHeader.Length)
 0932                {
 0933                    return TlsOperationStatus.NeedMoreData;
 934                }
 0935            }
 936
 0937            ProtocolToken token = default;
 0938            token.RentBuffer = true;
 939            try
 0940            {
 0941                if (_context!.IsServer)
 0942                {
 943                    // Parse and capture the ClientHello managed-side so the ClientHelloInfo /
 944                    // TargetHostName / GetClientHelloBytes surface is consistent across paths.
 945                    // We check on every call while _clientHelloBytesBuffered is null because the
 946                    // first ProcessHandshake call may pass only a partial CH record - OpenSSL will
 947                    // allocate _securityContext even on partial input and return WantRead, so we
 948                    // can't rely on _securityContext being null as our re-entry gate.
 0949                    if (_clientHelloBytesBuffered is null)
 0950                    {
 0951                        SslClientHelloInfo? parsed = TryParseClientHello(input, out int frameLength);
 0952                        if (parsed is not null)
 0953                        {
 0954                            _clientHelloInfo = parsed;
 0955                            if (!string.IsNullOrEmpty(parsed.Value.ServerName))
 0956                            {
 0957                                _sessionTargetHost = parsed.Value.ServerName;
 0958                            }
 0959                            if (frameLength > 0 && frameLength <= input.Length)
 0960                            {
 0961                                _clientHelloBytesBuffered = input.Slice(0, frameLength).ToArray();
 0962                            }
 963                            // If frameLength is out of range (shouldn't happen after a successful
 964                            // parse), silently skip capture; the session continues to work,
 965                            // GetClientHelloLength just reports 0.
 0966                        }
 0967                        else if (_securityContext is null)
 0968                        {
 969                            // No CH parse-able yet and no PAL context yet - wait for more bytes.
 0970                            return TlsOperationStatus.NeedMoreData;
 971                        }
 0972                    }
 973
 974                    // On the very first server-side call, inspect the incoming
 975                    // ClientHello to surface SNI (TargetHost) and, if the caller
 976                    // supplied a ServerCertificateSelectionCallback, resolve the
 977                    // server certificate from it before AllocateSslHandle runs.
 0978                    if (_securityContext is null)
 0979                    {
 0980                        if (!_hasServerOptions)
 0981                        {
 982                            // Deferred / SNI-callback flow: caller resolves via SetContext.
 983                            // Leave input unconsumed; the caller re-feeds the same bytes on resume.
 0984                            return TlsOperationStatus.NeedsTlsContext;
 985                        }
 986
 0987                        bool needsCertResolution =
 0988                            SessionCertificateContext is null &&
 0989                            _options.ServerCertSelectionDelegate is not null;
 990
 0991                        if (needsCertResolution && !ResolveServerCertificateFromClientHello(input))
 0992                        {
 993                            // Need more bytes to parse the ClientHello (and run the
 994                            // ServerCertificateSelectionCallback).
 0995                            return TlsOperationStatus.NeedMoreData;
 996                        }
 0997                    }
 998
 0999                    EnsureCredentialsAcquired();
 1000
 01001                    token = SslStreamPal.AcceptSecurityContext(
 01002                        ref ActiveCredentialsRef(),
 01003                        ref _securityContext,
 01004                        input,
 01005                        out bytesConsumed,
 01006                        _options);
 01007                }
 1008                else
 01009                {
 01010                    EnsureCredentialsAcquired();
 1011
 01012                    string hostName = TargetHostNameHelper.NormalizeHostName(_options.TargetHost);
 01013                    token = SslStreamPal.InitializeSecurityContext(
 01014                        ref ActiveCredentialsRef(),
 01015                        ref _securityContext,
 01016                        hostName,
 01017                        input,
 01018                        out bytesConsumed,
 01019                        _options);
 01020                }
 1021
 1022                // Stage any handshake bytes the PAL produced.
 01023                if (token.Size > 0)
 01024                {
 01025                    Debug.Assert(token.Payload != null);
 01026                    AppendPending(new ReadOnlySpan<byte>(token.Payload, 0, token.Size));
 01027                }
 1028
 1029                // Server-side ALPN selection ceremony (SChannel and SecureTransport).
 1030                // After parsing the ClientHello the PAL pauses and asks the caller to
 1031                // pick the application protocol before resuming. We re-enter ASC with
 1032                // an empty input so the PAL can generate the ServerHello carrying the
 1033                // selected ALPN value.
 01034                if (token.Status.ErrorCode == SecurityStatusPalErrorCode.HandshakeStarted)
 01035                {
 01036                    ReadOnlySpan<byte> rawAlpn = ReadOnlySpan<byte>.Empty;
 01037                    TlsFrameHelper.TlsFrameInfo frameInfo = default;
 01038                    if (TlsFrameHelper.TryGetFrameInfo(input, ref frameInfo,
 01039                            TlsFrameHelper.ProcessingOptions.ApplicationProtocol | TlsFrameHelper.ProcessingOptions.RawA
 01040                        frameInfo.RawApplicationProtocols is byte[] rawAlpnBytes)
 01041                    {
 01042                        rawAlpn = rawAlpnBytes;
 01043                    }
 1044
 01045                    SecurityStatusPal selStatus = SslStreamPal.SelectApplicationProtocol(
 01046                        _context!.CredentialsHandle,
 01047                        _securityContext!,
 01048                        _options,
 01049                        rawAlpn);
 1050
 01051                    if (selStatus.ErrorCode != SecurityStatusPalErrorCode.OK)
 01052                    {
 01053                        throw new AuthenticationException(SR.net_auth_SSPI, selStatus.Exception);
 1054                    }
 1055
 01056                    token.ReleasePayload();
 1057
 01058                    if (_context!.IsServer)
 01059                    {
 01060                        token = SslStreamPal.AcceptSecurityContext(
 01061                            ref ActiveCredentialsRef(),
 01062                            ref _securityContext,
 01063                            ReadOnlySpan<byte>.Empty,
 01064                            out _,
 01065                            _options);
 01066                    }
 1067                    else
 01068                    {
 01069                        string hostName = TargetHostNameHelper.NormalizeHostName(_options.TargetHost);
 01070                        token = SslStreamPal.InitializeSecurityContext(
 01071                            ref ActiveCredentialsRef(),
 01072                            ref _securityContext,
 01073                            hostName,
 01074                            ReadOnlySpan<byte>.Empty,
 01075                            out _,
 01076                            _options);
 01077                    }
 1078
 01079                    if (token.Size > 0)
 01080                    {
 01081                        Debug.Assert(token.Payload != null);
 01082                        AppendPending(new ReadOnlySpan<byte>(token.Payload, 0, token.Size));
 01083                    }
 01084                }
 1085
 01086                if (token.Failed &&
 01087                    token.Status.ErrorCode != SecurityStatusPalErrorCode.CredentialsNeeded &&
 01088                    token.Status.ErrorCode != SecurityStatusPalErrorCode.CertValidationNeeded)
 01089                {
 01090                    Exception authExc = new AuthenticationException(SR.net_auth_SSPI, token.GetException());
 1091
 1092                    // OpenSSL queued a TLS alert in the BIO during the failing SSL_do_handshake
 1093                    // (e.g. bad_certificate after the client-side retry-verify callback rejected
 1094                    // the peer). Drain the alert to the caller's output buffer before throwing so
 1095                    // the peer observes an AuthenticationException instead of a connection reset.
 1096                    // The fault is re-raised on the next ProcessHandshake call once the queue is
 1097                    // empty. Only fires on the client path today; server-side never reaches this
 1098                    // branch for external-validation reasons because CertVerifyCallback
 1099                    // accepts-and-defers (see gating in Interop.OpenSsl.CertVerifyCallback).
 01100                    if (_pendingBuffer.ActiveLength > 0)
 01101                    {
 01102                        bytesWritten = DrainTo(output);
 01103                        _externalValidationFault = authExc;
 01104                        return TlsOperationStatus.DestinationTooSmall;
 1105                    }
 1106
 01107                    throw authExc;
 1108                }
 1109
 01110                bool done = token.Status.ErrorCode == SecurityStatusPalErrorCode.OK;
 01111                bool needsCredentials = token.Status.ErrorCode == SecurityStatusPalErrorCode.CredentialsNeeded;
 01112                bool needsCertValidation = token.Status.ErrorCode == SecurityStatusPalErrorCode.CertValidationNeeded;
 1113
 01114                if (done)
 01115                {
 01116                    OnHandshakeCompleted();
 01117                }
 01118                else if (needsCertValidation)
 01119                {
 1120                    // PAL paused mid-handshake awaiting external certificate validation.
 1121                    // Capture the peer cert + chain so the caller can validate, then return
 1122                    // NeedsCertificateValidation. Not used by the current OpenSSL or SChannel
 1123                    // paths but kept as a generic suspension hook.
 01124                    CaptureRemoteCertificateForExternalValidation();
 01125                }
 1126
 01127                if (_pendingBuffer.ActiveLength > 0)
 01128                {
 01129                    bytesWritten = DrainTo(output);
 01130                    if (_pendingBuffer.ActiveLength > 0)
 01131                    {
 01132                        return TlsOperationStatus.DestinationTooSmall;
 1133                    }
 01134                }
 1135
 01136                if (done)
 01137                {
 01138                    return _externalValidationPending
 01139                        ? TlsOperationStatus.NeedsCertificateValidation
 01140                        : TlsOperationStatus.Complete;
 1141                }
 1142
 01143                if (needsCertValidation)
 01144                {
 01145                    return TlsOperationStatus.NeedsCertificateValidation;
 1146                }
 1147
 01148                if (needsCredentials)
 01149                {
 01150                    return TlsOperationStatus.CertificateRequested;
 1151                }
 1152
 1153                // SChannel consumes one TLS record per AcceptSecurityContext/
 1154                // InitializeSecurityContext call (OpenSSL typically consumes the
 1155                // whole input via the BIO). When the PAL accepted bytes but the
 1156                // caller still has more buffered, return Complete so the driver
 1157                // re-enters us with the remainder instead of blocking on a network
 1158                // read the peer will never satisfy (e.g. server seeing CKE+CCS+
 1159                // Finished in one TCP read during a TLS 1.2 handshake).
 01160                if (bytesConsumed > 0 && bytesConsumed < input.Length)
 01161                {
 01162                    return TlsOperationStatus.Complete;
 1163                }
 1164
 01165                return TlsOperationStatus.NeedMoreData;
 1166            }
 1167            finally
 01168            {
 01169                token.ReleasePayload();
 01170            }
 01171        }
 1172
 1173        private protected TlsOperationStatus WriteBufferedCore(
 1174            ReadOnlySpan<byte> plaintext,
 1175            Span<byte> ciphertext,
 1176            out int bytesConsumed,
 1177            out int bytesWritten)
 01178        {
 01179            ThrowIfDisposed();
 01180            ThrowIfPendingExternalValidation();
 01181            bytesConsumed = 0;
 01182            bytesWritten = 0;
 1183
 01184            if (!_isHandshakeComplete)
 01185            {
 01186                throw new InvalidOperationException(SR.net_tlssession_handshake_not_complete);
 1187            }
 1188
 01189            if (_pendingBuffer.ActiveLength > 0)
 01190            {
 01191                bytesWritten = DrainTo(ciphertext);
 01192                return _pendingBuffer.ActiveLength > 0 ? TlsOperationStatus.DestinationTooSmall : TlsOperationStatus.Com
 1193            }
 1194
 01195            if (plaintext.IsEmpty)
 01196            {
 01197                return TlsOperationStatus.Complete;
 1198            }
 1199
 01200            int chunk = Math.Min(plaintext.Length, _maxDataSize);
 01201            byte[] rented = ArrayPool<byte>.Shared.Rent(chunk);
 1202            try
 01203            {
 01204                plaintext.Slice(0, chunk).CopyTo(rented);
 1205
 01206                ProtocolToken token = SslStreamPal.EncryptMessage(
 01207                    _securityContext!,
 01208                    new ReadOnlyMemory<byte>(rented, 0, chunk),
 01209                    _headerSize,
 01210                    _trailerSize);
 1211
 1212                try
 01213                {
 01214                    if (token.Status.ErrorCode != SecurityStatusPalErrorCode.OK)
 01215                    {
 01216                        throw new IOException(SR.net_io_encrypt, SslStreamPal.GetException(token.Status));
 1217                    }
 1218
 01219                    bytesConsumed = chunk;
 1220
 01221                    if (token.Size > 0)
 01222                    {
 01223                        Debug.Assert(token.Payload != null);
 01224                        AppendPending(new ReadOnlySpan<byte>(token.Payload, 0, token.Size));
 01225                    }
 01226                }
 1227                finally
 01228                {
 01229                    token.ReleasePayload();
 01230                }
 01231            }
 1232            finally
 01233            {
 01234                ArrayPool<byte>.Shared.Return(rented);
 01235            }
 1236
 01237            bytesWritten = DrainTo(ciphertext);
 01238            return _pendingBuffer.ActiveLength > 0 ? TlsOperationStatus.DestinationTooSmall : TlsOperationStatus.Complet
 01239        }
 1240
 1241        // â”€â”€ Decrypt â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
 1242
 1243        private protected TlsOperationStatus ReadBufferedCore(
 1244            ReadOnlySpan<byte> ciphertext,
 1245            Span<byte> plaintext,
 1246            out int bytesConsumed,
 1247            out int bytesWritten)
 01248        {
 01249            ThrowIfDisposed();
 01250            ThrowIfPendingExternalValidation();
 01251            bytesConsumed = 0;
 01252            bytesWritten = 0;
 1253
 01254            if (!_isHandshakeComplete)
 01255            {
 01256                throw new InvalidOperationException(SR.net_tlssession_handshake_not_complete);
 1257            }
 1258
 01259            if (_pendingBuffer.ActiveLength > 0)
 01260            {
 1261                // Caller must drain before we accept new input.
 01262                return TlsOperationStatus.DestinationTooSmall;
 1263            }
 1264
 1265            // Need at least a frame header. If the caller didn't provide a full frame, the PAL
 1266            // may still have plaintext buffered internally â€” ciphertext absorbed by OpenSSL's
 1267            // BIO during ProcessHandshake (e.g. the peer coalesced its Finished with the first
 1268            // app-data record into one TCP segment) or a record consumed but not yet decrypted
 1269            // by a prior Decrypt call. On platforms whose PAL maintains such a buffer, probe it
 1270            // with an empty input before asking the caller for more wire bytes; otherwise the
 1271            // session deadlocks waiting on data the peer already sent.
 01272            if (ciphertext.Length < TlsFrameHelper.HeaderSize)
 01273            {
 01274                return TryDrainBufferedPlaintext(plaintext, out bytesWritten);
 1275            }
 1276
 01277            TlsFrameHeader header = default;
 01278            if (!TlsFrameHelper.TryGetFrameHeader(ciphertext, ref header))
 01279            {
 01280                throw new IOException(SR.net_io_decrypt);
 1281            }
 1282
 01283            int frameSize = header.Length;
 01284            if (ciphertext.Length < frameSize)
 01285            {
 01286                return TryDrainBufferedPlaintext(plaintext, out bytesWritten);
 1287            }
 1288
 1289            // PAL decrypts in place; copy into a writable scratch buffer.
 01290            EnsureDecryptScratch(frameSize);
 01291            ciphertext.Slice(0, frameSize).CopyTo(_decryptScratch);
 1292
 01293            SecurityStatusPal status = SslStreamPal.DecryptMessage(
 01294                _securityContext!,
 01295                _decryptScratch.AsSpan(0, frameSize),
 01296                plaintext,
 01297                out int decBytesWritten,
 01298                out int decLeftoverOffset,
 01299                out int decLeftoverLength);
 1300
 01301            switch (status.ErrorCode)
 1302            {
 1303                case SecurityStatusPalErrorCode.OK:
 01304                    bytesConsumed = frameSize;
 1305                    // Linux/macOS PALs write the plaintext directly into the destination span and
 1306                    // (if it didn't fit, or the PAL prefers in-place) leave overflow in the encrypted
 1307                    // span at leftoverOffset/leftoverLength. SChannel always decrypts in place and
 1308                    // reports bytesWritten = 0 with leftoverOffset/leftoverLength pointing at the
 1309                    // plaintext inside the encrypted span. Unify by appending the leftover slice
 1310                    // after whatever was written into destination.
 01311                    int needed = decBytesWritten + decLeftoverLength;
 01312                    if (needed > plaintext.Length)
 01313                    {
 01314                        throw new InvalidOperationException(
 01315                            SR.Format(SR.net_tlssession_plaintext_buffer_too_small, needed, plaintext.Length));
 1316                    }
 01317                    if (decLeftoverLength > 0)
 01318                    {
 01319                        _decryptScratch.AsSpan(decLeftoverOffset, decLeftoverLength)
 01320                            .CopyTo(plaintext.Slice(decBytesWritten));
 01321                    }
 01322                    bytesWritten = needed;
 01323                    return TlsOperationStatus.Complete;
 1324
 1325                case SecurityStatusPalErrorCode.ContextExpired:
 1326                case SecurityStatusPalErrorCode.ContextExpiredError:
 01327                    bytesConsumed = frameSize;
 01328                    return TlsOperationStatus.Closed;
 1329
 1330                case SecurityStatusPalErrorCode.Renegotiate:
 1331                    // SChannel surfaces SEC_I_RENEGOTIATE for two distinct cases:
 1332                    //  - TLS 1.2 peer-initiated renegotiation (HelloRequest).
 1333                    //  - TLS 1.3 post-handshake messages (NewSessionTicket,
 1334                    //    KeyUpdate, post-handshake CertificateRequest).
 1335                    // In either case the decrypted payload is the inner handshake
 1336                    // record that must be fed back into ASC/ISC so SChannel can
 1337                    // update its internal state. If we don't, the next DecryptMessage
 1338                    // returns SEC_E_CONTEXT_EXPIRED because the context is stuck.
 01339                    bytesConsumed = frameSize;
 01340                    if (decLeftoverLength > 0)
 01341                    {
 01342                        ProcessPostHandshakeMessage(_decryptScratch.AsSpan(decLeftoverOffset, decLeftoverLength));
 01343                    }
 1344                    // Return Complete (not WantRead): we consumed input bytes but
 1345                    // produced no plaintext. The caller's loop should re-enter to
 1346                    // process any remaining buffered ciphertext (e.g. application
 1347                    // data that arrived in the same TCP segment as the NST).
 01348                    return TlsOperationStatus.Complete;
 1349
 1350                default:
 01351                    throw new IOException(SR.net_io_decrypt, SslStreamPal.GetException(status));
 1352            }
 01353        }
 1354
 1355        // Empty-input probe used when the caller's buffer doesn't yet hold a complete TLS
 1356        // frame. On OpenSSL the PAL's record layer may still have plaintext queued from a
 1357        // prior call (handshake input that included trailing app-data, or a second record
 1358        // coalesced into the same TCP segment); calling DecryptMessage with an empty span
 1359        // surfaces it. On SChannel / SecureTransport the equivalent buffer does not exist,
 1360        // so the probe is skipped and the caller is asked for more bytes instead. The
 1361        // bytesConsumed out-parameter on the public Decrypt method is necessarily 0 here:
 1362        // no caller bytes were taken.
 1363        private TlsOperationStatus TryDrainBufferedPlaintext(Span<byte> plaintext, out int bytesWritten)
 01364        {
 01365            bytesWritten = 0;
 1366
 01367            if (!OperatingSystem.IsLinux() && !OperatingSystem.IsFreeBSD() && !OperatingSystem.IsAndroid())
 01368            {
 01369                return TlsOperationStatus.NeedMoreData;
 1370            }
 1371
 01372            SecurityStatusPal status = SslStreamPal.DecryptMessage(
 01373                _securityContext!,
 01374                Span<byte>.Empty,
 01375                plaintext,
 01376                out int decBytesWritten,
 01377                out int decLeftoverOffset,
 01378                out int decLeftoverLength);
 1379
 01380            if (status.ErrorCode != SecurityStatusPalErrorCode.OK)
 01381            {
 1382                // Anything other than success here means there's nothing to drain â€” the PAL
 1383                // is genuinely waiting on wire bytes. Surface as WantRead; fatal errors will
 1384                // resurface on the next regular Decrypt call with real ciphertext.
 01385                return TlsOperationStatus.NeedMoreData;
 1386            }
 1387
 01388            int produced = decBytesWritten + decLeftoverLength;
 01389            if (produced == 0)
 01390            {
 01391                return TlsOperationStatus.NeedMoreData;
 1392            }
 1393
 01394            if (produced > plaintext.Length)
 01395            {
 01396                throw new InvalidOperationException(
 01397                    SR.Format(SR.net_tlssession_plaintext_buffer_too_small, produced, plaintext.Length));
 1398            }
 1399
 01400            if (decLeftoverLength > 0)
 01401            {
 1402                // PAL stashed overflow in the (empty) input span â€” impossible here, but mirror
 1403                // the main Decrypt path for symmetry. With Span<byte>.Empty as input, the OpenSSL
 1404                // PAL has nowhere to stash leftover and won't take this path.
 01405                _decryptScratch.AsSpan(decLeftoverOffset, decLeftoverLength)
 01406                    .CopyTo(plaintext.Slice(decBytesWritten));
 01407            }
 1408
 01409            bytesWritten = produced;
 01410            return TlsOperationStatus.Complete;
 01411        }
 1412
 1413        // â”€â”€ Post-handshake auth â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
 1414
 1415        /// <summary>
 1416        /// Server-side: requests a client certificate from the peer after the
 1417        /// initial handshake has completed. On TLS 1.3 this issues a
 1418        /// post-handshake authentication CertificateRequest; on TLS 1.2 it
 1419        /// initiates a renegotiation.
 1420        /// </summary>
 1421        /// <remarks>
 1422        /// <para>
 1423        /// The session is created with client certificates optional
 1424        /// (<c>ClientCertificateRequired == false</c>); this method promotes the
 1425        /// requirement for the post-handshake exchange, mirroring
 1426        /// <see cref="SslStream.NegotiateClientCertificateAsync(System.Threading.CancellationToken)"/>.
 1427        /// </para>
 1428        /// <para>
 1429        /// The generated handshake bytes are staged into the pending-output
 1430        /// buffer (drained into <paramref name="ciphertext"/>). The caller must
 1431        /// send them to the peer and then drive the second handshake to
 1432        /// completion via <see cref="TlsBufferSession.Handshake"/>,
 1433        /// exactly like the initial handshake: it re-surfaces
 1434        /// <see cref="TlsOperationStatus.NeedsCertificateValidation"/> (re-running the
 1435        /// remote-certificate validation callback) and finally returns
 1436        /// <see cref="TlsOperationStatus.Complete"/>. Once validation is accepted the
 1437        /// peer certificate becomes observable via <see cref="GetRemoteCertificate"/>.
 1438        /// </para>
 1439        /// </remarks>
 1440        private protected TlsOperationStatus RequestClientCertificateBufferedCore(Span<byte> ciphertext, out int bytesWr
 01441        {
 01442            ThrowIfDisposed();
 01443            ThrowIfContextNotSet();
 1444            // Like Read/Write, post-handshake client authentication is a top-level session
 1445            // operation: if the previous handshake surfaced NeedsCertificateValidation the
 1446            // caller must record the result (or observe the recorded fault) before starting it.
 01447            ThrowIfPendingExternalValidation();
 01448            bytesWritten = 0;
 1449
 1450            // Post-handshake client authentication is a server-only operation. Enforce the role
 1451            // guard before the Apple platform check so client-session misuse consistently surfaces
 1452            // InvalidOperationException on every platform rather than PlatformNotSupportedException.
 01453            if (!_context!.IsServer)
 01454            {
 01455                throw new InvalidOperationException(SR.net_tlssession_request_client_cert_server_only);
 1456            }
 1457
 1458#if TARGET_APPLE
 1459            // SecureTransport does not expose a post-handshake client-authentication
 1460            // path, and Network.framework does not provide renegotiation primitives.
 1461            throw new PlatformNotSupportedException(SR.net_ssl_renegotiate_not_supported);
 1462#else
 01463            if (!_postHandshakeAuthActive)
 01464            {
 01465                if (!_isHandshakeComplete || _securityContext == null || _securityContext.IsInvalid)
 01466                {
 01467                    throw new InvalidOperationException(SR.net_tlssession_handshake_not_complete);
 1468                }
 1469
 1470                // Match SslStream.RenegotiateAsync: promote the client-certificate
 1471                // requirement for the post-handshake exchange even when the initial
 1472                // handshake was negotiated with RemoteCertRequired == false. This flips
 1473                // the MutualAuth context flag so the TLS 1.2 renegotiation / TLS 1.3
 1474                // post-handshake CertificateRequest actually asks for the client cert.
 01475                _options.RemoteCertRequired = true;
 1476
 01477                ProtocolToken token = SslStreamPal.Renegotiate(
 01478                    ref ActiveCredentialsRef(),
 01479                    ref _securityContext!,
 01480                    _options);
 01481                bool staged = false;
 1482                try
 01483                {
 1484                    // NoRenegotiation means no request can be made (e.g. a TLS 1.3 client that didn't offer
 1485                    // post-handshake authentication, or renegotiation disabled in the OpenSSL configuration),
 1486                    // not a failure: nothing is staged, so the session stays in its completed state below.
 01487                    bool noRenegotiation = token.Status.ErrorCode == SecurityStatusPalErrorCode.NoRenegotiation;
 01488                    if (token.Failed && !noRenegotiation)
 01489                    {
 01490                        throw new AuthenticationException(SR.net_auth_SSPI, token.GetException());
 1491                    }
 1492
 01493                    if (token.Size > 0 && !noRenegotiation)
 01494                    {
 01495                        Debug.Assert(token.Payload != null);
 01496                        AppendPending(new ReadOnlySpan<byte>(token.Payload, 0, token.Size));
 01497                        staged = true;
 01498                    }
 01499                }
 1500                finally
 01501                {
 01502                    token.ReleasePayload();
 01503                }
 1504
 01505                if (!staged)
 01506                {
 1507                    // The PAL produced no renegotiation request (e.g. the peer
 1508                    // declined with NoRenegotiation). Leave the session in its
 1509                    // completed state; there is nothing for the caller to drive.
 01510                    return TlsOperationStatus.Complete;
 1511                }
 1512
 1513                // Re-arm the handshake state machine so the caller drives the second
 1514                // handshake to completion via Handshake(), exactly like the initial
 1515                // handshake. HandshakeBufferedCore short-circuits to Complete while an
 1516                // already-complete session has resolved external validation, so both
 1517                // flags must be reset here for it to re-enter the PAL and re-surface
 1518                // NeedsCertificateValidation.
 01519                _isHandshakeComplete = false;
 01520                _externalValidationResolved = false;
 01521                _postHandshakeAuthActive = true;
 01522            }
 1523
 01524            bytesWritten = DrainTo(ciphertext);
 01525            if (_pendingBuffer.ActiveLength > 0)
 01526            {
 01527                return TlsOperationStatus.DestinationTooSmall;
 1528            }
 1529
 1530            // All staged request bytes handed off; from here the caller drives the
 1531            // second handshake through Handshake().
 01532            _postHandshakeAuthActive = false;
 01533            return TlsOperationStatus.Complete;
 1534#endif
 01535        }
 1536
 1537        // â”€â”€ Shutdown â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
 1538
 1539        private bool _shutdownSent;
 1540
 1541        /// <summary>
 1542        /// Initiates a TLS close_notify shutdown and stages the resulting alert
 1543        /// record into the pending-output buffer (drained into <paramref name="ciphertext"/>).
 1544        /// Subsequent calls drain any remaining shutdown output.
 1545        /// </summary>
 1546        /// <remarks>
 1547        /// Returns <see cref="TlsOperationStatus.DestinationTooSmall"/> if the caller must
 1548        /// drain more output before the shutdown record is fully written;
 1549        /// otherwise <see cref="TlsOperationStatus.Closed"/> once all bytes have
 1550        /// been handed to the caller.
 1551        /// </remarks>
 1552        private protected TlsOperationStatus ShutdownBufferedCore(Span<byte> ciphertext, out int bytesWritten)
 01553        {
 01554            ThrowIfDisposed();
 01555            bytesWritten = 0;
 1556
 01557            if (_securityContext == null || _securityContext.IsInvalid)
 01558            {
 01559                return TlsOperationStatus.Closed;
 1560            }
 1561
 01562            if (!_shutdownSent)
 01563            {
 01564                _shutdownSent = true;
 1565
 01566                SecurityStatusPal status = SslStreamPal.ApplyShutdownToken(_securityContext);
 01567                if (status.ErrorCode != SecurityStatusPalErrorCode.OK)
 01568                {
 01569                    throw new IOException(SR.net_io_encrypt, SslStreamPal.GetException(status));
 1570                }
 1571
 1572                // Drive one step to extract the close_notify bytes the PAL queued
 1573                // into the underlying BIO. Input is empty; we only care about
 1574                // any output the PAL produces.
 01575                ProtocolToken token = default;
 01576                token.RentBuffer = true;
 1577                try
 01578                {
 01579                    if (_context!.IsServer)
 01580                    {
 01581                        token = SslStreamPal.AcceptSecurityContext(
 01582                            ref ActiveCredentialsRef(),
 01583                            ref _securityContext,
 01584                            ReadOnlySpan<byte>.Empty,
 01585                            out _,
 01586                            _options);
 01587                    }
 1588                    else
 01589                    {
 01590                        string hostName = TargetHostNameHelper.NormalizeHostName(_options.TargetHost);
 01591                        token = SslStreamPal.InitializeSecurityContext(
 01592                            ref ActiveCredentialsRef(),
 01593                            ref _securityContext,
 01594                            hostName,
 01595                            ReadOnlySpan<byte>.Empty,
 01596                            out _,
 01597                            _options);
 01598                    }
 1599
 01600                    if (token.Size > 0)
 01601                    {
 01602                        Debug.Assert(token.Payload != null);
 01603                        AppendPending(new ReadOnlySpan<byte>(token.Payload, 0, token.Size));
 01604                    }
 01605                }
 1606                finally
 01607                {
 01608                    token.ReleasePayload();
 01609                }
 01610            }
 1611
 01612            bytesWritten = DrainTo(ciphertext);
 01613            return _pendingBuffer.ActiveLength > 0 ? TlsOperationStatus.DestinationTooSmall : TlsOperationStatus.Closed;
 01614        }
 1615
 1616        // â”€â”€ Pending output â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
 1617
 1618        private protected TlsOperationStatus DrainPendingOutputCore(Span<byte> ciphertext, out int bytesWritten)
 01619        {
 01620            ThrowIfDisposed();
 01621            bytesWritten = DrainTo(ciphertext);
 01622            return _pendingBuffer.ActiveLength > 0 ? TlsOperationStatus.DestinationTooSmall : TlsOperationStatus.Complet
 01623        }
 1624
 1625        // â”€â”€ Internals â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
 1626
 1627        private void AppendPending(ReadOnlySpan<byte> data)
 01628        {
 01629            if (data.IsEmpty)
 01630            {
 01631                return;
 1632            }
 1633
 01634            _pendingBuffer.EnsureAvailableSpace(data.Length);
 01635            data.CopyTo(_pendingBuffer.AvailableSpan);
 01636            _pendingBuffer.Commit(data.Length);
 01637        }
 1638
 1639        private int DrainTo(Span<byte> output)
 01640        {
 01641            int n = Math.Min(output.Length, _pendingBuffer.ActiveLength);
 01642            if (n == 0)
 01643            {
 01644                return 0;
 1645            }
 1646
 01647            _pendingBuffer.ActiveSpan.Slice(0, n).CopyTo(output);
 01648            _pendingBuffer.Discard(n);
 01649            return n;
 01650        }
 1651
 1652        private void EnsureDecryptScratch(int size)
 01653        {
 01654            if (_decryptScratch == null || _decryptScratch.Length < size)
 01655            {
 01656                if (_decryptScratch != null)
 01657                {
 01658                    ArrayPool<byte>.Shared.Return(_decryptScratch);
 01659                }
 01660                _decryptScratch = ArrayPool<byte>.Shared.Rent(size);
 01661            }
 01662        }
 1663
 01664        private void ThrowIfDisposed() => ObjectDisposedException.ThrowIf(_disposed, this);
 1665
 1666        private void ThrowIfContextNotSet()
 01667        {
 01668            if (_context is null)
 01669            {
 01670                throw new InvalidOperationException(SR.net_ssl_tlssession_context_not_set);
 1671            }
 01672        }
 1673
 1674        // Server-side: parses the ClientHello and returns a populated
 1675        // SslClientHelloInfo (SNI + supported versions), or null if more bytes
 1676        // are needed or the record is not a ClientHello. Used by the
 1677        // deferred-options path; does not mutate session state.
 1678        private static SslClientHelloInfo? TryParseClientHello(ReadOnlySpan<byte> input, out int frameLength)
 01679        {
 01680            frameLength = 0;
 01681            TlsFrameHelper.TlsFrameInfo frameInfo = default;
 01682            if (!TlsFrameHelper.TryGetFrameInfo(input, ref frameInfo))
 01683            {
 01684                return null;
 1685            }
 1686
 01687            if (frameInfo.HandshakeType != TlsHandshakeType.ClientHello)
 01688            {
 01689                return null;
 1690            }
 1691
 01692            frameLength = frameInfo.Header.Length;
 01693            return new SslClientHelloInfo(frameInfo.TargetName ?? string.Empty, frameInfo.SupportedVersions);
 01694        }
 1695
 1696        // Server-side SNI + certificate selection. Parses the ClientHello to
 1697        // extract the server_name extension (SNI) and, if a
 1698        // ServerCertificateSelectionCallback was supplied and no static
 1699        // CertificateContext has been resolved yet, invokes the callback to
 1700        // pick the cert. Mirrors the path SslStream takes in
 1701        // ReceiveBlobAsync/AcquireServerCredentials.
 1702        private bool ResolveServerCertificateFromClientHello(ReadOnlySpan<byte> input)
 01703        {
 01704            TlsFrameHelper.TlsFrameInfo frameInfo = default;
 01705            if (!TlsFrameHelper.TryGetFrameInfo(input, ref frameInfo))
 01706            {
 01707                return false;
 1708            }
 1709
 01710            if (frameInfo.HandshakeType != TlsHandshakeType.ClientHello)
 01711            {
 01712                return true;
 1713            }
 1714
 01715            if (!string.IsNullOrEmpty(frameInfo.TargetName))
 01716            {
 01717                _sessionTargetHost = frameInfo.TargetName;
 01718            }
 1719
 01720            ServerCertificateSelectionCallback? selector = _options.ServerCertSelectionDelegate;
 01721            if (selector is null || SessionCertificateContext is not null)
 01722            {
 01723                return true;
 1724            }
 1725
 01726            X509Certificate? selected = selector(this, _sessionTargetHost);
 01727            if (selected is null)
 01728            {
 01729                throw new AuthenticationException(SR.net_ssl_io_no_server_cert);
 1730            }
 1731
 01732            X509Certificate2? withKey = SslStream.FindCertificateWithPrivateKey(this, isServer: true, selected);
 01733            if (withKey is null)
 01734            {
 01735                throw new AuthenticationException(SR.net_ssl_io_no_server_cert);
 1736            }
 1737
 01738            SetSessionCertificateContext(
 01739                SslStreamCertificateContext.Create(withKey, additionalCertificates: null, offline: false, trust: null, n
 01740                takeOwnership: true);
 01741            return true;
 01742        }
 1743
 1744        // â”€â”€ Internal surface for the SslStream wedge (Linux/FreeBSD only) â”€
 1745
 1746        // Direct accessors used by SslStream to mirror state into its own fields after
 1747        // each handshake step. Both handles are owned by this TlsSession; SslStream
 1748        // observes them via the mirror but does not dispose them.
 1749        // Set by the SslStream wedge: SslStream owns the validation flow and will
 1750        // invoke the user callback itself with the SslStream as the sender. Skipping
 1751        // here avoids invoking the callback twice and avoids handing TlsSession to
 1752        // user code that expects SslStream.
 1753        internal bool SuppressInternalCertificateValidation
 1754        {
 1755            get => _suppressInternalCertificateValidation;
 01756            set => _suppressInternalCertificateValidation = value;
 1757        }
 1758
 01759        internal TlsSecurityContext? SecurityContext => _securityContext;
 1760        internal TlsContext Context => _context!;
 1761        internal SafeFreeCredentials? CredentialsHandle
 1762        {
 01763            get => ActiveCredentialsRef();
 1764            set
 01765            {
 01766                if (_sessionCredentialsHandle is not null)
 01767                {
 01768                    _sessionCredentialsHandle = value;
 01769                }
 1770                else
 01771                {
 01772                    _context!.CredentialsHandle = value;
 01773                }
 01774            }
 1775        }
 1776
 1777        // Returns a ref to the credentials handle this session should use for its next
 1778        // PAL call. When _sessionCredentialsHandle is set (via SetClientCertificateContext),
 1779        // it takes precedence; otherwise the shared TlsContext.CredentialsHandle is used.
 1780        // Class instance refs have unrestricted lifetime, no [UnscopedRef] needed.
 1781        private ref SafeFreeCredentials? ActiveCredentialsRef()
 01782            => ref (_sessionCredentialsHandle is not null
 01783                    ? ref _sessionCredentialsHandle
 01784                    : ref _context!.CredentialsHandle);
 1785
 1786        // SslStream's GenerateToken replacement. Drives one ASC/ISC step via PAL and
 1787        // updates internal handshake-complete state. Returns the raw PAL token so the
 1788        // caller can preserve existing ProtocolToken-based plumbing (alerts, error
 1789        // mapping, NetEventSource).
 1790        internal ProtocolToken HandshakeStepForSslStream(ReadOnlySpan<byte> input, out int bytesConsumed)
 01791        {
 01792            ThrowIfDisposed();
 1793
 1794            ProtocolToken token;
 01795            if (_context!.IsServer)
 01796            {
 01797                token = SslStreamPal.AcceptSecurityContext(
 01798                    ref ActiveCredentialsRef(),
 01799                    ref _securityContext,
 01800                    input,
 01801                    out bytesConsumed,
 01802                    _options);
 01803            }
 1804            else
 01805            {
 01806                string hostName = TargetHostNameHelper.NormalizeHostName(_options.TargetHost);
 01807                token = SslStreamPal.InitializeSecurityContext(
 01808                    ref ActiveCredentialsRef(),
 01809                    ref _securityContext,
 01810                    hostName,
 01811                    input,
 01812                    out bytesConsumed,
 01813                    _options);
 01814            }
 1815
 01816            if (token.Status.ErrorCode == SecurityStatusPalErrorCode.OK)
 01817            {
 01818                OnHandshakeCompleted();
 01819            }
 1820
 01821            return token;
 01822        }
 1823
 1824        private void OnHandshakeCompleted()
 01825        {
 01826            _isHandshakeComplete = true;
 01827            SslStreamPal.QueryContextConnectionInfo(_securityContext!, ref _connectionInfo);
 01828            SslStreamPal.QueryContextStreamSizes(_securityContext!, out StreamSizes streamSizes);
 01829            _headerSize = streamSizes.Header;
 01830            _trailerSize = streamSizes.Trailer;
 01831            if (streamSizes.MaximumMessage > 0)
 01832            {
 01833                _maxDataSize = Math.Min(streamSizes.MaximumMessage, MaxRecordPlaintext);
 01834            }
 1835
 1836            // Invoke remote-certificate validation callback (mirrors SslStream).
 1837            // Client: always validate the server cert.
 1838            // Server: always suspend so the caller's RemoteCertificateValidationCallback runs
 1839            // (it must see optional client certs and the no-cert case alike â€” only the
 1840            // RemoteCertificateNotAvailable error is suppressed in VerifyRemoteCertificateCore
 1841            // when there is no user callback and RemoteCertRequired is false).
 01842            if (_suppressInternalCertificateValidation)
 01843            {
 01844                return;
 1845            }
 1846
 1847            // If the caller already resolved validation via a prior suspension
 1848            // (defensive â€” current OpenSSL/SChannel paths only suspend once via
 1849            // the post-handshake hook below), don't re-suspend here.
 01850            if (_externalValidationResolved)
 01851            {
 01852                return;
 1853            }
 1854
 01855            CaptureRemoteCertificateForExternalValidation();
 01856        }
 1857
 1858        // Capture the peer certificate and chain so the caller can perform validation
 1859        // out of band. Keeps the cert in _externalPendingCert (not _remoteCertificate)
 1860        // so VerifyRemoteCertificateCore's renegotiation shortcut doesn't dispose it
 1861        // when AcceptWithDefaultValidation runs.
 1862        private void CaptureRemoteCertificateForExternalValidation()
 01863        {
 01864            X509ChainPolicy? chainPolicy = _options.CertificateChainPolicy?.Clone();
 01865            int preexistingExtraCertsCount = chainPolicy?.ExtraStore.Count ?? 0;
 01866            X509Chain? chain = null;
 01867            X509Certificate2Collection? intermediates = null;
 1868
 1869            try
 01870            {
 01871                _externalPendingCert = CertificateValidationPal.GetRemoteCertificate(
 01872                    _securityContext, ref chain, chainPolicy);
 1873
 01874                if (chain is not null)
 01875                {
 01876                    X509Certificate2Collection extraStore = chain.ChainPolicy.ExtraStore;
 01877                    while (extraStore.Count > preexistingExtraCertsCount)
 01878                    {
 01879                        X509Certificate2 certificate = extraStore[preexistingExtraCertsCount];
 01880                        extraStore.RemoveAt(preexistingExtraCertsCount);
 1881
 01882                        bool transferred = false;
 1883                        try
 01884                        {
 01885                            if (_externalPendingCert is null ||
 01886                                !certificate.RawDataMemory.Span.SequenceEqual(_externalPendingCert.RawDataMemory.Span))
 01887                            {
 01888                                (intermediates ??= new X509Certificate2Collection()).Add(certificate);
 01889                                transferred = true;
 01890                            }
 01891                        }
 1892                        finally
 01893                        {
 01894                            if (!transferred)
 01895                            {
 01896                                certificate.Dispose();
 01897                            }
 01898                        }
 01899                    }
 01900                }
 1901
 01902                _externalRemoteCertificates = intermediates;
 01903                intermediates = null;
 01904            }
 1905            finally
 01906            {
 01907                if (intermediates is not null)
 01908                {
 01909                    foreach (X509Certificate2 certificate in intermediates)
 01910                    {
 01911                        certificate.Dispose();
 01912                    }
 01913                }
 1914
 01915                if (chain is not null)
 01916                {
 01917                    X509Certificate2Collection extraStore = chain.ChainPolicy.ExtraStore;
 01918                    while (extraStore.Count > preexistingExtraCertsCount)
 01919                    {
 01920                        X509Certificate2 certificate = extraStore[preexistingExtraCertsCount];
 01921                        extraStore.RemoveAt(preexistingExtraCertsCount);
 01922                        certificate.Dispose();
 01923                    }
 1924
 01925                    chain.Dispose();
 01926                }
 01927            }
 1928
 01929            _externalValidationPending = true;
 01930        }
 1931
 1932        // Acquire the SafeFreeCredentials the PAL needs for the first ASC/ISC
 1933        // call. OpenSSL handles credential acquisition lazily inside the PAL,
 1934        // but SChannel rejects ASC/ISC with a null credentials handle.
 1935        //
 1936        // Server requires a pre-set CertificateContext (or one resolved via
 1937        // ServerCertSelectionDelegate above); the client connects anonymously.
 1938        // SslSessionsCache, the legacy CertSelectionDelegate, and client
 1939        // certificate selection are not yet integrated.
 1940        private void EnsureCredentialsAcquired()
 01941        {
 1942            // If SetContext or SetClientCertificateContext already produced a
 1943            // session-local handle, ActiveCredentialsRef() will route the PAL
 1944            // through it. Skip touching the shared TlsContext.CredentialsHandle
 1945            // to avoid racing with concurrent sessions on the same context.
 01946            if (_sessionCredentialsHandle is not null)
 01947            {
 01948                return;
 1949            }
 1950
 01951            if (_context!.CredentialsHandle is not null)
 01952            {
 01953                return;
 1954            }
 1955
 1956            // Multiple sessions on the same TlsContext can call EnsureCredentialsAcquired
 1957            // concurrently and each see CredentialsHandle == null. Atomically install
 1958            // ours; if another session beat us to it, dispose the loser to avoid a leak.
 1959            // Non-Windows PALs return null here (OpenSSL has no cred handle concept); the
 1960            // CompareExchange is a no-op in that case.
 01961            SafeFreeCredentials? acquired = SslStreamPal.AcquireCredentialsHandle(_options, false);
 01962            if (System.Threading.Interlocked.CompareExchange(ref _context!.CredentialsHandle, acquired, null) is not nul
 01963            {
 01964                acquired?.Dispose();
 01965            }
 01966        }
 1967
 1968        // Feed a decrypted post-handshake message (e.g. TLS 1.3 NewSessionTicket
 1969        // or KeyUpdate) back through ASC/ISC so SChannel updates its internal
 1970        // state. The PAL may or may not produce a reply token; if it does, stage
 1971        // it for the caller to send on the next drain.
 1972        private void ProcessPostHandshakeMessage(ReadOnlySpan<byte> data)
 01973        {
 01974            if (data.IsEmpty)
 01975            {
 01976                return;
 1977            }
 1978
 01979            ProtocolToken token = default;
 01980            token.RentBuffer = true;
 1981            try
 01982            {
 01983                if (_context!.IsServer)
 01984                {
 01985                    token = SslStreamPal.AcceptSecurityContext(
 01986                        ref ActiveCredentialsRef(),
 01987                        ref _securityContext,
 01988                        data,
 01989                        out _,
 01990                        _options);
 01991                }
 1992                else
 01993                {
 01994                    string hostName = TargetHostNameHelper.NormalizeHostName(_options.TargetHost);
 01995                    token = SslStreamPal.InitializeSecurityContext(
 01996                        ref ActiveCredentialsRef(),
 01997                        ref _securityContext,
 01998                        hostName,
 01999                        data,
 02000                        out _,
 02001                        _options);
 02002                }
 2003
 02004                if (token.Size > 0)
 02005                {
 02006                    Debug.Assert(token.Payload != null);
 02007                    AppendPending(new ReadOnlySpan<byte>(token.Payload, 0, token.Size));
 02008                }
 02009            }
 2010            finally
 02011            {
 02012                token.ReleasePayload();
 02013            }
 02014        }
 2015
 2016        // â”€â”€ Socket-bound I/O â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
 2017        //
 2018        // These methods are only valid when the session was created via
 2019        // Create(TlsContext, SafeSocketHandle). They drive ciphertext on the
 2020        // bound non-blocking socket and translate WouldBlock into WantRead/
 2021        // WantWrite back to the caller so a select/epoll/IOCP-like loop can
 2022        // schedule the next attempt.
 2023
 2024        private const int SocketScratchSize = MaxRecordPlaintext + 256;
 2025
 2026        private void ThrowIfNotSocketBound()
 02027        {
 02028            if (_socketHandle is null)
 02029            {
 02030                throw new InvalidOperationException(SR.net_tlssession_not_socket_bound);
 2031            }
 02032        }
 2033
 2034        // Drains any TLS bytes that we previously failed to fully send into the
 2035        // socket. Returns true if pending output is now empty, false if the
 2036        // socket would block (WantWrite should be surfaced).
 2037        private bool TryDrainPendingToSocket(out SocketError lastError)
 02038        {
 02039            lastError = SocketError.Success;
 02040            while (_pendingBuffer.ActiveLength > 0)
 02041            {
 02042                int sent = _socket!.Send(
 02043                    _pendingBuffer.ActiveReadOnlySpan,
 02044                    SocketFlags.None,
 02045                    out SocketError err);
 02046                lastError = err;
 02047                if (sent > 0)
 02048                {
 02049                    _pendingBuffer.Discard(sent);
 02050                    if (_pendingBuffer.ActiveLength == 0)
 02051                    {
 02052                        return true;
 2053                    }
 02054                    continue;
 2055                }
 02056                return false;
 2057            }
 02058            return true;
 02059        }
 2060
 2061        private protected TlsOperationStatus HandshakeSocketCore()
 02062        {
 02063            ThrowIfDisposed();
 02064            ThrowIfContextNotSet();
 02065            ThrowIfNotSocketBound();
 2066
 02067            if (_isHandshakeComplete && !_externalValidationPending && !_externalValidationResolved)
 02068            {
 02069                return TlsOperationStatus.Complete;
 2070            }
 2071
 02072            TlsOperationStatus? fast = null;
 2073            TryFastHandshake(ref fast);
 02074            if (fast.HasValue)
 02075            {
 02076                return fast.Value;
 2077            }
 2078
 2079            TryPeekClientHello(ref fast);
 02080            if (fast.HasValue)
 02081            {
 02082                return fast.Value;
 2083            }
 2084
 02085            _socketInBuffer.EnsureAvailableSpace(SocketScratchSize);
 02086            byte[] scratch = ArrayPool<byte>.Shared.Rent(SocketScratchSize);
 2087            try
 02088            {
 02089                while (true)
 02090                {
 02091                    if (_pendingBuffer.ActiveLength > 0)
 02092                    {
 02093                        if (!TryDrainPendingToSocket(out SocketError drainErr))
 02094                        {
 02095                            if (drainErr == SocketError.WouldBlock)
 02096                            {
 02097                                return TlsOperationStatus.DestinationTooSmall;
 2098                            }
 02099                            throw new SocketException((int)drainErr);
 2100                        }
 02101                    }
 2102
 02103                    TlsOperationStatus status = HandshakeBufferedCore(
 02104                        _socketInBuffer.ActiveReadOnlySpan,
 02105                        scratch,
 02106                        out int consumed,
 02107                        out int produced);
 2108
 02109                    if (consumed > 0)
 02110                    {
 02111                        _socketInBuffer.Discard(consumed);
 02112                    }
 2113
 02114                    if (produced > 0)
 02115                    {
 02116                        int offset = 0;
 02117                        while (offset < produced)
 02118                        {
 02119                            int sent = _socket!.Send(
 02120                                new ReadOnlySpan<byte>(scratch, offset, produced - offset),
 02121                                SocketFlags.None,
 02122                                out SocketError sendErr);
 02123                            if (sent > 0)
 02124                            {
 02125                                offset += sent;
 02126                                continue;
 2127                            }
 02128                            if (sendErr == SocketError.WouldBlock)
 02129                            {
 2130                                // Stash the unsent tail so the next call resumes the drain.
 02131                                AppendPending(new ReadOnlySpan<byte>(scratch, offset, produced - offset));
 02132                                return TlsOperationStatus.DestinationTooSmall;
 2133                            }
 02134                            throw new SocketException((int)sendErr);
 2135                        }
 02136                    }
 2137
 02138                    switch (status)
 2139                    {
 2140                        case TlsOperationStatus.Complete:
 02141                            return TlsOperationStatus.Complete;
 2142
 2143                        case TlsOperationStatus.NeedMoreData:
 2144                            // Should not happen with conservative scratch sizing, but guard.
 02145                            _socketInBuffer.EnsureAvailableSpace(1);
 02146                            int received = _socket!.Receive(
 02147                                _socketInBuffer.AvailableSpan,
 02148                                SocketFlags.None,
 02149                                out SocketError recvErr);
 02150                            if (received > 0)
 02151                            {
 02152                                _socketInBuffer.Commit(received);
 02153                                continue;
 2154                            }
 02155                            if (recvErr == SocketError.WouldBlock)
 02156                            {
 02157                                return TlsOperationStatus.NeedMoreData;
 2158                            }
 02159                            if (received == 0)
 02160                            {
 02161                                return TlsOperationStatus.Closed;
 2162                            }
 02163                            throw new SocketException((int)recvErr);
 2164
 2165                        case TlsOperationStatus.DestinationTooSmall:
 2166                            // Output is staged; loop drains it on next iteration.
 02167                            continue;
 2168
 2169                        default:
 02170                            return status;
 2171                    }
 2172                }
 2173            }
 2174            finally
 02175            {
 02176                ArrayPool<byte>.Shared.Return(scratch);
 02177            }
 02178        }
 2179
 2180        private protected TlsOperationStatus ReadSocketCore(Span<byte> buffer, out int bytesRead)
 02181        {
 02182            ThrowIfDisposed();
 02183            ThrowIfNotSocketBound();
 2184            // A prior handshake may have surfaced NeedsCertificateValidation whose result the
 2185            // caller has not recorded yet. The buffered Read core gates on this, but the socket
 2186            // core can return NeedMoreData off a non-blocking recv before ever reaching it, so
 2187            // apply the guard here to stay consistent with the buffered path and socket Write.
 02188            ThrowIfPendingExternalValidation();
 02189            bytesRead = 0;
 2190
 02191            if (!_isHandshakeComplete)
 02192            {
 02193                throw new InvalidOperationException(SR.net_tlssession_handshake_not_complete);
 2194            }
 2195
 02196            TlsOperationStatus? fast = null;
 2197            TryFastRead(buffer, ref bytesRead, ref fast);
 02198            if (fast.HasValue)
 02199            {
 02200                return fast.Value;
 2201            }
 2202
 02203            _socketInBuffer.EnsureAvailableSpace(SocketScratchSize);
 2204
 02205            while (true)
 02206            {
 02207                if (_socketInBuffer.ActiveLength > 0)
 02208                {
 02209                    TlsOperationStatus status = ReadBufferedCore(
 02210                        _socketInBuffer.ActiveReadOnlySpan,
 02211                        buffer,
 02212                        out int consumed,
 02213                        out int produced);
 2214
 02215                    if (consumed > 0)
 02216                    {
 02217                        _socketInBuffer.Discard(consumed);
 02218                    }
 2219
 02220                    bytesRead = produced;
 2221
 02222                    if (status == TlsOperationStatus.Complete && produced > 0)
 02223                    {
 02224                        return TlsOperationStatus.Complete;
 2225                    }
 02226                    if (status == TlsOperationStatus.Closed)
 02227                    {
 02228                        return TlsOperationStatus.Closed;
 2229                    }
 02230                    if (status == TlsOperationStatus.Complete && produced == 0)
 02231                    {
 2232                        // Post-handshake message consumed; loop to try more.
 02233                        continue;
 2234                    }
 02235                    if (status != TlsOperationStatus.NeedMoreData)
 02236                    {
 02237                        return status;
 2238                    }
 2239                    // WantRead: fall through to socket recv.
 02240                }
 2241
 02242                _socketInBuffer.EnsureAvailableSpace(1);
 02243                int received = _socket!.Receive(
 02244                    _socketInBuffer.AvailableSpan,
 02245                    SocketFlags.None,
 02246                    out SocketError recvErr);
 02247                if (received > 0)
 02248                {
 02249                    _socketInBuffer.Commit(received);
 02250                    continue;
 2251                }
 02252                if (recvErr == SocketError.WouldBlock)
 02253                {
 02254                    return TlsOperationStatus.NeedMoreData;
 2255                }
 02256                if (received == 0)
 02257                {
 02258                    return TlsOperationStatus.Closed;
 2259                }
 02260                throw new SocketException((int)recvErr);
 2261            }
 02262        }
 2263
 2264        private protected TlsOperationStatus WriteSocketCore(ReadOnlySpan<byte> buffer, out int bytesWritten)
 02265        {
 02266            ThrowIfDisposed();
 02267            ThrowIfNotSocketBound();
 2268            // Empty-buffer writes short-circuit before reaching WriteBufferedCore's guard, so
 2269            // gate on any unrecorded external-validation result here too, matching socket Read.
 02270            ThrowIfPendingExternalValidation();
 02271            bytesWritten = 0;
 2272
 02273            if (!_isHandshakeComplete)
 02274            {
 02275                throw new InvalidOperationException(SR.net_tlssession_handshake_not_complete);
 2276            }
 2277
 02278            TlsOperationStatus? fast = null;
 2279            TryFastWrite(buffer, ref bytesWritten, ref fast);
 02280            if (fast.HasValue)
 02281            {
 02282                return fast.Value;
 2283            }
 2284
 2285            // Drain any previously stashed ciphertext first.
 02286            if (_pendingBuffer.ActiveLength > 0)
 02287            {
 02288                if (!TryDrainPendingToSocket(out SocketError drainErr))
 02289                {
 02290                    if (drainErr == SocketError.WouldBlock)
 02291                    {
 02292                        return TlsOperationStatus.DestinationTooSmall;
 2293                    }
 02294                    throw new SocketException((int)drainErr);
 2295                }
 02296            }
 2297
 02298            if (buffer.IsEmpty)
 02299            {
 02300                return TlsOperationStatus.Complete;
 2301            }
 2302
 02303            byte[] scratch = ArrayPool<byte>.Shared.Rent(SocketScratchSize);
 2304            try
 02305            {
 02306                int totalConsumed = 0;
 02307                while (totalConsumed < buffer.Length)
 02308                {
 02309                    TlsOperationStatus encStatus = WriteBufferedCore(
 02310                        buffer.Slice(totalConsumed),
 02311                        scratch,
 02312                        out int consumed,
 02313                        out int produced);
 2314
 02315                    totalConsumed += consumed;
 2316
 02317                    if (produced > 0)
 02318                    {
 02319                        int offset = 0;
 02320                        while (offset < produced)
 02321                        {
 02322                            int sent = _socket!.Send(
 02323                                new ReadOnlySpan<byte>(scratch, offset, produced - offset),
 02324                                SocketFlags.None,
 02325                                out SocketError sendErr);
 02326                            if (sent > 0)
 02327                            {
 02328                                offset += sent;
 02329                                continue;
 2330                            }
 02331                            if (sendErr == SocketError.WouldBlock)
 02332                            {
 02333                                AppendPending(new ReadOnlySpan<byte>(scratch, offset, produced - offset));
 02334                                bytesWritten = totalConsumed;
 02335                                return TlsOperationStatus.DestinationTooSmall;
 2336                            }
 02337                            throw new SocketException((int)sendErr);
 2338                        }
 02339                    }
 2340
 02341                    if (encStatus == TlsOperationStatus.DestinationTooSmall)
 02342                    {
 2343                        // Pending output owed; resume next call.
 02344                        bytesWritten = totalConsumed;
 02345                        return TlsOperationStatus.DestinationTooSmall;
 2346                    }
 02347                    if (encStatus != TlsOperationStatus.Complete)
 02348                    {
 02349                        bytesWritten = totalConsumed;
 02350                        return encStatus;
 2351                    }
 02352                    if (consumed == 0)
 02353                    {
 2354                        // Nothing more to do (shouldn't happen with non-empty buffer).
 02355                        break;
 2356                    }
 02357                }
 2358
 02359                bytesWritten = totalConsumed;
 02360                return TlsOperationStatus.Complete;
 2361            }
 2362            finally
 02363            {
 02364                ArrayPool<byte>.Shared.Return(scratch);
 02365            }
 02366        }
 2367
 2368        // Simple driver that runs a buffered "output-only" op (Shutdown /
 2369        // RequestClientCertificate) and drains its staged ciphertext to the socket.
 2370        private TlsOperationStatus DriveBufferedOpOverSocket(Func<Span<byte>, (TlsOperationStatus status, int written)> 
 02371        {
 02372            ThrowIfDisposed();
 02373            ThrowIfNotSocketBound();
 2374
 2375            // Drain any leftover pending output before staging new bytes.
 02376            if (_pendingBuffer.ActiveLength > 0)
 02377            {
 02378                if (!TryDrainPendingToSocket(out SocketError leftoverErr))
 02379                {
 02380                    if (leftoverErr == SocketError.WouldBlock)
 02381                    {
 02382                        return TlsOperationStatus.DestinationTooSmall;
 2383                    }
 02384                    throw new SocketException((int)leftoverErr);
 2385                }
 02386            }
 2387
 02388            byte[] scratch = ArrayPool<byte>.Shared.Rent(SocketScratchSize);
 2389            try
 02390            {
 02391                (TlsOperationStatus status, int written) = op(scratch);
 02392                if (written > 0)
 02393                {
 02394                    int offset = 0;
 02395                    while (offset < written)
 02396                    {
 02397                        int sent = _socket!.Send(
 02398                            new ReadOnlySpan<byte>(scratch, offset, written - offset),
 02399                            SocketFlags.None,
 02400                            out SocketError sendErr);
 02401                        if (sent > 0)
 02402                        {
 02403                            offset += sent;
 02404                            continue;
 2405                        }
 02406                        if (sendErr == SocketError.WouldBlock)
 02407                        {
 02408                            AppendPending(new ReadOnlySpan<byte>(scratch, offset, written - offset));
 02409                            return TlsOperationStatus.DestinationTooSmall;
 2410                        }
 02411                        throw new SocketException((int)sendErr);
 2412                    }
 02413                }
 02414                return status;
 2415            }
 2416            finally
 02417            {
 02418                ArrayPool<byte>.Shared.Return(scratch);
 02419            }
 02420        }
 2421
 2422        private protected TlsOperationStatus ShutdownSocketCore()
 02423            => DriveBufferedOpOverSocket(dest =>
 02424            {
 02425                TlsOperationStatus s = ShutdownBufferedCore(dest, out int w);
 02426                return (s, w);
 02427            });
 2428
 2429        private protected TlsOperationStatus RequestClientCertificateSocketCore()
 02430        {
 02431            ThrowIfDisposed();
 02432            ThrowIfNotSocketBound();
 2433            // The fd fast path below bypasses the buffered core, so apply the external-validation
 2434            // guard here as well, matching the other socket-bound operations.
 02435            ThrowIfPendingExternalValidation();
 2436
 02437            TlsOperationStatus? fast = null;
 2438            TryFastRequestClientCertificate(ref fast);
 02439            if (fast.HasValue)
 02440            {
 02441                return fast.Value;
 2442            }
 2443
 02444            return DriveBufferedOpOverSocket(dest =>
 02445            {
 02446                TlsOperationStatus s = RequestClientCertificateBufferedCore(dest, out int w);
 02447                return (s, w);
 02448            });
 02449        }
 2450
 2451        // Platform hooks. Implemented by the OpenSSL partial (TlsSession.OpenSsl.cs)
 2452        // to bind the socket fd directly to the SSL object and drive ciphertext
 2453        // through OpenSSL. On Windows (SChannel) these are no-ops and the buffered
 2454        // ProcessHandshake/Encrypt/Decrypt path above is used unchanged.
 2455        partial void EnableNativeSocketBinding(SafeSocketHandle socket, ref bool nativeBindingEnabled);
 2456        partial void TryFastHandshake(ref TlsOperationStatus? result);
 2457        partial void TryFastRequestClientCertificate(ref TlsOperationStatus? result);
 2458        partial void TryPeekClientHello(ref TlsOperationStatus? result);
 2459        partial void TryFastRead(Span<byte> buffer, ref int bytesRead, ref TlsOperationStatus? result);
 2460        partial void TryFastWrite(ReadOnlySpan<byte> buffer, ref int bytesWritten, ref TlsOperationStatus? result);
 2461
 2462        // Fires at the end of SetContext. Platforms with a deferred-server
 2463        // fast path (OpenSSL socket-bound sessions) use this hook to activate
 2464        // native binding now that server options are known.
 2465        partial void OnServerContextSet();
 2466
 2467        // Fires from Dispose so the OpenSSL partial can release the peek BIO if the
 2468        // session is disposed before its ownership is transferred to an SSL* handle.
 2469        partial void OnDispose();
 2470
 2471        // Fires from GetClientHelloBytes so the OpenSSL partial can return a span
 2472        // over the socket-replay BIO's retained peek buffer. No-op on the buffered
 2473        // path; the getter falls back to the managed byte[] copy.
 2474        partial void TryGetNativeClientHelloBytes(ref ReadOnlySpan<byte> bytes);
 2475
 2476        public void Dispose()
 02477        {
 02478            if (_disposed)
 02479            {
 02480                return;
 2481            }
 02482            _disposed = true;
 2483
 02484            DisposeExternalRemoteCertificates();
 02485            _externalPendingCert?.Dispose();
 02486            _externalPendingCert = null;
 2487
 02488            _securityContext?.Dispose();
 02489            _securityContext = null;
 2490
 2491            // Disposes the underlying SafeSocketHandle as well (ownership transferred at Create).
 02492            if (_socket is not null)
 02493            {
 02494                _socket.Dispose();
 02495                _socket = null;
 02496            }
 2497            else
 02498            {
 02499                _socketHandle?.Dispose();
 02500            }
 02501            _socketHandle = null;
 2502
 02503            if (_ownsOptions)
 02504            {
 02505                _options.Dispose();
 02506            }
 2507
 02508            _pendingBuffer.Dispose();
 02509            if (_decryptScratch != null)
 02510            {
 02511                ArrayPool<byte>.Shared.Return(_decryptScratch);
 02512                _decryptScratch = null;
 02513            }
 02514            _socketInBuffer.Dispose();
 2515
 2516            // Release the session-local credentials handle acquired by
 2517            // SetContext / SetClientCertificateContext. The shared handle on
 2518            // _context is owned by TlsContext and released with it.
 02519            _sessionCredentialsHandle?.Dispose();
 02520            _sessionCredentialsHandle = null;
 2521
 2522            // Release the session-owned CertificateContext (only true when we built
 2523            // one via the server-cert-selector path). Caller-provided contexts and the
 2524            // template context inherited from TlsContext are not disposed here.
 02525            if (_ownsSessionCertificateContext && _sessionCertificateContext is not null)
 02526            {
 02527                _sessionCertificateContext.ReleaseResources();
 02528            }
 02529            _sessionCertificateContext = null;
 02530            _ownsSessionCertificateContext = false;
 2531
 2532            OnDispose();
 02533        }
 2534    }
 2535}
 2536

Methods/Properties

.ctor()
AttachSocket(System.Net.Sockets.SafeSocketHandle)
SessionCertificateContext()
SetSessionCertificateContext(System.Net.Security.SslStreamCertificateContext,System.Boolean)
InitializeFromContext(System.Net.Security.TlsContext)
OnContextInitialized()
IsHandshakeComplete()
HasPendingOutput()
TargetHostName()
TargetHostName(System.String)
NegotiatedProtocol()
NegotiatedCipherSuite()
NegotiatedApplicationProtocol()
GetRemoteCertificate()
GetRemoteCertificates()
AcceptWithDefaultValidation()
SetRemoteCertificateValidationResult(System.Net.Security.SslPolicyErrors)
ClientHelloInfo()
GetClientHelloLength()
TryGetClientHelloBytes(System.Span`1<System.Byte>,System.Int32&)
SetContext(System.Net.Security.TlsContext)
SetClientCertificateContext(System.Net.Security.SslStreamCertificateContext)
GetAcceptableIssuers()
ThrowIfPendingExternalValidation()
DisposeExternalRemoteCertificates()
LocalCertificate()
GetChannelBinding(System.Security.Authentication.ExtendedProtection.ChannelBindingKind)
HandshakeBufferedCore(System.ReadOnlySpan`1<System.Byte>,System.Span`1<System.Byte>,System.Int32&,System.Int32&)
WriteBufferedCore(System.ReadOnlySpan`1<System.Byte>,System.Span`1<System.Byte>,System.Int32&,System.Int32&)
ReadBufferedCore(System.ReadOnlySpan`1<System.Byte>,System.Span`1<System.Byte>,System.Int32&,System.Int32&)
TryDrainBufferedPlaintext(System.Span`1<System.Byte>,System.Int32&)
RequestClientCertificateBufferedCore(System.Span`1<System.Byte>,System.Int32&)
ShutdownBufferedCore(System.Span`1<System.Byte>,System.Int32&)
DrainPendingOutputCore(System.Span`1<System.Byte>,System.Int32&)
AppendPending(System.ReadOnlySpan`1<System.Byte>)
DrainTo(System.Span`1<System.Byte>)
EnsureDecryptScratch(System.Int32)
ThrowIfDisposed()
ThrowIfContextNotSet()
TryParseClientHello(System.ReadOnlySpan`1<System.Byte>,System.Int32&)
ResolveServerCertificateFromClientHello(System.ReadOnlySpan`1<System.Byte>)
SuppressInternalCertificateValidation(System.Boolean)
SecurityContext()
CredentialsHandle()
CredentialsHandle(System.Net.Security.SafeFreeCredentials)
ActiveCredentialsRef()
HandshakeStepForSslStream(System.ReadOnlySpan`1<System.Byte>,System.Int32&)
OnHandshakeCompleted()
CaptureRemoteCertificateForExternalValidation()
EnsureCredentialsAcquired()
ProcessPostHandshakeMessage(System.ReadOnlySpan`1<System.Byte>)
ThrowIfNotSocketBound()
TryDrainPendingToSocket(System.Net.Sockets.SocketError&)
HandshakeSocketCore()
ReadSocketCore(System.Span`1<System.Byte>,System.Int32&)
WriteSocketCore(System.ReadOnlySpan`1<System.Byte>,System.Int32&)
DriveBufferedOpOverSocket(System.Func`2<System.Span`1<System.Byte>,System.ValueTuple`2<System.Net.Security.TlsOperationStatus,System.Int32>>)
ShutdownSocketCore()
RequestClientCertificateSocketCore()
Dispose()