< Summary

Information
Class: System.Net.Security.TlsSession
Assembly: System.Net.Security
File(s): D:\runner\runtime\src\libraries\System.Net.Security\src\System\Net\Security\TlsSession.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 1332
Coverable lines: 1332
Total lines: 2571
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 568
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%12120%
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%1141140%
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)

D:\runner\runtime\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        /// <para>
 351        /// Must be called only after <see cref="TlsBufferSession.Handshake"/> returned
 352        /// <see cref="TlsOperationStatus.NeedsCertificateValidation"/> and before
 353        /// <see cref="SetRemoteCertificateValidationResult"/> is called.
 354        /// </para>
 355        /// <para>
 356        /// Any <see cref="SslCertificateTrust"/> associated with the session's certificate
 357        /// context is used for chain validation unless a custom chain policy is specified by
 358        /// <see cref="SslClientAuthenticationOptions.CertificateChainPolicy"/> or
 359        /// <see cref="SslServerAuthenticationOptions.CertificateChainPolicy"/>.
 360        /// </para>
 361        /// </remarks>
 362        public SslPolicyErrors AcceptWithDefaultValidation()
 0363        {
 0364            ThrowIfDisposed();
 0365            if (!_externalValidationPending)
 0366            {
 0367                throw new InvalidOperationException(
 0368                    SR.Format(SR.net_tlssession_validation_not_pending, nameof(AcceptWithDefaultValidation)));
 369            }
 370
 371            // Build a fresh X509Chain locally. VerifyRemoteCertificateCore applies the configured
 372            // chain policy before adding the peer-sent intermediates captured by this session.
 0373            using X509Chain chain = new X509Chain();
 374
 0375            ProtocolToken alertToken = default;
 0376            SslPolicyErrors sslPolicyErrors = SslPolicyErrors.None;
 377            bool ok;
 378            try
 0379            {
 380                // Pass _externalPendingCert as the candidate cert and an empty _remoteCertificate slot.
 381                // VerifyRemoteCertificateCore assigns the slot to the candidate on success; the renegotiation
 382                // shortcut at the top of that method would otherwise dispose our cert if the slot were already
 383                // populated with the same instance.
 0384                ok = SslStream.VerifyRemoteCertificateCore(
 0385                    this,
 0386                    // The external certificate is being (re)validated after the handshake, so the
 0387                    // resumption shortcut in VerifyRemoteCertificateCore must not apply here.
 0388                    isInitialHandshake: false,
 0389                    _options,
 0390                    _securityContext,
 0391                    ref _remoteCertificate,
 0392                    ref _connectionInfo,
 0393                    _externalPendingCert,
 0394                    chain,
 0395                    trust: SessionCertificateContext?.Trust,
 0396                    ref alertToken,
 0397                    ref sslPolicyErrors,
 0398                    out _,
 0399                    out _,
 0400                    _externalRemoteCertificates,
 0401                    cloneCertificateChainPolicy: true);
 0402            }
 403            finally
 0404            {
 405                // Dispose the certificates that chain.Build() populated into ChainElements so
 406                // they don't linger until GC. Mirrors SslStream.VerifyRemoteCertificate's cleanup
 407                // when no user callback is provided. ExtraStore entries were supplied by the caller
 408                // in _externalRemoteCertificates and are intentionally left alone.
 0409                int elementsCount = chain.ChainElements.Count;
 0410                for (int i = 0; i < elementsCount; i++)
 0411                {
 0412                    chain.ChainElements[i].Certificate.Dispose();
 0413                }
 0414            }
 415
 416            // A user RemoteCertificateValidationCallback can reject an otherwise-clean chain
 417            // by returning false with sslPolicyErrors == None. Synthesize a non-None failure
 418            // so SetRemoteCertificateValidationResult takes the reject branch instead of accepting.
 0419            if (!ok && sslPolicyErrors == SslPolicyErrors.None)
 0420            {
 0421                sslPolicyErrors = SslPolicyErrors.RemoteCertificateChainErrors;
 0422            }
 423
 424            // On success VerifyRemoteCertificateCore set _remoteCertificate = _externalPendingCert, so
 425            // SetRemoteCertificateValidationResult below leaves it alone. On failure we must dispose the
 426            // pending cert ourselves because no one adopted it.
 0427            SetRemoteCertificateValidationResult(ok ? SslPolicyErrors.None : sslPolicyErrors);
 0428            return sslPolicyErrors;
 0429        }
 430
 431        /// <summary>
 432        /// Records the caller's external certificate-validation result.
 433        /// <see cref="SslPolicyErrors.None"/> means accept; any other value causes
 434        /// subsequent calls to <see cref="TlsBufferSession.Handshake"/>, <see cref="TlsBufferSession.Write"/>,
 435        /// and <see cref="TlsBufferSession.Read"/> to throw <see cref="AuthenticationException"/>.
 436        /// Must be called exactly once between
 437        /// <see cref="TlsOperationStatus.NeedsCertificateValidation"/> and the next
 438        /// session operation.
 439        /// </summary>
 440        public void SetRemoteCertificateValidationResult(SslPolicyErrors errors)
 0441        {
 0442            ThrowIfDisposed();
 0443            if (!_externalValidationPending)
 0444            {
 0445                throw new InvalidOperationException(
 0446                    SR.Format(SR.net_tlssession_validation_not_pending, nameof(SetRemoteCertificateValidationResult)));
 447            }
 448
 0449            _externalValidationPending = false;
 0450            _externalValidationResolved = true;
 451
 452            // If the PAL paused mid-handshake pending external validation (SecureTransport
 453            // on macOS returns errSSL{Server,Client}AuthCompleted before any handshake
 454            // response bytes are produced), the next ProcessHandshake call must be allowed
 455            // to re-enter the PAL with an empty input to drive the handshake forward
 456            // (produce ClientKeyExchange/Finished on accept, or a fatal alert on reject).
 457            // On OpenSSL and SChannel the suspension only fires after _isHandshakeComplete
 458            // is already true, so this resume flag is a no-op for those PALs.
 0459            if (!_isHandshakeComplete)
 0460            {
 0461                _resumeAfterCertValidation = true;
 0462            }
 463
 464#if !TARGET_WINDOWS && !SYSNETSECURITY_NO_OPENSSL
 465            // OpenSSL 3.0+ retry-verify path: the handshake paused inside the CertVerifyCallback.
 466            // Push the verdict to the SafeSslHandle so the next SSL_do_handshake call (driven by
 467            // the caller's next ProcessHandshake) re-invokes the callback and either accepts the
 468            // peer cert (Finished is emitted) or rejects it (a fatal alert is emitted).
 469            PushExternalValidationVerdictToPalIfRetryVerify(errors);
 470#endif
 471
 0472            if (errors == SslPolicyErrors.None)
 0473            {
 474                // Caller accepted. Promote the pending cert to the canonical remote-cert slot
 475                // (unless AcceptWithDefaultValidation already did so).
 0476                if (_remoteCertificate is null)
 0477                {
 0478                    _remoteCertificate = _externalPendingCert;
 0479                    _externalPendingCert = null;
 0480                }
 481                else
 0482                {
 483                    // VerifyRemoteCertificateCore adopted the cert into _remoteCertificate. Drop our copy.
 0484                    _externalPendingCert = null;
 0485                }
 0486            }
 487            else
 0488            {
 489                // The caller refused the peer certificate. Record the rejection so
 490                // GetRemoteCertificate does not later re-surface it from the PAL security context.
 0491                _remoteCertificateRejected = true;
 492
 493                // Post-hoc rejection (handshake already wire-complete on OpenSSL 1.1.x or Schannel):
 494                // surface the fault immediately so subsequent Encrypt/Decrypt throw. For the
 495                // retry-verify path the handshake is still incomplete and the fault is set when
 496                // ProcessHandshake drives SSL_do_handshake to failure (so any pending alert bytes
 497                // are drained to the caller first).
 0498                if (_isHandshakeComplete)
 0499                {
 0500                    _externalValidationFault = new AuthenticationException(SR.Format(SR.net_ssl_io_cert_validation, erro
 0501                }
 0502                else if (_resumeAfterCertValidation)
 0503                {
 504                    // Mid-handshake rejection. On OpenSSL 1.1.x / SecureTransport (macOS)
 505                    // the peer-verify callback took the accept-and-defer path, so the
 506                    // handshake will still complete on the wire; the caller's Write / Read
 507                    // must throw AuthenticationException afterwards. Set the fault now, but
 508                    // do NOT throw it from HandshakeBufferedCore -- let the PAL drive the
 509                    // handshake to completion silently so the peer doesn't hang waiting
 510                    // for our Finished. Write / Read guard on ThrowIfPendingExternalValidation
 511                    // which checks _externalValidationFault. On OpenSSL 3.0+ retry-verify the
 512                    // fault will instead be set by the natural token-failed branch when
 513                    // SSL_do_handshake emits the fatal alert.
 0514                    _externalValidationFault = new AuthenticationException(SR.Format(SR.net_ssl_io_cert_validation, erro
 0515                }
 516
 517                // VerifyRemoteCertificateCore assigns _remoteCertificate to the candidate before it
 518                // knows whether the chain validates, so on the reject path the rejected leaf is sitting
 519                // in the canonical slot. Drop it so GetRemoteCertificate cannot surface a cert the caller
 520                // explicitly refused. Either _remoteCertificate or _externalPendingCert owns it, not both.
 0521                if (_remoteCertificate is not null && ReferenceEquals(_remoteCertificate, _externalPendingCert))
 0522                {
 0523                    _remoteCertificate = null;
 0524                }
 525                else
 0526                {
 0527                    _remoteCertificate?.Dispose();
 0528                    _remoteCertificate = null;
 0529                }
 0530                _externalPendingCert?.Dispose();
 0531                _externalPendingCert = null;
 0532            }
 533
 0534            DisposeExternalRemoteCertificates();
 0535        }
 536
 537#if !TARGET_WINDOWS && !SYSNETSECURITY_NO_OPENSSL
 538        // Client-side only path. When CertVerifyCallback paused the handshake via
 539        // SSL_set_retry_verify, RetryVerifyAttempted is set on the SafeSslHandle. Stamp
 540        // the caller's verdict onto the handle so the next SSL_do_handshake (driven by
 541        // the caller's next ProcessHandshake) re-enters the callback and either accepts
 542        // the peer cert or emits a fatal alert. No-op on server sessions and on 1.1.x
 543        // where CertVerifyCallback took the accept-and-defer branch instead of retrying.
 544        private void PushExternalValidationVerdictToPalIfRetryVerify(SslPolicyErrors errors)
 545        {
 546            if (_securityContext is not Microsoft.Win32.SafeHandles.SafeSslHandle sslHandle ||
 547                !sslHandle.RetryVerifyAttempted)
 548            {
 549                return;
 550            }
 551
 552            sslHandle.ExternalValidationAccepted = errors == SslPolicyErrors.None;
 553        }
 554#endif
 555
 556        /// <summary>
 557        /// Server-side only. The parsed ClientHello information, populated once the
 558        /// ClientHello has been received and stays populated for the lifetime of the
 559        /// session. Returns <see langword="null"/> before the ClientHello arrives,
 560        /// on client-side sessions, and on server sessions where ClientHello capture
 561        /// was disabled via the <c>System.Net.Security.CaptureClientHello</c> AppContext
 562        /// switch AND options were supplied at <see cref="TlsContext"/> creation time.
 563        /// </summary>
 564        public SslClientHelloInfo? ClientHelloInfo
 565        {
 566            get
 0567            {
 0568                ThrowIfDisposed();
 0569                return _clientHelloInfo;
 0570            }
 571        }
 572
 573        /// <summary>
 574        /// Server-side only. Returns the number of bytes in the captured raw ClientHello
 575        /// record (5-byte TLS record header plus the ClientHello handshake message), or
 576        /// 0 if unavailable. Callers use this to size a destination buffer for
 577        /// <see cref="TryGetClientHelloBytes"/>.
 578        /// </summary>
 579        /// <remarks>
 580        /// The ClientHello is only captured on server-side sessions and requires the
 581        /// <c>System.Net.Security.CaptureClientHello</c> AppContext switch to be enabled
 582        /// (default true). Returns 0 on client-side sessions, before the ClientHello has
 583        /// been received, or when capture has been disabled.
 584        /// </remarks>
 585        public int GetClientHelloLength()
 0586        {
 0587            ThrowIfDisposed();
 588
 0589            ReadOnlySpan<byte> native = default;
 590            TryGetNativeClientHelloBytes(ref native);
 0591            if (!native.IsEmpty)
 0592            {
 0593                return native.Length;
 594            }
 595
 0596            return _clientHelloBytesBuffered?.Length ?? 0;
 0597        }
 598
 599        /// <summary>
 600        /// Server-side only. Copies the captured raw ClientHello record into
 601        /// <paramref name="destination"/>. Returns <see langword="true"/> when the full
 602        /// record was written; <see langword="false"/> if the destination is too small
 603        /// or the ClientHello is not available.
 604        /// </summary>
 605        /// <param name="destination">Buffer that receives the ClientHello bytes.</param>
 606        /// <param name="bytesWritten">Number of bytes copied. Zero when the method returns false.</param>
 607        public bool TryGetClientHelloBytes(Span<byte> destination, out int bytesWritten)
 0608        {
 0609            ThrowIfDisposed();
 610
 0611            ReadOnlySpan<byte> source = default;
 612            TryGetNativeClientHelloBytes(ref source);
 0613            if (source.IsEmpty)
 0614            {
 0615                if (_clientHelloBytesBuffered is null)
 0616                {
 0617                    bytesWritten = 0;
 0618                    return false;
 619                }
 0620                source = _clientHelloBytesBuffered;
 0621            }
 622
 0623            if (destination.Length < source.Length)
 0624            {
 0625                bytesWritten = 0;
 0626                return false;
 627            }
 628
 0629            source.CopyTo(destination);
 0630            bytesWritten = source.Length;
 0631            return true;
 0632        }
 633
 634        /// <summary>
 635        /// Assigns a <see cref="TlsContext"/> to this session. Must be called at least
 636        /// once before <see cref="TlsBufferSession.Handshake"/> or its socket-bound
 637        /// equivalent can make forward progress. May also be called on a server-side
 638        /// session that suspended with <see cref="TlsOperationStatus.NeedsTlsContext"/>
 639        /// to steer it onto the resolved per-tenant context.
 640        /// </summary>
 641        /// <param name="context">A fully-configured <see cref="TlsContext"/>.</param>
 642        /// <exception cref="ArgumentNullException">Thrown when <paramref name="context"/> is null.</exception>
 643        /// <exception cref="ArgumentException">
 644        /// Thrown when supplying a resolved context after
 645        /// <see cref="TlsOperationStatus.NeedsTlsContext"/> and the passed context is
 646        /// not server-side.
 647        /// </exception>
 648        /// <exception cref="InvalidOperationException">
 649        /// Thrown when the session already has a context and is not currently awaiting
 650        /// server options (i.e., the caller tried to swap a context that was already
 651        /// fully configured).
 652        /// </exception>
 653        public void SetContext(TlsContext context)
 0654        {
 0655            ArgumentNullException.ThrowIfNull(context);
 0656            ThrowIfDisposed();
 657
 0658            if (_context is null)
 0659            {
 0660                InitializeFromContext(context);
 0661                return;
 662            }
 663
 0664            if (!_context!.IsServer)
 0665            {
 0666                throw new InvalidOperationException(SR.net_tlssession_setcontext_server_only);
 667            }
 0668            if (!context.IsServer)
 0669            {
 0670                throw new ArgumentException(SR.net_tlssession_context_must_be_server, nameof(context));
 671            }
 0672            if (_hasServerOptions)
 0673            {
 0674                throw new InvalidOperationException(SR.net_tlssession_server_options_already_supplied);
 675            }
 0676            if (_clientHelloInfo is null)
 0677            {
 0678                throw new InvalidOperationException(SR.net_tlssession_setcontext_needs_context_first);
 679            }
 680
 681            // Ask the supplied context for a session-options bag â€” this allocates its
 682            // long-lived SSL_CTX (if not already) and stamps PreallocatedSslContext on
 683            // the returned bag. Copy those fields (including PreallocatedSslContext) into
 684            // our session's options so subsequent AllocateSslHandle picks up the passed
 685            // context's SSL_CTX instead of falling back to the per-session cache path.
 0686            SslAuthenticationOptions serverOpts = context.CreateSessionOptions();
 0687            _options.CopyFrom(serverOpts);
 688#if !TARGET_WINDOWS && !SYSNETSECURITY_NO_OPENSSL
 689            _options.PreallocatedSslContext = serverOpts.PreallocatedSslContext;
 690#endif
 691
 692            // CopyFrom sets _options.OwnsCertificateContext = false and copies the
 693            // template's CertificateContext reference into the bag. Re-seat our session
 694            // ownership from the freshly-copied serverOpts (the new template may have
 695            // brought its own owned context via ServerCertificate), releasing any prior
 696            // session-owned context. serverOpts itself is a per-session clone that Owns
 697            // = false, so its live-owner is the source TlsContext template that stays
 698            // alive across sessions â€” hence takeOwnership: false here.
 0699            SetSessionCertificateContext(_options.CertificateContext, takeOwnership: false);
 700
 0701            _hasServerOptions = true;
 702
 703            // The per-tenant options differ from the bootstrap context's template, so
 704            // credentials must be session-local. Otherwise EnsureCredentialsAcquired
 705            // would stamp this session's SChannel cred handle into the shared bootstrap
 706            // TlsContext.CredentialsHandle, and every subsequent session on the same
 707            // bootstrap would inherit those credentials regardless of which tenant it
 708            // resolved to (SChannel-only; OpenSSL routes per-tenant SSL_CTX via
 709            // PreallocatedSslContext on the session-local options bag). Acquire eagerly
 710            // so any AcquireCredentialsHandle failure surfaces from SetContext,
 711            // not from an opaque PAL call downstream.
 0712            _sessionCredentialsHandle?.Dispose();
 0713            _sessionCredentialsHandle = SslStreamPal.AcquireCredentialsHandle(_options, false);
 714
 715            OnServerContextSet();
 0716        }
 717
 718        /// <summary>
 719        /// Client-side only. Supplies the certificate context the session should send
 720        /// in response to the server's CertificateRequest, or <see langword="null"/> to
 721        /// decline. Intended to resolve a session suspended on
 722        /// <see cref="TlsOperationStatus.CertificateRequested"/>: callers that need to
 723        /// fetch a certificate from an out-of-process source (e.g. a key vault) do so
 724        /// outside the session, then resume the handshake. May also be called before
 725        /// the first handshake call to seed the client credential when the
 726        /// <see cref="TlsContext"/> was created without one.
 727        /// </summary>
 728        /// <exception cref="InvalidOperationException">
 729        /// Thrown on a server-side session, or before <see cref="SetContext"/> has been
 730        /// called.
 731        /// </exception>
 732        public void SetClientCertificateContext(SslStreamCertificateContext? context)
 0733        {
 0734            ThrowIfDisposed();
 0735            ThrowIfContextNotSet();
 736
 0737            if (_context!.IsServer)
 0738            {
 0739                throw new InvalidOperationException(SR.net_tlssession_setclientcert_client_only);
 740            }
 0741            SetSessionCertificateContext(context, takeOwnership: false);
 742
 743            // Acquire a session-local credentials handle so we don't touch the shared
 744            // TlsContext.CredentialsHandle, which is used by any concurrent session on
 745            // the same context (racing/disposing it can cause handshake failures or
 746            // deliver the wrong certificate on SChannel). ActiveCredentialsRef() will
 747            // return this session-local handle for subsequent PAL calls. Acquire eagerly
 748            // so any AcquireCredentialsHandle failure surfaces here, not from an opaque
 749            // PAL call downstream.
 0750            _sessionCredentialsHandle?.Dispose();
 0751            _sessionCredentialsHandle = SslStreamPal.AcquireCredentialsHandle(_options, false);
 0752            _resumeAfterCredentials = true;
 0753        }
 754
 755        /// <summary>
 756        /// Client-side only. Returns the distinguished names of the certificate authorities
 757        /// the server listed in its TLS 1.2 <c>CertificateRequest</c> or TLS 1.3
 758        /// <c>certificate_authorities</c> extension. Intended to be called while the session
 759        /// is suspended on <see cref="TlsOperationStatus.CertificateRequested"/> so the caller can
 760        /// pick a client certificate that chains to one of the listed CAs. Returns
 761        /// <see langword="null"/> when no security context exists yet, when the peer sent no
 762        /// hints, or on a server-side session.
 763        /// </summary>
 764        public IReadOnlyList<string>? GetAcceptableIssuers()
 0765        {
 0766            ThrowIfDisposed();
 767
 0768            if (_context is null || _context.IsServer || _securityContext is null)
 0769            {
 0770                return null;
 771            }
 772
 0773            string[] issuers = CertificateValidationPal.GetRequestCertificateAuthorities(_securityContext);
 0774            return issuers.Length == 0 ? null : issuers;
 0775        }
 776
 777        private void ThrowIfPendingExternalValidation()
 0778        {
 0779            if (_externalValidationFault is not null)
 0780            {
 0781                throw _externalValidationFault;
 782            }
 0783            if (_externalValidationPending)
 0784            {
 0785                throw new InvalidOperationException(
 0786                    SR.net_tlssession_validation_result_not_recorded);
 787            }
 0788        }
 789
 790        private void DisposeExternalRemoteCertificates()
 0791        {
 0792            X509Certificate2Collection? certs = _externalRemoteCertificates;
 0793            _externalRemoteCertificates = null;
 0794            if (certs is null)
 0795            {
 0796                return;
 797            }
 0798            foreach (X509Certificate2 c in certs)
 0799            {
 0800                c.Dispose();
 0801            }
 0802        }
 803
 804        /// <summary>
 805        /// Returns the local certificate sent to the peer, or <c>null</c> if no
 806        /// local certificate was negotiated. For a server session this is the
 807        /// server certificate; for a client session this is the client
 808        /// certificate selected during handshake (which may be <c>null</c> if
 809        /// the server did not request a client certificate or the client did
 810        /// not supply one).
 811        /// </summary>
 812        public X509Certificate2? LocalCertificate
 813        {
 814            get
 0815            {
 0816                ThrowIfDisposed();
 0817                if (_context!.IsServer)
 0818                {
 0819                    return SessionCertificateContext?.TargetCertificate;
 820                }
 821
 0822                if (_securityContext == null || _securityContext.IsInvalid)
 0823                {
 0824                    return null;
 825                }
 826
 0827                if (!CertificateValidationPal.IsLocalCertificateUsed(ActiveCredentialsRef(), _securityContext))
 0828                {
 0829                    return null;
 830                }
 831
 0832                return SessionCertificateContext?.TargetCertificate;
 0833            }
 834        }
 835
 836        /// <summary>
 837        /// Returns a <see cref="ChannelBinding"/> for the requested
 838        /// <paramref name="kind"/> derived from the current TLS session, or
 839        /// <c>null</c> if the binding is unavailable (e.g. handshake not yet
 840        /// complete, or unsupported binding kind).
 841        /// </summary>
 842        public ChannelBinding? GetChannelBinding(ChannelBindingKind kind)
 0843        {
 0844            ThrowIfDisposed();
 0845            if (_securityContext == null || _securityContext.IsInvalid)
 0846            {
 0847                return null;
 848            }
 0849            return SslStreamPal.QueryContextChannelBinding(_securityContext, kind);
 0850        }
 851
 852        // â”€â”€ Handshake â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
 853
 854        private protected TlsOperationStatus HandshakeBufferedCore(
 855            ReadOnlySpan<byte> input,
 856            Span<byte> output,
 857            out int bytesConsumed,
 858            out int bytesWritten)
 0859        {
 0860            ThrowIfDisposed();
 0861            ThrowIfContextNotSet();
 0862            bytesConsumed = 0;
 0863            bytesWritten = 0;
 864
 0865            if (_externalValidationFault is not null)
 0866            {
 867                // Mid-handshake external-validation reject: on OpenSSL 1.1.x and SecureTransport
 868                // the peer-verify callback took the accept-and-defer path (no retry-verify), so
 869                // the wire handshake still needs to complete before the fault surfaces on the
 870                // caller's Write / Read. Suppress the throw here while the handshake is still
 871                // in-flight so the PAL can silently drive it to completion; the fault will still
 872                // fire on Write / Read via ThrowIfPendingExternalValidation. On OpenSSL 3.0+
 873                // retry-verify the natural PAL failure produces a fatal alert to the peer.
 0874                if (_isHandshakeComplete || !_externalValidationResolved)
 0875                {
 0876                    throw _externalValidationFault;
 877                }
 0878            }
 879
 0880            if (_externalValidationPending)
 0881            {
 0882                return TlsOperationStatus.NeedsCertificateValidation;
 883            }
 884
 0885            if (_clientHelloInfo is not null && !_hasServerOptions)
 0886            {
 887                // The caller previously saw NeedsServerOptions but hasn't supplied options yet.
 0888                return TlsOperationStatus.NeedsTlsContext;
 889            }
 890
 0891            if (_isHandshakeComplete)
 0892            {
 893                // Once the caller has resolved external validation, subsequent
 894                // ProcessHandshake calls on an already-complete session are a
 895                // no-op signal that the handshake is done (one-call window).
 0896                if (_externalValidationResolved)
 0897                {
 0898                    return TlsOperationStatus.Complete;
 899                }
 900
 0901                throw new InvalidOperationException(SR.net_tlssession_handshake_already_complete);
 902            }
 903
 904            // Drain pending first; do not consume new input while output is owed.
 0905            if (_pendingBuffer.ActiveLength > 0)
 0906            {
 0907                bytesWritten = DrainTo(output);
 0908                return _pendingBuffer.ActiveLength > 0 ? TlsOperationStatus.DestinationTooSmall : TlsOperationStatus.Com
 909            }
 910
 911            // The PAL state machine â€” SChannel in particular â€” must only be handed
 912            // complete TLS records. SChannel's PAL wrapper reports consumed=input.Length
 913            // when it returns SEC_E_INCOMPLETE_MESSAGE, which would silently swallow
 914            // bytes it actually still needs. OpenSSL's BIO accepts partial bytes, but
 915            // pre-checking the frame here costs nothing extra and keeps the state
 916            // machine identical across platforms.
 917            //
 918            // The only call that legitimately runs with empty input is the very first
 919            // client-side ISC, which produces the ClientHello, or a client resume after
 920            // SetClientCertificateContext resolved a prior WantCredentials suspension.
 0921            bool isInitialClientCall = !_context!.IsServer && _securityContext is null;
 0922            bool isCredentialResume = _resumeAfterCredentials;
 0923            _resumeAfterCredentials = false;
 0924            bool isCertValidationResume = _resumeAfterCertValidation;
 0925            _resumeAfterCertValidation = false;
 0926            if (!isInitialClientCall && !isCredentialResume && !isCertValidationResume)
 0927            {
 0928                if (input.Length < TlsFrameHelper.HeaderSize)
 0929                {
 0930                    return TlsOperationStatus.NeedMoreData;
 931                }
 932
 0933                TlsFrameHeader frameHeader = default;
 0934                if (!TlsFrameHelper.TryGetFrameHeader(input, ref frameHeader))
 0935                {
 0936                    throw new IOException(SR.net_io_decrypt);
 937                }
 938
 0939                if (input.Length < frameHeader.Length)
 0940                {
 0941                    return TlsOperationStatus.NeedMoreData;
 942                }
 0943            }
 944
 0945            ProtocolToken token = default;
 0946            token.RentBuffer = true;
 947            try
 0948            {
 0949                if (_context!.IsServer)
 0950                {
 951                    // Parse and capture the ClientHello managed-side so the ClientHelloInfo /
 952                    // TargetHostName / GetClientHelloBytes surface is consistent across paths.
 953                    // We check on every call while _clientHelloBytesBuffered is null because the
 954                    // first ProcessHandshake call may pass only a partial CH record - OpenSSL will
 955                    // allocate _securityContext even on partial input and return WantRead, so we
 956                    // can't rely on _securityContext being null as our re-entry gate.
 0957                    if (_clientHelloBytesBuffered is null)
 0958                    {
 0959                        SslClientHelloInfo? parsed = TryParseClientHello(input, out int frameLength);
 0960                        if (parsed is not null)
 0961                        {
 0962                            _clientHelloInfo = parsed;
 0963                            if (!string.IsNullOrEmpty(parsed.Value.ServerName))
 0964                            {
 0965                                _sessionTargetHost = parsed.Value.ServerName;
 0966                            }
 0967                            if (frameLength > 0 && frameLength <= input.Length)
 0968                            {
 0969                                _clientHelloBytesBuffered = input.Slice(0, frameLength).ToArray();
 0970                            }
 971                            // If frameLength is out of range (shouldn't happen after a successful
 972                            // parse), silently skip capture; the session continues to work,
 973                            // GetClientHelloLength just reports 0.
 0974                        }
 0975                        else if (_securityContext is null)
 0976                        {
 977                            // No CH parse-able yet and no PAL context yet - wait for more bytes.
 0978                            return TlsOperationStatus.NeedMoreData;
 979                        }
 0980                    }
 981
 982                    // On the very first server-side call, inspect the incoming
 983                    // ClientHello to surface SNI (TargetHost) and, if the caller
 984                    // supplied a ServerCertificateSelectionCallback, resolve the
 985                    // server certificate from it before AllocateSslHandle runs.
 0986                    if (_securityContext is null)
 0987                    {
 0988                        if (!_hasServerOptions)
 0989                        {
 990                            // Deferred / SNI-callback flow: caller resolves via SetContext.
 991                            // Leave input unconsumed; the caller re-feeds the same bytes on resume.
 0992                            return TlsOperationStatus.NeedsTlsContext;
 993                        }
 994
 0995                        bool needsCertResolution =
 0996                            SessionCertificateContext is null &&
 0997                            _options.ServerCertSelectionDelegate is not null;
 998
 0999                        if (needsCertResolution && !ResolveServerCertificateFromClientHello(input))
 01000                        {
 1001                            // Need more bytes to parse the ClientHello (and run the
 1002                            // ServerCertificateSelectionCallback).
 01003                            return TlsOperationStatus.NeedMoreData;
 1004                        }
 01005                    }
 1006
 01007                    EnsureCredentialsAcquired();
 1008
 01009                    token = SslStreamPal.AcceptSecurityContext(
 01010                        ref ActiveCredentialsRef(),
 01011                        ref _securityContext,
 01012                        input,
 01013                        out bytesConsumed,
 01014                        _options);
 01015                }
 1016                else
 01017                {
 01018                    EnsureCredentialsAcquired();
 1019
 1020                    // Resuming after CertificateRequested: SecureTransport does not stop again, so the
 1021                    // CredentialsNeeded path below never runs; install the certificate here.
 01022                    if (isCredentialResume && _securityContext is not null && _options.CertificateContext is not null)
 01023                    {
 01024                        SslStreamPal.TryUpdateClintCertificate(ActiveCredentialsRef(), _securityContext, _options);
 01025                    }
 1026
 01027                    string hostName = TargetHostNameHelper.NormalizeHostName(_options.TargetHost);
 01028                    token = SslStreamPal.InitializeSecurityContext(
 01029                        ref ActiveCredentialsRef(),
 01030                        ref _securityContext,
 01031                        hostName,
 01032                        input,
 01033                        out bytesConsumed,
 01034                        _options);
 1035
 1036                    // Certificate configured before the handshake: SecureTransport still stops at
 1037                    // CertificateRequest, so install it here and continue.
 01038                    if (token.Status.ErrorCode == SecurityStatusPalErrorCode.CredentialsNeeded &&
 01039                        _options.CertificateContext is not null &&
 01040                        SslStreamPal.TryUpdateClintCertificate(ActiveCredentialsRef(), _securityContext, _options))
 01041                    {
 01042                        if (token.Size > 0)
 01043                        {
 01044                            Debug.Assert(token.Payload != null);
 01045                            AppendPending(new ReadOnlySpan<byte>(token.Payload, 0, token.Size));
 01046                        }
 01047                        token.ReleasePayload();
 01048                        token = SslStreamPal.InitializeSecurityContext(
 01049                            ref ActiveCredentialsRef(),
 01050                            ref _securityContext,
 01051                            hostName,
 01052                            ReadOnlySpan<byte>.Empty,
 01053                            out _,
 01054                            _options);
 01055                    }
 01056                }
 1057
 1058                // Stage any handshake bytes the PAL produced.
 01059                if (token.Size > 0)
 01060                {
 01061                    Debug.Assert(token.Payload != null);
 01062                    AppendPending(new ReadOnlySpan<byte>(token.Payload, 0, token.Size));
 01063                }
 1064
 1065                // Server-side ALPN selection ceremony (SChannel and SecureTransport).
 1066                // After parsing the ClientHello the PAL pauses and asks the caller to
 1067                // pick the application protocol before resuming. We re-enter ASC with
 1068                // an empty input so the PAL can generate the ServerHello carrying the
 1069                // selected ALPN value.
 01070                if (token.Status.ErrorCode == SecurityStatusPalErrorCode.HandshakeStarted)
 01071                {
 01072                    ReadOnlySpan<byte> rawAlpn = ReadOnlySpan<byte>.Empty;
 01073                    TlsFrameHelper.TlsFrameInfo frameInfo = default;
 01074                    if (TlsFrameHelper.TryGetFrameInfo(input, ref frameInfo,
 01075                            TlsFrameHelper.ProcessingOptions.ApplicationProtocol | TlsFrameHelper.ProcessingOptions.RawA
 01076                        frameInfo.RawApplicationProtocols is byte[] rawAlpnBytes)
 01077                    {
 01078                        rawAlpn = rawAlpnBytes;
 01079                    }
 1080
 01081                    SecurityStatusPal selStatus = SslStreamPal.SelectApplicationProtocol(
 01082                        _context!.CredentialsHandle,
 01083                        _securityContext!,
 01084                        _options,
 01085                        rawAlpn);
 1086
 01087                    if (selStatus.ErrorCode != SecurityStatusPalErrorCode.OK)
 01088                    {
 01089                        throw new AuthenticationException(SR.net_auth_SSPI, selStatus.Exception);
 1090                    }
 1091
 01092                    token.ReleasePayload();
 1093
 01094                    if (_context!.IsServer)
 01095                    {
 01096                        token = SslStreamPal.AcceptSecurityContext(
 01097                            ref ActiveCredentialsRef(),
 01098                            ref _securityContext,
 01099                            ReadOnlySpan<byte>.Empty,
 01100                            out _,
 01101                            _options);
 01102                    }
 1103                    else
 01104                    {
 01105                        string hostName = TargetHostNameHelper.NormalizeHostName(_options.TargetHost);
 01106                        token = SslStreamPal.InitializeSecurityContext(
 01107                            ref ActiveCredentialsRef(),
 01108                            ref _securityContext,
 01109                            hostName,
 01110                            ReadOnlySpan<byte>.Empty,
 01111                            out _,
 01112                            _options);
 01113                    }
 1114
 01115                    if (token.Size > 0)
 01116                    {
 01117                        Debug.Assert(token.Payload != null);
 01118                        AppendPending(new ReadOnlySpan<byte>(token.Payload, 0, token.Size));
 01119                    }
 01120                }
 1121
 01122                if (token.Failed &&
 01123                    token.Status.ErrorCode != SecurityStatusPalErrorCode.CredentialsNeeded &&
 01124                    token.Status.ErrorCode != SecurityStatusPalErrorCode.CertValidationNeeded)
 01125                {
 01126                    Exception authExc = new AuthenticationException(SR.net_auth_SSPI, token.GetException());
 1127
 1128                    // OpenSSL queued a TLS alert in the BIO during the failing SSL_do_handshake
 1129                    // (e.g. bad_certificate after the client-side retry-verify callback rejected
 1130                    // the peer). Drain the alert to the caller's output buffer before throwing so
 1131                    // the peer observes an AuthenticationException instead of a connection reset.
 1132                    // The fault is re-raised on the next ProcessHandshake call once the queue is
 1133                    // empty. Only fires on the client path today; server-side never reaches this
 1134                    // branch for external-validation reasons because CertVerifyCallback
 1135                    // accepts-and-defers (see gating in Interop.OpenSsl.CertVerifyCallback).
 01136                    if (_pendingBuffer.ActiveLength > 0)
 01137                    {
 01138                        bytesWritten = DrainTo(output);
 01139                        _externalValidationFault = authExc;
 01140                        return TlsOperationStatus.DestinationTooSmall;
 1141                    }
 1142
 01143                    throw authExc;
 1144                }
 1145
 01146                bool done = token.Status.ErrorCode == SecurityStatusPalErrorCode.OK;
 01147                bool needsCredentials = token.Status.ErrorCode == SecurityStatusPalErrorCode.CredentialsNeeded;
 01148                bool needsCertValidation = token.Status.ErrorCode == SecurityStatusPalErrorCode.CertValidationNeeded;
 1149
 01150                if (done)
 01151                {
 01152                    OnHandshakeCompleted();
 01153                }
 01154                else if (needsCertValidation)
 01155                {
 1156                    // PAL paused mid-handshake awaiting external certificate validation.
 1157                    // Capture the peer cert + chain so the caller can validate, then return
 1158                    // NeedsCertificateValidation. Not used by the current OpenSSL or SChannel
 1159                    // paths but kept as a generic suspension hook.
 01160                    CaptureRemoteCertificateForExternalValidation();
 01161                }
 1162
 01163                if (_pendingBuffer.ActiveLength > 0)
 01164                {
 01165                    bytesWritten = DrainTo(output);
 01166                    if (_pendingBuffer.ActiveLength > 0)
 01167                    {
 01168                        return TlsOperationStatus.DestinationTooSmall;
 1169                    }
 01170                }
 1171
 01172                if (done)
 01173                {
 01174                    return _externalValidationPending
 01175                        ? TlsOperationStatus.NeedsCertificateValidation
 01176                        : TlsOperationStatus.Complete;
 1177                }
 1178
 01179                if (needsCertValidation)
 01180                {
 01181                    return TlsOperationStatus.NeedsCertificateValidation;
 1182                }
 1183
 01184                if (needsCredentials)
 01185                {
 01186                    return TlsOperationStatus.CertificateRequested;
 1187                }
 1188
 1189                // SChannel consumes one TLS record per AcceptSecurityContext/
 1190                // InitializeSecurityContext call (OpenSSL typically consumes the
 1191                // whole input via the BIO). When the PAL accepted bytes but the
 1192                // caller still has more buffered, return Complete so the driver
 1193                // re-enters us with the remainder instead of blocking on a network
 1194                // read the peer will never satisfy (e.g. server seeing CKE+CCS+
 1195                // Finished in one TCP read during a TLS 1.2 handshake).
 01196                if (bytesConsumed > 0 && bytesConsumed < input.Length)
 01197                {
 01198                    return TlsOperationStatus.Complete;
 1199                }
 1200
 01201                return TlsOperationStatus.NeedMoreData;
 1202            }
 1203            finally
 01204            {
 01205                token.ReleasePayload();
 01206            }
 01207        }
 1208
 1209        private protected TlsOperationStatus WriteBufferedCore(
 1210            ReadOnlySpan<byte> plaintext,
 1211            Span<byte> ciphertext,
 1212            out int bytesConsumed,
 1213            out int bytesWritten)
 01214        {
 01215            ThrowIfDisposed();
 01216            ThrowIfPendingExternalValidation();
 01217            bytesConsumed = 0;
 01218            bytesWritten = 0;
 1219
 01220            if (!_isHandshakeComplete)
 01221            {
 01222                throw new InvalidOperationException(SR.net_tlssession_handshake_not_complete);
 1223            }
 1224
 01225            if (_pendingBuffer.ActiveLength > 0)
 01226            {
 01227                bytesWritten = DrainTo(ciphertext);
 01228                return _pendingBuffer.ActiveLength > 0 ? TlsOperationStatus.DestinationTooSmall : TlsOperationStatus.Com
 1229            }
 1230
 01231            if (plaintext.IsEmpty)
 01232            {
 01233                return TlsOperationStatus.Complete;
 1234            }
 1235
 01236            int chunk = Math.Min(plaintext.Length, _maxDataSize);
 01237            byte[] rented = ArrayPool<byte>.Shared.Rent(chunk);
 1238            try
 01239            {
 01240                plaintext.Slice(0, chunk).CopyTo(rented);
 1241
 01242                ProtocolToken token = SslStreamPal.EncryptMessage(
 01243                    _securityContext!,
 01244                    new ReadOnlyMemory<byte>(rented, 0, chunk),
 01245                    _headerSize,
 01246                    _trailerSize);
 1247
 1248                try
 01249                {
 01250                    if (token.Status.ErrorCode != SecurityStatusPalErrorCode.OK)
 01251                    {
 01252                        throw new IOException(SR.net_io_encrypt, SslStreamPal.GetException(token.Status));
 1253                    }
 1254
 01255                    bytesConsumed = chunk;
 1256
 01257                    if (token.Size > 0)
 01258                    {
 01259                        Debug.Assert(token.Payload != null);
 01260                        AppendPending(new ReadOnlySpan<byte>(token.Payload, 0, token.Size));
 01261                    }
 01262                }
 1263                finally
 01264                {
 01265                    token.ReleasePayload();
 01266                }
 01267            }
 1268            finally
 01269            {
 01270                ArrayPool<byte>.Shared.Return(rented);
 01271            }
 1272
 01273            bytesWritten = DrainTo(ciphertext);
 01274            return _pendingBuffer.ActiveLength > 0 ? TlsOperationStatus.DestinationTooSmall : TlsOperationStatus.Complet
 01275        }
 1276
 1277        // â”€â”€ Decrypt â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
 1278
 1279        private protected TlsOperationStatus ReadBufferedCore(
 1280            ReadOnlySpan<byte> ciphertext,
 1281            Span<byte> plaintext,
 1282            out int bytesConsumed,
 1283            out int bytesWritten)
 01284        {
 01285            ThrowIfDisposed();
 01286            ThrowIfPendingExternalValidation();
 01287            bytesConsumed = 0;
 01288            bytesWritten = 0;
 1289
 01290            if (!_isHandshakeComplete)
 01291            {
 01292                throw new InvalidOperationException(SR.net_tlssession_handshake_not_complete);
 1293            }
 1294
 01295            if (_pendingBuffer.ActiveLength > 0)
 01296            {
 1297                // Caller must drain before we accept new input.
 01298                return TlsOperationStatus.DestinationTooSmall;
 1299            }
 1300
 1301            // Need at least a frame header. If the caller didn't provide a full frame, the PAL
 1302            // may still have plaintext buffered internally â€” ciphertext absorbed by OpenSSL's
 1303            // BIO during ProcessHandshake (e.g. the peer coalesced its Finished with the first
 1304            // app-data record into one TCP segment) or a record consumed but not yet decrypted
 1305            // by a prior Decrypt call. On platforms whose PAL maintains such a buffer, probe it
 1306            // with an empty input before asking the caller for more wire bytes; otherwise the
 1307            // session deadlocks waiting on data the peer already sent.
 01308            if (ciphertext.Length < TlsFrameHelper.HeaderSize)
 01309            {
 01310                return TryDrainBufferedPlaintext(plaintext, out bytesWritten);
 1311            }
 1312
 01313            TlsFrameHeader header = default;
 01314            if (!TlsFrameHelper.TryGetFrameHeader(ciphertext, ref header))
 01315            {
 01316                throw new IOException(SR.net_io_decrypt);
 1317            }
 1318
 01319            int frameSize = header.Length;
 01320            if (ciphertext.Length < frameSize)
 01321            {
 01322                return TryDrainBufferedPlaintext(plaintext, out bytesWritten);
 1323            }
 1324
 1325            // PAL decrypts in place; copy into a writable scratch buffer.
 01326            EnsureDecryptScratch(frameSize);
 01327            ciphertext.Slice(0, frameSize).CopyTo(_decryptScratch);
 1328
 01329            SecurityStatusPal status = SslStreamPal.DecryptMessage(
 01330                _securityContext!,
 01331                _decryptScratch.AsSpan(0, frameSize),
 01332                plaintext,
 01333                out int decBytesWritten,
 01334                out int decLeftoverOffset,
 01335                out int decLeftoverLength);
 1336
 01337            switch (status.ErrorCode)
 1338            {
 1339                case SecurityStatusPalErrorCode.OK:
 01340                    bytesConsumed = frameSize;
 1341                    // Linux/macOS PALs write the plaintext directly into the destination span and
 1342                    // (if it didn't fit, or the PAL prefers in-place) leave overflow in the encrypted
 1343                    // span at leftoverOffset/leftoverLength. SChannel always decrypts in place and
 1344                    // reports bytesWritten = 0 with leftoverOffset/leftoverLength pointing at the
 1345                    // plaintext inside the encrypted span. Unify by appending the leftover slice
 1346                    // after whatever was written into destination.
 01347                    int needed = decBytesWritten + decLeftoverLength;
 01348                    if (needed > plaintext.Length)
 01349                    {
 01350                        throw new InvalidOperationException(
 01351                            SR.Format(SR.net_tlssession_plaintext_buffer_too_small, needed, plaintext.Length));
 1352                    }
 01353                    if (decLeftoverLength > 0)
 01354                    {
 01355                        _decryptScratch.AsSpan(decLeftoverOffset, decLeftoverLength)
 01356                            .CopyTo(plaintext.Slice(decBytesWritten));
 01357                    }
 01358                    bytesWritten = needed;
 01359                    return TlsOperationStatus.Complete;
 1360
 1361                case SecurityStatusPalErrorCode.ContextExpired:
 1362                case SecurityStatusPalErrorCode.ContextExpiredError:
 01363                    bytesConsumed = frameSize;
 01364                    return TlsOperationStatus.Closed;
 1365
 1366                case SecurityStatusPalErrorCode.Renegotiate:
 1367                    // SChannel surfaces SEC_I_RENEGOTIATE for two distinct cases:
 1368                    //  - TLS 1.2 peer-initiated renegotiation (HelloRequest).
 1369                    //  - TLS 1.3 post-handshake messages (NewSessionTicket,
 1370                    //    KeyUpdate, post-handshake CertificateRequest).
 1371                    // In either case the decrypted payload is the inner handshake
 1372                    // record that must be fed back into ASC/ISC so SChannel can
 1373                    // update its internal state. If we don't, the next DecryptMessage
 1374                    // returns SEC_E_CONTEXT_EXPIRED because the context is stuck.
 01375                    bytesConsumed = frameSize;
 01376                    if (decLeftoverLength > 0)
 01377                    {
 01378                        ProcessPostHandshakeMessage(_decryptScratch.AsSpan(decLeftoverOffset, decLeftoverLength));
 01379                    }
 1380                    // Return Complete (not WantRead): we consumed input bytes but
 1381                    // produced no plaintext. The caller's loop should re-enter to
 1382                    // process any remaining buffered ciphertext (e.g. application
 1383                    // data that arrived in the same TCP segment as the NST).
 01384                    return TlsOperationStatus.Complete;
 1385
 1386                default:
 01387                    throw new IOException(SR.net_io_decrypt, SslStreamPal.GetException(status));
 1388            }
 01389        }
 1390
 1391        // Empty-input probe used when the caller's buffer doesn't yet hold a complete TLS
 1392        // frame. On OpenSSL the PAL's record layer may still have plaintext queued from a
 1393        // prior call (handshake input that included trailing app-data, or a second record
 1394        // coalesced into the same TCP segment); calling DecryptMessage with an empty span
 1395        // surfaces it. On SChannel / SecureTransport the equivalent buffer does not exist,
 1396        // so the probe is skipped and the caller is asked for more bytes instead. The
 1397        // bytesConsumed out-parameter on the public Decrypt method is necessarily 0 here:
 1398        // no caller bytes were taken.
 1399        private TlsOperationStatus TryDrainBufferedPlaintext(Span<byte> plaintext, out int bytesWritten)
 01400        {
 01401            bytesWritten = 0;
 1402
 01403            if (!OperatingSystem.IsLinux() && !OperatingSystem.IsFreeBSD() && !OperatingSystem.IsAndroid())
 01404            {
 01405                return TlsOperationStatus.NeedMoreData;
 1406            }
 1407
 01408            SecurityStatusPal status = SslStreamPal.DecryptMessage(
 01409                _securityContext!,
 01410                Span<byte>.Empty,
 01411                plaintext,
 01412                out int decBytesWritten,
 01413                out int decLeftoverOffset,
 01414                out int decLeftoverLength);
 1415
 01416            if (status.ErrorCode != SecurityStatusPalErrorCode.OK)
 01417            {
 1418                // Anything other than success here means there's nothing to drain â€” the PAL
 1419                // is genuinely waiting on wire bytes. Surface as WantRead; fatal errors will
 1420                // resurface on the next regular Decrypt call with real ciphertext.
 01421                return TlsOperationStatus.NeedMoreData;
 1422            }
 1423
 01424            int produced = decBytesWritten + decLeftoverLength;
 01425            if (produced == 0)
 01426            {
 01427                return TlsOperationStatus.NeedMoreData;
 1428            }
 1429
 01430            if (produced > plaintext.Length)
 01431            {
 01432                throw new InvalidOperationException(
 01433                    SR.Format(SR.net_tlssession_plaintext_buffer_too_small, produced, plaintext.Length));
 1434            }
 1435
 01436            if (decLeftoverLength > 0)
 01437            {
 1438                // PAL stashed overflow in the (empty) input span â€” impossible here, but mirror
 1439                // the main Decrypt path for symmetry. With Span<byte>.Empty as input, the OpenSSL
 1440                // PAL has nowhere to stash leftover and won't take this path.
 01441                _decryptScratch.AsSpan(decLeftoverOffset, decLeftoverLength)
 01442                    .CopyTo(plaintext.Slice(decBytesWritten));
 01443            }
 1444
 01445            bytesWritten = produced;
 01446            return TlsOperationStatus.Complete;
 01447        }
 1448
 1449        // â”€â”€ Post-handshake auth â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
 1450
 1451        /// <summary>
 1452        /// Server-side: requests a client certificate from the peer after the
 1453        /// initial handshake has completed. On TLS 1.3 this issues a
 1454        /// post-handshake authentication CertificateRequest; on TLS 1.2 it
 1455        /// initiates a renegotiation.
 1456        /// </summary>
 1457        /// <remarks>
 1458        /// <para>
 1459        /// The session is created with client certificates optional
 1460        /// (<c>ClientCertificateRequired == false</c>); this method promotes the
 1461        /// requirement for the post-handshake exchange, mirroring
 1462        /// <see cref="SslStream.NegotiateClientCertificateAsync(System.Threading.CancellationToken)"/>.
 1463        /// </para>
 1464        /// <para>
 1465        /// The generated handshake bytes are staged into the pending-output
 1466        /// buffer (drained into <paramref name="ciphertext"/>). The caller must
 1467        /// send them to the peer and then drive the second handshake to
 1468        /// completion via <see cref="TlsBufferSession.Handshake"/>,
 1469        /// exactly like the initial handshake: it re-surfaces
 1470        /// <see cref="TlsOperationStatus.NeedsCertificateValidation"/> (re-running the
 1471        /// remote-certificate validation callback) and finally returns
 1472        /// <see cref="TlsOperationStatus.Complete"/>. Once validation is accepted the
 1473        /// peer certificate becomes observable via <see cref="GetRemoteCertificate"/>.
 1474        /// </para>
 1475        /// </remarks>
 1476        private protected TlsOperationStatus RequestClientCertificateBufferedCore(Span<byte> ciphertext, out int bytesWr
 01477        {
 01478            ThrowIfDisposed();
 01479            ThrowIfContextNotSet();
 1480            // Like Read/Write, post-handshake client authentication is a top-level session
 1481            // operation: if the previous handshake surfaced NeedsCertificateValidation the
 1482            // caller must record the result (or observe the recorded fault) before starting it.
 01483            ThrowIfPendingExternalValidation();
 01484            bytesWritten = 0;
 1485
 1486            // Post-handshake client authentication is a server-only operation. Enforce the role
 1487            // guard before the Apple platform check so client-session misuse consistently surfaces
 1488            // InvalidOperationException on every platform rather than PlatformNotSupportedException.
 01489            if (!_context!.IsServer)
 01490            {
 01491                throw new InvalidOperationException(SR.net_tlssession_request_client_cert_server_only);
 1492            }
 1493
 1494#if TARGET_APPLE
 1495            // SecureTransport does not expose a post-handshake client-authentication
 1496            // path, and Network.framework does not provide renegotiation primitives.
 1497            throw new PlatformNotSupportedException(SR.net_ssl_renegotiate_not_supported);
 1498#else
 01499            if (!_postHandshakeAuthActive)
 01500            {
 01501                if (!_isHandshakeComplete || _securityContext == null || _securityContext.IsInvalid)
 01502                {
 01503                    throw new InvalidOperationException(SR.net_tlssession_handshake_not_complete);
 1504                }
 1505
 1506                // Match SslStream.RenegotiateAsync: promote the client-certificate
 1507                // requirement for the post-handshake exchange even when the initial
 1508                // handshake was negotiated with RemoteCertRequired == false. This flips
 1509                // the MutualAuth context flag so the TLS 1.2 renegotiation / TLS 1.3
 1510                // post-handshake CertificateRequest actually asks for the client cert.
 01511                _options.RemoteCertRequired = true;
 1512
 01513                ProtocolToken token = SslStreamPal.Renegotiate(
 01514                    ref ActiveCredentialsRef(),
 01515                    ref _securityContext!,
 01516                    _options);
 01517                bool staged = false;
 1518                try
 01519                {
 1520                    // NoRenegotiation means no request can be made (e.g. a TLS 1.3 client that didn't offer
 1521                    // post-handshake authentication, or renegotiation disabled in the OpenSSL configuration),
 1522                    // not a failure: nothing is staged, so the session stays in its completed state below.
 01523                    bool noRenegotiation = token.Status.ErrorCode == SecurityStatusPalErrorCode.NoRenegotiation;
 01524                    if (token.Failed && !noRenegotiation)
 01525                    {
 01526                        throw new AuthenticationException(SR.net_auth_SSPI, token.GetException());
 1527                    }
 1528
 01529                    if (token.Size > 0 && !noRenegotiation)
 01530                    {
 01531                        Debug.Assert(token.Payload != null);
 01532                        AppendPending(new ReadOnlySpan<byte>(token.Payload, 0, token.Size));
 01533                        staged = true;
 01534                    }
 01535                }
 1536                finally
 01537                {
 01538                    token.ReleasePayload();
 01539                }
 1540
 01541                if (!staged)
 01542                {
 1543                    // The PAL produced no renegotiation request (e.g. the peer
 1544                    // declined with NoRenegotiation). Leave the session in its
 1545                    // completed state; there is nothing for the caller to drive.
 01546                    return TlsOperationStatus.Complete;
 1547                }
 1548
 1549                // Re-arm the handshake state machine so the caller drives the second
 1550                // handshake to completion via Handshake(), exactly like the initial
 1551                // handshake. HandshakeBufferedCore short-circuits to Complete while an
 1552                // already-complete session has resolved external validation, so both
 1553                // flags must be reset here for it to re-enter the PAL and re-surface
 1554                // NeedsCertificateValidation.
 01555                _isHandshakeComplete = false;
 01556                _externalValidationResolved = false;
 01557                _postHandshakeAuthActive = true;
 01558            }
 1559
 01560            bytesWritten = DrainTo(ciphertext);
 01561            if (_pendingBuffer.ActiveLength > 0)
 01562            {
 01563                return TlsOperationStatus.DestinationTooSmall;
 1564            }
 1565
 1566            // All staged request bytes handed off; from here the caller drives the
 1567            // second handshake through Handshake().
 01568            _postHandshakeAuthActive = false;
 01569            return TlsOperationStatus.Complete;
 1570#endif
 01571        }
 1572
 1573        // â”€â”€ Shutdown â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
 1574
 1575        private bool _shutdownSent;
 1576
 1577        /// <summary>
 1578        /// Initiates a TLS close_notify shutdown and stages the resulting alert
 1579        /// record into the pending-output buffer (drained into <paramref name="ciphertext"/>).
 1580        /// Subsequent calls drain any remaining shutdown output.
 1581        /// </summary>
 1582        /// <remarks>
 1583        /// Returns <see cref="TlsOperationStatus.DestinationTooSmall"/> if the caller must
 1584        /// drain more output before the shutdown record is fully written;
 1585        /// otherwise <see cref="TlsOperationStatus.Closed"/> once all bytes have
 1586        /// been handed to the caller.
 1587        /// </remarks>
 1588        private protected TlsOperationStatus ShutdownBufferedCore(Span<byte> ciphertext, out int bytesWritten)
 01589        {
 01590            ThrowIfDisposed();
 01591            bytesWritten = 0;
 1592
 01593            if (_securityContext == null || _securityContext.IsInvalid)
 01594            {
 01595                return TlsOperationStatus.Closed;
 1596            }
 1597
 01598            if (!_shutdownSent)
 01599            {
 01600                _shutdownSent = true;
 1601
 01602                SecurityStatusPal status = SslStreamPal.ApplyShutdownToken(_securityContext);
 01603                if (status.ErrorCode != SecurityStatusPalErrorCode.OK)
 01604                {
 01605                    throw new IOException(SR.net_io_encrypt, SslStreamPal.GetException(status));
 1606                }
 1607
 1608                // Drive one step to extract the close_notify bytes the PAL queued
 1609                // into the underlying BIO. Input is empty; we only care about
 1610                // any output the PAL produces.
 01611                ProtocolToken token = default;
 01612                token.RentBuffer = true;
 1613                try
 01614                {
 01615                    if (_context!.IsServer)
 01616                    {
 01617                        token = SslStreamPal.AcceptSecurityContext(
 01618                            ref ActiveCredentialsRef(),
 01619                            ref _securityContext,
 01620                            ReadOnlySpan<byte>.Empty,
 01621                            out _,
 01622                            _options);
 01623                    }
 1624                    else
 01625                    {
 01626                        string hostName = TargetHostNameHelper.NormalizeHostName(_options.TargetHost);
 01627                        token = SslStreamPal.InitializeSecurityContext(
 01628                            ref ActiveCredentialsRef(),
 01629                            ref _securityContext,
 01630                            hostName,
 01631                            ReadOnlySpan<byte>.Empty,
 01632                            out _,
 01633                            _options);
 01634                    }
 1635
 01636                    if (token.Size > 0)
 01637                    {
 01638                        Debug.Assert(token.Payload != null);
 01639                        AppendPending(new ReadOnlySpan<byte>(token.Payload, 0, token.Size));
 01640                    }
 01641                }
 1642                finally
 01643                {
 01644                    token.ReleasePayload();
 01645                }
 01646            }
 1647
 01648            bytesWritten = DrainTo(ciphertext);
 01649            return _pendingBuffer.ActiveLength > 0 ? TlsOperationStatus.DestinationTooSmall : TlsOperationStatus.Closed;
 01650        }
 1651
 1652        // â”€â”€ Pending output â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
 1653
 1654        private protected TlsOperationStatus DrainPendingOutputCore(Span<byte> ciphertext, out int bytesWritten)
 01655        {
 01656            ThrowIfDisposed();
 01657            bytesWritten = DrainTo(ciphertext);
 01658            return _pendingBuffer.ActiveLength > 0 ? TlsOperationStatus.DestinationTooSmall : TlsOperationStatus.Complet
 01659        }
 1660
 1661        // â”€â”€ Internals â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
 1662
 1663        private void AppendPending(ReadOnlySpan<byte> data)
 01664        {
 01665            if (data.IsEmpty)
 01666            {
 01667                return;
 1668            }
 1669
 01670            _pendingBuffer.EnsureAvailableSpace(data.Length);
 01671            data.CopyTo(_pendingBuffer.AvailableSpan);
 01672            _pendingBuffer.Commit(data.Length);
 01673        }
 1674
 1675        private int DrainTo(Span<byte> output)
 01676        {
 01677            int n = Math.Min(output.Length, _pendingBuffer.ActiveLength);
 01678            if (n == 0)
 01679            {
 01680                return 0;
 1681            }
 1682
 01683            _pendingBuffer.ActiveSpan.Slice(0, n).CopyTo(output);
 01684            _pendingBuffer.Discard(n);
 01685            return n;
 01686        }
 1687
 1688        private void EnsureDecryptScratch(int size)
 01689        {
 01690            if (_decryptScratch == null || _decryptScratch.Length < size)
 01691            {
 01692                if (_decryptScratch != null)
 01693                {
 01694                    ArrayPool<byte>.Shared.Return(_decryptScratch);
 01695                }
 01696                _decryptScratch = ArrayPool<byte>.Shared.Rent(size);
 01697            }
 01698        }
 1699
 01700        private void ThrowIfDisposed() => ObjectDisposedException.ThrowIf(_disposed, this);
 1701
 1702        private void ThrowIfContextNotSet()
 01703        {
 01704            if (_context is null)
 01705            {
 01706                throw new InvalidOperationException(SR.net_ssl_tlssession_context_not_set);
 1707            }
 01708        }
 1709
 1710        // Server-side: parses the ClientHello and returns a populated
 1711        // SslClientHelloInfo (SNI + supported versions), or null if more bytes
 1712        // are needed or the record is not a ClientHello. Used by the
 1713        // deferred-options path; does not mutate session state.
 1714        private static SslClientHelloInfo? TryParseClientHello(ReadOnlySpan<byte> input, out int frameLength)
 01715        {
 01716            frameLength = 0;
 01717            TlsFrameHelper.TlsFrameInfo frameInfo = default;
 01718            if (!TlsFrameHelper.TryGetFrameInfo(input, ref frameInfo))
 01719            {
 01720                return null;
 1721            }
 1722
 01723            if (frameInfo.HandshakeType != TlsHandshakeType.ClientHello)
 01724            {
 01725                return null;
 1726            }
 1727
 01728            frameLength = frameInfo.Header.Length;
 01729            return new SslClientHelloInfo(frameInfo.TargetName ?? string.Empty, frameInfo.SupportedVersions);
 01730        }
 1731
 1732        // Server-side SNI + certificate selection. Parses the ClientHello to
 1733        // extract the server_name extension (SNI) and, if a
 1734        // ServerCertificateSelectionCallback was supplied and no static
 1735        // CertificateContext has been resolved yet, invokes the callback to
 1736        // pick the cert. Mirrors the path SslStream takes in
 1737        // ReceiveBlobAsync/AcquireServerCredentials.
 1738        private bool ResolveServerCertificateFromClientHello(ReadOnlySpan<byte> input)
 01739        {
 01740            TlsFrameHelper.TlsFrameInfo frameInfo = default;
 01741            if (!TlsFrameHelper.TryGetFrameInfo(input, ref frameInfo))
 01742            {
 01743                return false;
 1744            }
 1745
 01746            if (frameInfo.HandshakeType != TlsHandshakeType.ClientHello)
 01747            {
 01748                return true;
 1749            }
 1750
 01751            if (!string.IsNullOrEmpty(frameInfo.TargetName))
 01752            {
 01753                _sessionTargetHost = frameInfo.TargetName;
 01754            }
 1755
 01756            ServerCertificateSelectionCallback? selector = _options.ServerCertSelectionDelegate;
 01757            if (selector is null || SessionCertificateContext is not null)
 01758            {
 01759                return true;
 1760            }
 1761
 01762            X509Certificate? selected = selector(this, _sessionTargetHost);
 01763            if (selected is null)
 01764            {
 01765                throw new AuthenticationException(SR.net_ssl_io_no_server_cert);
 1766            }
 1767
 01768            X509Certificate2? withKey = SslStream.FindCertificateWithPrivateKey(this, isServer: true, selected);
 01769            if (withKey is null)
 01770            {
 01771                throw new AuthenticationException(SR.net_ssl_io_no_server_cert);
 1772            }
 1773
 01774            SetSessionCertificateContext(
 01775                SslStreamCertificateContext.Create(withKey, additionalCertificates: null, offline: false, trust: null, n
 01776                takeOwnership: true);
 01777            return true;
 01778        }
 1779
 1780        // â”€â”€ Internal surface for the SslStream wedge (Linux/FreeBSD only) â”€
 1781
 1782        // Direct accessors used by SslStream to mirror state into its own fields after
 1783        // each handshake step. Both handles are owned by this TlsSession; SslStream
 1784        // observes them via the mirror but does not dispose them.
 1785        // Set by the SslStream wedge: SslStream owns the validation flow and will
 1786        // invoke the user callback itself with the SslStream as the sender. Skipping
 1787        // here avoids invoking the callback twice and avoids handing TlsSession to
 1788        // user code that expects SslStream.
 1789        internal bool SuppressInternalCertificateValidation
 1790        {
 1791            get => _suppressInternalCertificateValidation;
 01792            set => _suppressInternalCertificateValidation = value;
 1793        }
 1794
 01795        internal TlsSecurityContext? SecurityContext => _securityContext;
 1796        internal TlsContext Context => _context!;
 1797        internal SafeFreeCredentials? CredentialsHandle
 1798        {
 01799            get => ActiveCredentialsRef();
 1800            set
 01801            {
 01802                if (_sessionCredentialsHandle is not null)
 01803                {
 01804                    _sessionCredentialsHandle = value;
 01805                }
 1806                else
 01807                {
 01808                    _context!.CredentialsHandle = value;
 01809                }
 01810            }
 1811        }
 1812
 1813        // Returns a ref to the credentials handle this session should use for its next
 1814        // PAL call. When _sessionCredentialsHandle is set (via SetClientCertificateContext),
 1815        // it takes precedence; otherwise the shared TlsContext.CredentialsHandle is used.
 1816        // Class instance refs have unrestricted lifetime, no [UnscopedRef] needed.
 1817        private ref SafeFreeCredentials? ActiveCredentialsRef()
 01818            => ref (_sessionCredentialsHandle is not null
 01819                    ? ref _sessionCredentialsHandle
 01820                    : ref _context!.CredentialsHandle);
 1821
 1822        // SslStream's GenerateToken replacement. Drives one ASC/ISC step via PAL and
 1823        // updates internal handshake-complete state. Returns the raw PAL token so the
 1824        // caller can preserve existing ProtocolToken-based plumbing (alerts, error
 1825        // mapping, NetEventSource).
 1826        internal ProtocolToken HandshakeStepForSslStream(ReadOnlySpan<byte> input, out int bytesConsumed)
 01827        {
 01828            ThrowIfDisposed();
 1829
 1830            ProtocolToken token;
 01831            if (_context!.IsServer)
 01832            {
 01833                token = SslStreamPal.AcceptSecurityContext(
 01834                    ref ActiveCredentialsRef(),
 01835                    ref _securityContext,
 01836                    input,
 01837                    out bytesConsumed,
 01838                    _options);
 01839            }
 1840            else
 01841            {
 01842                string hostName = TargetHostNameHelper.NormalizeHostName(_options.TargetHost);
 01843                token = SslStreamPal.InitializeSecurityContext(
 01844                    ref ActiveCredentialsRef(),
 01845                    ref _securityContext,
 01846                    hostName,
 01847                    input,
 01848                    out bytesConsumed,
 01849                    _options);
 01850            }
 1851
 01852            if (token.Status.ErrorCode == SecurityStatusPalErrorCode.OK)
 01853            {
 01854                OnHandshakeCompleted();
 01855            }
 1856
 01857            return token;
 01858        }
 1859
 1860        private void OnHandshakeCompleted()
 01861        {
 01862            _isHandshakeComplete = true;
 01863            SslStreamPal.QueryContextConnectionInfo(_securityContext!, ref _connectionInfo);
 01864            SslStreamPal.QueryContextStreamSizes(_securityContext!, out StreamSizes streamSizes);
 01865            _headerSize = streamSizes.Header;
 01866            _trailerSize = streamSizes.Trailer;
 01867            if (streamSizes.MaximumMessage > 0)
 01868            {
 01869                _maxDataSize = Math.Min(streamSizes.MaximumMessage, MaxRecordPlaintext);
 01870            }
 1871
 1872            // Invoke remote-certificate validation callback (mirrors SslStream).
 1873            // Client: always validate the server cert.
 1874            // Server: always suspend so the caller's RemoteCertificateValidationCallback runs
 1875            // (it must see optional client certs and the no-cert case alike â€” only the
 1876            // RemoteCertificateNotAvailable error is suppressed in VerifyRemoteCertificateCore
 1877            // when there is no user callback and RemoteCertRequired is false).
 01878            if (_suppressInternalCertificateValidation)
 01879            {
 01880                return;
 1881            }
 1882
 1883            // If the caller already resolved validation via a prior suspension
 1884            // (defensive â€” current OpenSSL/SChannel paths only suspend once via
 1885            // the post-handshake hook below), don't re-suspend here.
 01886            if (_externalValidationResolved)
 01887            {
 01888                return;
 1889            }
 1890
 01891            CaptureRemoteCertificateForExternalValidation();
 01892        }
 1893
 1894        // Capture the peer certificate and chain so the caller can perform validation
 1895        // out of band. Keeps the cert in _externalPendingCert (not _remoteCertificate)
 1896        // so VerifyRemoteCertificateCore's renegotiation shortcut doesn't dispose it
 1897        // when AcceptWithDefaultValidation runs.
 1898        private void CaptureRemoteCertificateForExternalValidation()
 01899        {
 01900            X509ChainPolicy? chainPolicy = _options.CertificateChainPolicy?.Clone();
 01901            int preexistingExtraCertsCount = chainPolicy?.ExtraStore.Count ?? 0;
 01902            X509Chain? chain = null;
 01903            X509Certificate2Collection? intermediates = null;
 1904
 1905            try
 01906            {
 01907                _externalPendingCert = CertificateValidationPal.GetRemoteCertificate(
 01908                    _securityContext, ref chain, chainPolicy);
 1909
 01910                if (chain is not null)
 01911                {
 01912                    X509Certificate2Collection extraStore = chain.ChainPolicy.ExtraStore;
 01913                    while (extraStore.Count > preexistingExtraCertsCount)
 01914                    {
 01915                        X509Certificate2 certificate = extraStore[preexistingExtraCertsCount];
 01916                        extraStore.RemoveAt(preexistingExtraCertsCount);
 1917
 01918                        bool transferred = false;
 1919                        try
 01920                        {
 01921                            if (_externalPendingCert is null ||
 01922                                !certificate.RawDataMemory.Span.SequenceEqual(_externalPendingCert.RawDataMemory.Span))
 01923                            {
 01924                                (intermediates ??= new X509Certificate2Collection()).Add(certificate);
 01925                                transferred = true;
 01926                            }
 01927                        }
 1928                        finally
 01929                        {
 01930                            if (!transferred)
 01931                            {
 01932                                certificate.Dispose();
 01933                            }
 01934                        }
 01935                    }
 01936                }
 1937
 01938                _externalRemoteCertificates = intermediates;
 01939                intermediates = null;
 01940            }
 1941            finally
 01942            {
 01943                if (intermediates is not null)
 01944                {
 01945                    foreach (X509Certificate2 certificate in intermediates)
 01946                    {
 01947                        certificate.Dispose();
 01948                    }
 01949                }
 1950
 01951                if (chain is not null)
 01952                {
 01953                    X509Certificate2Collection extraStore = chain.ChainPolicy.ExtraStore;
 01954                    while (extraStore.Count > preexistingExtraCertsCount)
 01955                    {
 01956                        X509Certificate2 certificate = extraStore[preexistingExtraCertsCount];
 01957                        extraStore.RemoveAt(preexistingExtraCertsCount);
 01958                        certificate.Dispose();
 01959                    }
 1960
 01961                    chain.Dispose();
 01962                }
 01963            }
 1964
 01965            _externalValidationPending = true;
 01966        }
 1967
 1968        // Acquire the SafeFreeCredentials the PAL needs for the first ASC/ISC
 1969        // call. OpenSSL handles credential acquisition lazily inside the PAL,
 1970        // but SChannel rejects ASC/ISC with a null credentials handle.
 1971        //
 1972        // Server requires a pre-set CertificateContext (or one resolved via
 1973        // ServerCertSelectionDelegate above); the client connects anonymously.
 1974        // SslSessionsCache, the legacy CertSelectionDelegate, and client
 1975        // certificate selection are not yet integrated.
 1976        private void EnsureCredentialsAcquired()
 01977        {
 1978            // If SetContext or SetClientCertificateContext already produced a
 1979            // session-local handle, ActiveCredentialsRef() will route the PAL
 1980            // through it. Skip touching the shared TlsContext.CredentialsHandle
 1981            // to avoid racing with concurrent sessions on the same context.
 01982            if (_sessionCredentialsHandle is not null)
 01983            {
 01984                return;
 1985            }
 1986
 01987            if (_context!.CredentialsHandle is not null)
 01988            {
 01989                return;
 1990            }
 1991
 1992            // Multiple sessions on the same TlsContext can call EnsureCredentialsAcquired
 1993            // concurrently and each see CredentialsHandle == null. Atomically install
 1994            // ours; if another session beat us to it, dispose the loser to avoid a leak.
 1995            // Non-Windows PALs return null here (OpenSSL has no cred handle concept); the
 1996            // CompareExchange is a no-op in that case.
 01997            SafeFreeCredentials? acquired = SslStreamPal.AcquireCredentialsHandle(_options, false);
 01998            if (System.Threading.Interlocked.CompareExchange(ref _context!.CredentialsHandle, acquired, null) is not nul
 01999            {
 02000                acquired?.Dispose();
 02001            }
 02002        }
 2003
 2004        // Feed a decrypted post-handshake message (e.g. TLS 1.3 NewSessionTicket
 2005        // or KeyUpdate) back through ASC/ISC so SChannel updates its internal
 2006        // state. The PAL may or may not produce a reply token; if it does, stage
 2007        // it for the caller to send on the next drain.
 2008        private void ProcessPostHandshakeMessage(ReadOnlySpan<byte> data)
 02009        {
 02010            if (data.IsEmpty)
 02011            {
 02012                return;
 2013            }
 2014
 02015            ProtocolToken token = default;
 02016            token.RentBuffer = true;
 2017            try
 02018            {
 02019                if (_context!.IsServer)
 02020                {
 02021                    token = SslStreamPal.AcceptSecurityContext(
 02022                        ref ActiveCredentialsRef(),
 02023                        ref _securityContext,
 02024                        data,
 02025                        out _,
 02026                        _options);
 02027                }
 2028                else
 02029                {
 02030                    string hostName = TargetHostNameHelper.NormalizeHostName(_options.TargetHost);
 02031                    token = SslStreamPal.InitializeSecurityContext(
 02032                        ref ActiveCredentialsRef(),
 02033                        ref _securityContext,
 02034                        hostName,
 02035                        data,
 02036                        out _,
 02037                        _options);
 02038                }
 2039
 02040                if (token.Size > 0)
 02041                {
 02042                    Debug.Assert(token.Payload != null);
 02043                    AppendPending(new ReadOnlySpan<byte>(token.Payload, 0, token.Size));
 02044                }
 02045            }
 2046            finally
 02047            {
 02048                token.ReleasePayload();
 02049            }
 02050        }
 2051
 2052        // â”€â”€ Socket-bound I/O â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
 2053        //
 2054        // These methods are only valid when the session was created via
 2055        // Create(TlsContext, SafeSocketHandle). They drive ciphertext on the
 2056        // bound non-blocking socket and translate WouldBlock into WantRead/
 2057        // WantWrite back to the caller so a select/epoll/IOCP-like loop can
 2058        // schedule the next attempt.
 2059
 2060        private const int SocketScratchSize = MaxRecordPlaintext + 256;
 2061
 2062        private void ThrowIfNotSocketBound()
 02063        {
 02064            if (_socketHandle is null)
 02065            {
 02066                throw new InvalidOperationException(SR.net_tlssession_not_socket_bound);
 2067            }
 02068        }
 2069
 2070        // Drains any TLS bytes that we previously failed to fully send into the
 2071        // socket. Returns true if pending output is now empty, false if the
 2072        // socket would block (WantWrite should be surfaced).
 2073        private bool TryDrainPendingToSocket(out SocketError lastError)
 02074        {
 02075            lastError = SocketError.Success;
 02076            while (_pendingBuffer.ActiveLength > 0)
 02077            {
 02078                int sent = _socket!.Send(
 02079                    _pendingBuffer.ActiveReadOnlySpan,
 02080                    SocketFlags.None,
 02081                    out SocketError err);
 02082                lastError = err;
 02083                if (sent > 0)
 02084                {
 02085                    _pendingBuffer.Discard(sent);
 02086                    if (_pendingBuffer.ActiveLength == 0)
 02087                    {
 02088                        return true;
 2089                    }
 02090                    continue;
 2091                }
 02092                return false;
 2093            }
 02094            return true;
 02095        }
 2096
 2097        private protected TlsOperationStatus HandshakeSocketCore()
 02098        {
 02099            ThrowIfDisposed();
 02100            ThrowIfContextNotSet();
 02101            ThrowIfNotSocketBound();
 2102
 02103            if (_isHandshakeComplete && !_externalValidationPending && !_externalValidationResolved)
 02104            {
 02105                return TlsOperationStatus.Complete;
 2106            }
 2107
 02108            TlsOperationStatus? fast = null;
 2109            TryFastHandshake(ref fast);
 02110            if (fast.HasValue)
 02111            {
 02112                return fast.Value;
 2113            }
 2114
 2115            TryPeekClientHello(ref fast);
 02116            if (fast.HasValue)
 02117            {
 02118                return fast.Value;
 2119            }
 2120
 02121            _socketInBuffer.EnsureAvailableSpace(SocketScratchSize);
 02122            byte[] scratch = ArrayPool<byte>.Shared.Rent(SocketScratchSize);
 2123            try
 02124            {
 02125                while (true)
 02126                {
 02127                    if (_pendingBuffer.ActiveLength > 0)
 02128                    {
 02129                        if (!TryDrainPendingToSocket(out SocketError drainErr))
 02130                        {
 02131                            if (drainErr == SocketError.WouldBlock)
 02132                            {
 02133                                return TlsOperationStatus.DestinationTooSmall;
 2134                            }
 02135                            throw new SocketException((int)drainErr);
 2136                        }
 02137                    }
 2138
 02139                    TlsOperationStatus status = HandshakeBufferedCore(
 02140                        _socketInBuffer.ActiveReadOnlySpan,
 02141                        scratch,
 02142                        out int consumed,
 02143                        out int produced);
 2144
 02145                    if (consumed > 0)
 02146                    {
 02147                        _socketInBuffer.Discard(consumed);
 02148                    }
 2149
 02150                    if (produced > 0)
 02151                    {
 02152                        int offset = 0;
 02153                        while (offset < produced)
 02154                        {
 02155                            int sent = _socket!.Send(
 02156                                new ReadOnlySpan<byte>(scratch, offset, produced - offset),
 02157                                SocketFlags.None,
 02158                                out SocketError sendErr);
 02159                            if (sent > 0)
 02160                            {
 02161                                offset += sent;
 02162                                continue;
 2163                            }
 02164                            if (sendErr == SocketError.WouldBlock)
 02165                            {
 2166                                // Stash the unsent tail so the next call resumes the drain.
 02167                                AppendPending(new ReadOnlySpan<byte>(scratch, offset, produced - offset));
 02168                                return TlsOperationStatus.DestinationTooSmall;
 2169                            }
 02170                            throw new SocketException((int)sendErr);
 2171                        }
 02172                    }
 2173
 02174                    switch (status)
 2175                    {
 2176                        case TlsOperationStatus.Complete:
 02177                            return TlsOperationStatus.Complete;
 2178
 2179                        case TlsOperationStatus.NeedMoreData:
 2180                            // Should not happen with conservative scratch sizing, but guard.
 02181                            _socketInBuffer.EnsureAvailableSpace(1);
 02182                            int received = _socket!.Receive(
 02183                                _socketInBuffer.AvailableSpan,
 02184                                SocketFlags.None,
 02185                                out SocketError recvErr);
 02186                            if (received > 0)
 02187                            {
 02188                                _socketInBuffer.Commit(received);
 02189                                continue;
 2190                            }
 02191                            if (recvErr == SocketError.WouldBlock)
 02192                            {
 02193                                return TlsOperationStatus.NeedMoreData;
 2194                            }
 02195                            if (received == 0)
 02196                            {
 02197                                return TlsOperationStatus.Closed;
 2198                            }
 02199                            throw new SocketException((int)recvErr);
 2200
 2201                        case TlsOperationStatus.DestinationTooSmall:
 2202                            // Output is staged; loop drains it on next iteration.
 02203                            continue;
 2204
 2205                        default:
 02206                            return status;
 2207                    }
 2208                }
 2209            }
 2210            finally
 02211            {
 02212                ArrayPool<byte>.Shared.Return(scratch);
 02213            }
 02214        }
 2215
 2216        private protected TlsOperationStatus ReadSocketCore(Span<byte> buffer, out int bytesRead)
 02217        {
 02218            ThrowIfDisposed();
 02219            ThrowIfNotSocketBound();
 2220            // A prior handshake may have surfaced NeedsCertificateValidation whose result the
 2221            // caller has not recorded yet. The buffered Read core gates on this, but the socket
 2222            // core can return NeedMoreData off a non-blocking recv before ever reaching it, so
 2223            // apply the guard here to stay consistent with the buffered path and socket Write.
 02224            ThrowIfPendingExternalValidation();
 02225            bytesRead = 0;
 2226
 02227            if (!_isHandshakeComplete)
 02228            {
 02229                throw new InvalidOperationException(SR.net_tlssession_handshake_not_complete);
 2230            }
 2231
 02232            TlsOperationStatus? fast = null;
 2233            TryFastRead(buffer, ref bytesRead, ref fast);
 02234            if (fast.HasValue)
 02235            {
 02236                return fast.Value;
 2237            }
 2238
 02239            _socketInBuffer.EnsureAvailableSpace(SocketScratchSize);
 2240
 02241            while (true)
 02242            {
 02243                if (_socketInBuffer.ActiveLength > 0)
 02244                {
 02245                    TlsOperationStatus status = ReadBufferedCore(
 02246                        _socketInBuffer.ActiveReadOnlySpan,
 02247                        buffer,
 02248                        out int consumed,
 02249                        out int produced);
 2250
 02251                    if (consumed > 0)
 02252                    {
 02253                        _socketInBuffer.Discard(consumed);
 02254                    }
 2255
 02256                    bytesRead = produced;
 2257
 02258                    if (status == TlsOperationStatus.Complete && produced > 0)
 02259                    {
 02260                        return TlsOperationStatus.Complete;
 2261                    }
 02262                    if (status == TlsOperationStatus.Closed)
 02263                    {
 02264                        return TlsOperationStatus.Closed;
 2265                    }
 02266                    if (status == TlsOperationStatus.Complete && produced == 0)
 02267                    {
 2268                        // Post-handshake message consumed; loop to try more.
 02269                        continue;
 2270                    }
 02271                    if (status != TlsOperationStatus.NeedMoreData)
 02272                    {
 02273                        return status;
 2274                    }
 2275                    // WantRead: fall through to socket recv.
 02276                }
 2277
 02278                _socketInBuffer.EnsureAvailableSpace(1);
 02279                int received = _socket!.Receive(
 02280                    _socketInBuffer.AvailableSpan,
 02281                    SocketFlags.None,
 02282                    out SocketError recvErr);
 02283                if (received > 0)
 02284                {
 02285                    _socketInBuffer.Commit(received);
 02286                    continue;
 2287                }
 02288                if (recvErr == SocketError.WouldBlock)
 02289                {
 02290                    return TlsOperationStatus.NeedMoreData;
 2291                }
 02292                if (received == 0)
 02293                {
 02294                    return TlsOperationStatus.Closed;
 2295                }
 02296                throw new SocketException((int)recvErr);
 2297            }
 02298        }
 2299
 2300        private protected TlsOperationStatus WriteSocketCore(ReadOnlySpan<byte> buffer, out int bytesWritten)
 02301        {
 02302            ThrowIfDisposed();
 02303            ThrowIfNotSocketBound();
 2304            // Empty-buffer writes short-circuit before reaching WriteBufferedCore's guard, so
 2305            // gate on any unrecorded external-validation result here too, matching socket Read.
 02306            ThrowIfPendingExternalValidation();
 02307            bytesWritten = 0;
 2308
 02309            if (!_isHandshakeComplete)
 02310            {
 02311                throw new InvalidOperationException(SR.net_tlssession_handshake_not_complete);
 2312            }
 2313
 02314            TlsOperationStatus? fast = null;
 2315            TryFastWrite(buffer, ref bytesWritten, ref fast);
 02316            if (fast.HasValue)
 02317            {
 02318                return fast.Value;
 2319            }
 2320
 2321            // Drain any previously stashed ciphertext first.
 02322            if (_pendingBuffer.ActiveLength > 0)
 02323            {
 02324                if (!TryDrainPendingToSocket(out SocketError drainErr))
 02325                {
 02326                    if (drainErr == SocketError.WouldBlock)
 02327                    {
 02328                        return TlsOperationStatus.DestinationTooSmall;
 2329                    }
 02330                    throw new SocketException((int)drainErr);
 2331                }
 02332            }
 2333
 02334            if (buffer.IsEmpty)
 02335            {
 02336                return TlsOperationStatus.Complete;
 2337            }
 2338
 02339            byte[] scratch = ArrayPool<byte>.Shared.Rent(SocketScratchSize);
 2340            try
 02341            {
 02342                int totalConsumed = 0;
 02343                while (totalConsumed < buffer.Length)
 02344                {
 02345                    TlsOperationStatus encStatus = WriteBufferedCore(
 02346                        buffer.Slice(totalConsumed),
 02347                        scratch,
 02348                        out int consumed,
 02349                        out int produced);
 2350
 02351                    totalConsumed += consumed;
 2352
 02353                    if (produced > 0)
 02354                    {
 02355                        int offset = 0;
 02356                        while (offset < produced)
 02357                        {
 02358                            int sent = _socket!.Send(
 02359                                new ReadOnlySpan<byte>(scratch, offset, produced - offset),
 02360                                SocketFlags.None,
 02361                                out SocketError sendErr);
 02362                            if (sent > 0)
 02363                            {
 02364                                offset += sent;
 02365                                continue;
 2366                            }
 02367                            if (sendErr == SocketError.WouldBlock)
 02368                            {
 02369                                AppendPending(new ReadOnlySpan<byte>(scratch, offset, produced - offset));
 02370                                bytesWritten = totalConsumed;
 02371                                return TlsOperationStatus.DestinationTooSmall;
 2372                            }
 02373                            throw new SocketException((int)sendErr);
 2374                        }
 02375                    }
 2376
 02377                    if (encStatus == TlsOperationStatus.DestinationTooSmall)
 02378                    {
 2379                        // Pending output owed; resume next call.
 02380                        bytesWritten = totalConsumed;
 02381                        return TlsOperationStatus.DestinationTooSmall;
 2382                    }
 02383                    if (encStatus != TlsOperationStatus.Complete)
 02384                    {
 02385                        bytesWritten = totalConsumed;
 02386                        return encStatus;
 2387                    }
 02388                    if (consumed == 0)
 02389                    {
 2390                        // Nothing more to do (shouldn't happen with non-empty buffer).
 02391                        break;
 2392                    }
 02393                }
 2394
 02395                bytesWritten = totalConsumed;
 02396                return TlsOperationStatus.Complete;
 2397            }
 2398            finally
 02399            {
 02400                ArrayPool<byte>.Shared.Return(scratch);
 02401            }
 02402        }
 2403
 2404        // Simple driver that runs a buffered "output-only" op (Shutdown /
 2405        // RequestClientCertificate) and drains its staged ciphertext to the socket.
 2406        private TlsOperationStatus DriveBufferedOpOverSocket(Func<Span<byte>, (TlsOperationStatus status, int written)> 
 02407        {
 02408            ThrowIfDisposed();
 02409            ThrowIfNotSocketBound();
 2410
 2411            // Drain any leftover pending output before staging new bytes.
 02412            if (_pendingBuffer.ActiveLength > 0)
 02413            {
 02414                if (!TryDrainPendingToSocket(out SocketError leftoverErr))
 02415                {
 02416                    if (leftoverErr == SocketError.WouldBlock)
 02417                    {
 02418                        return TlsOperationStatus.DestinationTooSmall;
 2419                    }
 02420                    throw new SocketException((int)leftoverErr);
 2421                }
 02422            }
 2423
 02424            byte[] scratch = ArrayPool<byte>.Shared.Rent(SocketScratchSize);
 2425            try
 02426            {
 02427                (TlsOperationStatus status, int written) = op(scratch);
 02428                if (written > 0)
 02429                {
 02430                    int offset = 0;
 02431                    while (offset < written)
 02432                    {
 02433                        int sent = _socket!.Send(
 02434                            new ReadOnlySpan<byte>(scratch, offset, written - offset),
 02435                            SocketFlags.None,
 02436                            out SocketError sendErr);
 02437                        if (sent > 0)
 02438                        {
 02439                            offset += sent;
 02440                            continue;
 2441                        }
 02442                        if (sendErr == SocketError.WouldBlock)
 02443                        {
 02444                            AppendPending(new ReadOnlySpan<byte>(scratch, offset, written - offset));
 02445                            return TlsOperationStatus.DestinationTooSmall;
 2446                        }
 02447                        throw new SocketException((int)sendErr);
 2448                    }
 02449                }
 02450                return status;
 2451            }
 2452            finally
 02453            {
 02454                ArrayPool<byte>.Shared.Return(scratch);
 02455            }
 02456        }
 2457
 2458        private protected TlsOperationStatus ShutdownSocketCore()
 02459            => DriveBufferedOpOverSocket(dest =>
 02460            {
 02461                TlsOperationStatus s = ShutdownBufferedCore(dest, out int w);
 02462                return (s, w);
 02463            });
 2464
 2465        private protected TlsOperationStatus RequestClientCertificateSocketCore()
 02466        {
 02467            ThrowIfDisposed();
 02468            ThrowIfNotSocketBound();
 2469            // The fd fast path below bypasses the buffered core, so apply the external-validation
 2470            // guard here as well, matching the other socket-bound operations.
 02471            ThrowIfPendingExternalValidation();
 2472
 02473            TlsOperationStatus? fast = null;
 2474            TryFastRequestClientCertificate(ref fast);
 02475            if (fast.HasValue)
 02476            {
 02477                return fast.Value;
 2478            }
 2479
 02480            return DriveBufferedOpOverSocket(dest =>
 02481            {
 02482                TlsOperationStatus s = RequestClientCertificateBufferedCore(dest, out int w);
 02483                return (s, w);
 02484            });
 02485        }
 2486
 2487        // Platform hooks. Implemented by the OpenSSL partial (TlsSession.OpenSsl.cs)
 2488        // to bind the socket fd directly to the SSL object and drive ciphertext
 2489        // through OpenSSL. On Windows (SChannel) these are no-ops and the buffered
 2490        // ProcessHandshake/Encrypt/Decrypt path above is used unchanged.
 2491        partial void EnableNativeSocketBinding(SafeSocketHandle socket, ref bool nativeBindingEnabled);
 2492        partial void TryFastHandshake(ref TlsOperationStatus? result);
 2493        partial void TryFastRequestClientCertificate(ref TlsOperationStatus? result);
 2494        partial void TryPeekClientHello(ref TlsOperationStatus? result);
 2495        partial void TryFastRead(Span<byte> buffer, ref int bytesRead, ref TlsOperationStatus? result);
 2496        partial void TryFastWrite(ReadOnlySpan<byte> buffer, ref int bytesWritten, ref TlsOperationStatus? result);
 2497
 2498        // Fires at the end of SetContext. Platforms with a deferred-server
 2499        // fast path (OpenSSL socket-bound sessions) use this hook to activate
 2500        // native binding now that server options are known.
 2501        partial void OnServerContextSet();
 2502
 2503        // Fires from Dispose so the OpenSSL partial can release the peek BIO if the
 2504        // session is disposed before its ownership is transferred to an SSL* handle.
 2505        partial void OnDispose();
 2506
 2507        // Fires from GetClientHelloBytes so the OpenSSL partial can return a span
 2508        // over the socket-replay BIO's retained peek buffer. No-op on the buffered
 2509        // path; the getter falls back to the managed byte[] copy.
 2510        partial void TryGetNativeClientHelloBytes(ref ReadOnlySpan<byte> bytes);
 2511
 2512        public void Dispose()
 02513        {
 02514            if (_disposed)
 02515            {
 02516                return;
 2517            }
 02518            _disposed = true;
 2519
 02520            DisposeExternalRemoteCertificates();
 02521            _externalPendingCert?.Dispose();
 02522            _externalPendingCert = null;
 2523
 02524            _securityContext?.Dispose();
 02525            _securityContext = null;
 2526
 2527            // Disposes the underlying SafeSocketHandle as well (ownership transferred at Create).
 02528            if (_socket is not null)
 02529            {
 02530                _socket.Dispose();
 02531                _socket = null;
 02532            }
 2533            else
 02534            {
 02535                _socketHandle?.Dispose();
 02536            }
 02537            _socketHandle = null;
 2538
 02539            if (_ownsOptions)
 02540            {
 02541                _options.Dispose();
 02542            }
 2543
 02544            _pendingBuffer.Dispose();
 02545            if (_decryptScratch != null)
 02546            {
 02547                ArrayPool<byte>.Shared.Return(_decryptScratch);
 02548                _decryptScratch = null;
 02549            }
 02550            _socketInBuffer.Dispose();
 2551
 2552            // Release the session-local credentials handle acquired by
 2553            // SetContext / SetClientCertificateContext. The shared handle on
 2554            // _context is owned by TlsContext and released with it.
 02555            _sessionCredentialsHandle?.Dispose();
 02556            _sessionCredentialsHandle = null;
 2557
 2558            // Release the session-owned CertificateContext (only true when we built
 2559            // one via the server-cert-selector path). Caller-provided contexts and the
 2560            // template context inherited from TlsContext are not disposed here.
 02561            if (_ownsSessionCertificateContext && _sessionCertificateContext is not null)
 02562            {
 02563                _sessionCertificateContext.ReleaseResources();
 02564            }
 02565            _sessionCertificateContext = null;
 02566            _ownsSessionCertificateContext = false;
 2567
 2568            OnDispose();
 02569        }
 2570    }
 2571}

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()