File: System\Net\Security\NegotiateStream.cs
Web Access
Project: src\src\libraries\System.Net.Security\src\System.Net.Security.csproj (System.Net.Security)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Buffers;
using System.Buffers.Binary;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using System.Runtime.Versioning;
using System.Security.Authentication;
using System.Security.Authentication.ExtendedProtection;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
 
namespace System.Net.Security
{
    /// <summary>
    /// Provides a stream that uses the Negotiate security protocol to authenticate the client, and optionally the server, in client-server communication.
    /// </summary>
    public partial class NegotiateStream : AuthenticatedStream
    {
        /// <summary>Set as the _exception when the instance is disposed.</summary>
        private static readonly ExceptionDispatchInfo s_disposedSentinel = ExceptionDispatchInfo.Capture(new ObjectDisposedException(nameof(NegotiateStream), (string?)null));
 
        private const int ERROR_TRUST_FAILURE = 1790;   // Used to serialize protectionLevel or impersonationLevel mismatch error to the remote side.
        private const int MaxReadFrameSize = 64 * 1024;
        private const int MaxWriteDataSize = 63 * 1024; // 1k for the framing and trailer that is always less as per SSPI.
        private const string DefaultPackage = NegotiationInfoClass.Negotiate;
 
#pragma warning disable CA1825 // used in reference comparison, requires unique object identity
        private static readonly byte[] s_emptyMessage = new byte[0];
#pragma warning restore CA1825
 
        private readonly byte[] _writeHeader;
        private readonly byte[] _readHeader;
        private byte[] _readBuffer;
        private int _readBufferOffset;
        private int _readBufferCount;
        private ArrayBufferWriter<byte>? _writeBuffer;
 
        private volatile bool _writeInProgress;
        private volatile bool _readInProgress;
        private volatile bool _authInProgress;
 
        private ExceptionDispatchInfo? _exception;
        private StreamFramer? _framer;
        private NegotiateAuthentication? _context;
        private bool _canRetryAuthentication;
        private ProtectionLevel _expectedProtectionLevel;
        private TokenImpersonationLevel _expectedImpersonationLevel;
        private ExtendedProtectionPolicy? _extendedProtectionPolicy;
 
        private bool isNtlm;
 
        /// <summary>
        /// SSPI does not send a server ack on successful auth.
        /// This is a state variable used to gracefully handle auth confirmation.
        /// </summary>
        private bool _remoteOk;
 
        public NegotiateStream(Stream innerStream) : this(innerStream, false)
        {
        }
 
        public NegotiateStream(Stream innerStream, bool leaveInnerStreamOpen) : base(innerStream, leaveInnerStreamOpen)
        {
            _writeHeader = new byte[4];
            _readHeader = new byte[4];
            _readBuffer = Array.Empty<byte>();
        }
 
        protected override void Dispose(bool disposing)
        {
            try
            {
                _exception = s_disposedSentinel;
                _context?.Dispose();
            }
            finally
            {
                base.Dispose(disposing);
            }
        }
 
        public override async ValueTask DisposeAsync()
        {
            try
            {
                _exception = s_disposedSentinel;
                _context?.Dispose();
            }
            finally
            {
                await base.DisposeAsync().ConfigureAwait(false);
            }
        }
 
        public virtual IAsyncResult BeginAuthenticateAsClient(AsyncCallback? asyncCallback, object? asyncState) =>
            BeginAuthenticateAsClient((NetworkCredential)CredentialCache.DefaultCredentials, binding: null, string.Empty, ProtectionLevel.EncryptAndSign, TokenImpersonationLevel.Identification,
                                      asyncCallback, asyncState);
 
        public virtual IAsyncResult BeginAuthenticateAsClient(NetworkCredential credential, string targetName, AsyncCallback? asyncCallback, object? asyncState) =>
            BeginAuthenticateAsClient(credential, binding: null, targetName, ProtectionLevel.EncryptAndSign, TokenImpersonationLevel.Identification,
                                      asyncCallback, asyncState);
 
        public virtual IAsyncResult BeginAuthenticateAsClient(NetworkCredential credential, ChannelBinding? binding, string targetName, AsyncCallback? asyncCallback, object? asyncState) =>
            BeginAuthenticateAsClient(credential, binding, targetName, ProtectionLevel.EncryptAndSign, TokenImpersonationLevel.Identification,
                                      asyncCallback, asyncState);
 
        public virtual IAsyncResult BeginAuthenticateAsClient(
            NetworkCredential credential, string targetName, ProtectionLevel requiredProtectionLevel, TokenImpersonationLevel allowedImpersonationLevel,
            AsyncCallback? asyncCallback, object? asyncState) =>
            BeginAuthenticateAsClient(credential, binding: null, targetName, requiredProtectionLevel, allowedImpersonationLevel,
                                      asyncCallback, asyncState);
 
        public virtual IAsyncResult BeginAuthenticateAsClient(
            NetworkCredential credential, ChannelBinding? binding, string targetName, ProtectionLevel requiredProtectionLevel, TokenImpersonationLevel allowedImpersonationLevel,
            AsyncCallback? asyncCallback, object? asyncState) =>
            TaskToAsyncResult.Begin(AuthenticateAsClientAsync(credential, binding, targetName, requiredProtectionLevel, allowedImpersonationLevel), asyncCallback, asyncState);
 
        public virtual void EndAuthenticateAsClient(IAsyncResult asyncResult) => TaskToAsyncResult.End(asyncResult);
 
        public virtual void AuthenticateAsServer() =>
            AuthenticateAsServer((NetworkCredential)CredentialCache.DefaultCredentials, policy: null, ProtectionLevel.EncryptAndSign, TokenImpersonationLevel.Identification);
 
        public virtual void AuthenticateAsServer(ExtendedProtectionPolicy? policy) =>
            AuthenticateAsServer((NetworkCredential)CredentialCache.DefaultCredentials, policy, ProtectionLevel.EncryptAndSign, TokenImpersonationLevel.Identification);
 
        public virtual void AuthenticateAsServer(NetworkCredential credential, ProtectionLevel requiredProtectionLevel, TokenImpersonationLevel requiredImpersonationLevel) =>
            AuthenticateAsServer(credential, policy: null, requiredProtectionLevel, requiredImpersonationLevel);
 
        public virtual void AuthenticateAsServer(NetworkCredential credential, ExtendedProtectionPolicy? policy, ProtectionLevel requiredProtectionLevel, TokenImpersonationLevel requiredImpersonationLevel)
        {
            ValidateCreateContext(DefaultPackage, credential, string.Empty, policy, requiredProtectionLevel, requiredImpersonationLevel);
            AuthenticateAsync<SyncReadWriteAdapter>(default(CancellationToken)).GetAwaiter().GetResult();
        }
 
        public virtual IAsyncResult BeginAuthenticateAsServer(AsyncCallback? asyncCallback, object? asyncState) =>
            BeginAuthenticateAsServer((NetworkCredential)CredentialCache.DefaultCredentials, policy: null, ProtectionLevel.EncryptAndSign, TokenImpersonationLevel.Identification, asyncCallback, asyncState);
 
        public virtual IAsyncResult BeginAuthenticateAsServer(ExtendedProtectionPolicy? policy, AsyncCallback? asyncCallback, object? asyncState) =>
            BeginAuthenticateAsServer((NetworkCredential)CredentialCache.DefaultCredentials, policy, ProtectionLevel.EncryptAndSign, TokenImpersonationLevel.Identification, asyncCallback, asyncState);
 
        public virtual IAsyncResult BeginAuthenticateAsServer(
            NetworkCredential credential, ProtectionLevel requiredProtectionLevel, TokenImpersonationLevel requiredImpersonationLevel,
            AsyncCallback? asyncCallback, object? asyncState) =>
            BeginAuthenticateAsServer(credential, policy: null, requiredProtectionLevel, requiredImpersonationLevel, asyncCallback, asyncState);
 
        public virtual IAsyncResult BeginAuthenticateAsServer(
            NetworkCredential credential, ExtendedProtectionPolicy? policy, ProtectionLevel requiredProtectionLevel, TokenImpersonationLevel requiredImpersonationLevel,
            AsyncCallback? asyncCallback, object? asyncState) =>
            TaskToAsyncResult.Begin(AuthenticateAsServerAsync(credential, policy, requiredProtectionLevel, requiredImpersonationLevel), asyncCallback, asyncState);
 
        public virtual void EndAuthenticateAsServer(IAsyncResult asyncResult) => TaskToAsyncResult.End(asyncResult);
 
        public virtual void AuthenticateAsClient() =>
            AuthenticateAsClient((NetworkCredential)CredentialCache.DefaultCredentials, binding: null, string.Empty, ProtectionLevel.EncryptAndSign, TokenImpersonationLevel.Identification);
 
        public virtual void AuthenticateAsClient(NetworkCredential credential, string targetName) =>
            AuthenticateAsClient(credential, binding: null, targetName, ProtectionLevel.EncryptAndSign, TokenImpersonationLevel.Identification);
 
        public virtual void AuthenticateAsClient(NetworkCredential credential, ChannelBinding? binding, string targetName) =>
            AuthenticateAsClient(credential, binding, targetName, ProtectionLevel.EncryptAndSign, TokenImpersonationLevel.Identification);
 
        public virtual void AuthenticateAsClient(
            NetworkCredential credential, string targetName, ProtectionLevel requiredProtectionLevel, TokenImpersonationLevel allowedImpersonationLevel) =>
            AuthenticateAsClient(credential, binding: null, targetName, requiredProtectionLevel, allowedImpersonationLevel);
 
        public virtual void AuthenticateAsClient(
            NetworkCredential credential, ChannelBinding? binding, string targetName, ProtectionLevel requiredProtectionLevel, TokenImpersonationLevel allowedImpersonationLevel)
        {
            ValidateCreateContext(DefaultPackage, isServer: false, credential, targetName, binding, requiredProtectionLevel, allowedImpersonationLevel);
            AuthenticateAsync<SyncReadWriteAdapter>(default(CancellationToken)).GetAwaiter().GetResult();
        }
 
        public virtual Task AuthenticateAsClientAsync() =>
            AuthenticateAsClientAsync((NetworkCredential)CredentialCache.DefaultCredentials, binding: null, string.Empty, ProtectionLevel.EncryptAndSign, TokenImpersonationLevel.Identification);
 
        public virtual Task AuthenticateAsClientAsync(NetworkCredential credential, string targetName) =>
            AuthenticateAsClientAsync(credential, binding: null, targetName, ProtectionLevel.EncryptAndSign, TokenImpersonationLevel.Identification);
 
        public virtual Task AuthenticateAsClientAsync(
            NetworkCredential credential, string targetName,
            ProtectionLevel requiredProtectionLevel,
            TokenImpersonationLevel allowedImpersonationLevel) =>
            AuthenticateAsClientAsync(credential, binding: null, targetName, requiredProtectionLevel, allowedImpersonationLevel);
 
        public virtual Task AuthenticateAsClientAsync(NetworkCredential credential, ChannelBinding? binding, string targetName) =>
            AuthenticateAsClientAsync(credential, binding, targetName, ProtectionLevel.EncryptAndSign, TokenImpersonationLevel.Identification);
 
        public virtual Task AuthenticateAsClientAsync(
            NetworkCredential credential, ChannelBinding? binding, string targetName, ProtectionLevel requiredProtectionLevel,
            TokenImpersonationLevel allowedImpersonationLevel)
        {
            ValidateCreateContext(DefaultPackage, isServer: false, credential, targetName, binding, requiredProtectionLevel, allowedImpersonationLevel);
            return AuthenticateAsync<AsyncReadWriteAdapter>(default(CancellationToken));
        }
 
        public virtual Task AuthenticateAsServerAsync() =>
            AuthenticateAsServerAsync((NetworkCredential)CredentialCache.DefaultCredentials, policy: null, ProtectionLevel.EncryptAndSign, TokenImpersonationLevel.Identification);
 
        public virtual Task AuthenticateAsServerAsync(ExtendedProtectionPolicy? policy) =>
            AuthenticateAsServerAsync((NetworkCredential)CredentialCache.DefaultCredentials, policy, ProtectionLevel.EncryptAndSign, TokenImpersonationLevel.Identification);
 
        public virtual Task AuthenticateAsServerAsync(NetworkCredential credential, ProtectionLevel requiredProtectionLevel, TokenImpersonationLevel requiredImpersonationLevel) =>
            AuthenticateAsServerAsync(credential, policy: null, requiredProtectionLevel, requiredImpersonationLevel);
 
        public virtual Task AuthenticateAsServerAsync(
            NetworkCredential credential, ExtendedProtectionPolicy? policy, ProtectionLevel requiredProtectionLevel, TokenImpersonationLevel requiredImpersonationLevel)
        {
            ValidateCreateContext(DefaultPackage, credential, string.Empty, policy, requiredProtectionLevel, requiredImpersonationLevel);
            return AuthenticateAsync<AsyncReadWriteAdapter>(default(CancellationToken));
        }
 
        public override bool IsAuthenticated => IsAuthenticatedCore;
 
        [MemberNotNullWhen(true, nameof(_context))]
        private bool IsAuthenticatedCore => _context != null && HandshakeComplete && _exception == null && _remoteOk;
 
        public override bool IsMutuallyAuthenticated =>
            IsAuthenticatedCore &&
            !string.Equals(_context.Package, NegotiationInfoClass.NTLM) && // suppressing for NTLM since SSPI does not return correct value in the context flags.
            _context.IsMutuallyAuthenticated;
 
        public override bool IsEncrypted => IsAuthenticatedCore && _context.IsEncrypted;
 
        public override bool IsSigned => IsAuthenticatedCore && (_context.IsSigned || _context.IsEncrypted);
 
        public override bool IsServer => _context != null && _context.IsServer;
 
        public virtual TokenImpersonationLevel ImpersonationLevel
        {
            get
            {
                ThrowIfFailed(authSuccessCheck: true);
                return PrivateImpersonationLevel;
            }
        }
 
        private TokenImpersonationLevel PrivateImpersonationLevel => _context!.ImpersonationLevel;
 
        private bool HandshakeComplete => _context!.IsAuthenticated;
 
        private bool CanGetSecureStream => _context!.IsEncrypted || _context.IsSigned;
 
        public virtual IIdentity RemoteIdentity
        {
            get
            {
                ThrowIfFailed(authSuccessCheck: true);
                return _context!.RemoteIdentity;
            }
        }
 
        public override bool CanSeek => false;
 
        public override bool CanRead => IsAuthenticated && InnerStream.CanRead;
 
        public override bool CanTimeout => InnerStream.CanTimeout;
 
        public override bool CanWrite => IsAuthenticated && InnerStream.CanWrite;
 
        public override int ReadTimeout
        {
            get => InnerStream.ReadTimeout;
            set => InnerStream.ReadTimeout = value;
        }
 
        public override int WriteTimeout
        {
            get => InnerStream.WriteTimeout;
            set => InnerStream.WriteTimeout = value;
        }
 
        public override long Length => InnerStream.Length;
 
        public override long Position
        {
            get => InnerStream.Position;
            set => throw new NotSupportedException(SR.net_noseek);
        }
 
        public override void SetLength(long value) =>
            InnerStream.SetLength(value);
 
        public override long Seek(long offset, SeekOrigin origin) =>
            throw new NotSupportedException(SR.net_noseek);
 
        public override void Flush() =>
            InnerStream.Flush();
 
        public override Task FlushAsync(CancellationToken cancellationToken) =>
            InnerStream.FlushAsync(cancellationToken);
 
        public override int Read(byte[] buffer, int offset, int count)
        {
            ValidateBufferArguments(buffer, offset, count);
 
            ThrowIfFailed(authSuccessCheck: true);
            if (!CanGetSecureStream)
            {
                return InnerStream.Read(buffer, offset, count);
            }
 
            ValueTask<int> vt = ReadAsync<SyncReadWriteAdapter>(new Memory<byte>(buffer, offset, count), default(CancellationToken));
            Debug.Assert(vt.IsCompleted, "Should have completed synchroously with sync adapter");
            return vt.GetAwaiter().GetResult();
        }
 
        public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
        {
            ValidateBufferArguments(buffer, offset, count);
 
            ThrowIfFailed(authSuccessCheck: true);
            if (!CanGetSecureStream)
            {
                return InnerStream.ReadAsync(buffer, offset, count, cancellationToken);
            }
 
            return ReadAsync<AsyncReadWriteAdapter>(new Memory<byte>(buffer, offset, count), cancellationToken).AsTask();
        }
 
        public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
        {
            ThrowIfFailed(authSuccessCheck: true);
            if (!CanGetSecureStream)
            {
                return InnerStream.ReadAsync(buffer, cancellationToken);
            }
 
            return ReadAsync<AsyncReadWriteAdapter>(buffer, cancellationToken);
        }
 
        private async ValueTask<int> ReadAsync<TIOAdapter>(Memory<byte> buffer, CancellationToken cancellationToken)
            where TIOAdapter : IReadWriteAdapter
        {
            Debug.Assert(_context is not null);
 
            if (Interlocked.Exchange(ref _readInProgress, true))
            {
                throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, "read"));
            }
 
            try
            {
                ThrowIfFailed(authSuccessCheck: true);
 
                if (_readBufferCount != 0)
                {
                    int copyBytes = Math.Min(_readBufferCount, buffer.Length);
                    if (copyBytes != 0)
                    {
                        _readBuffer.AsMemory(_readBufferOffset, copyBytes).CopyTo(buffer);
                        _readBufferOffset += copyBytes;
                        _readBufferCount -= copyBytes;
                    }
                    return copyBytes;
                }
 
                while (true)
                {
                    int readBytes = await ReadAllAsync(InnerStream, _readHeader, allowZeroRead: true, cancellationToken).ConfigureAwait(false);
                    if (readBytes == 0)
                    {
                        return 0;
                    }
 
                    // Replace readBytes with the body size recovered from the header content.
                    readBytes = BinaryPrimitives.ReadInt32LittleEndian(_readHeader);
 
                    // The body carries 4 bytes for trailer size slot plus trailer, hence <= 4 frame size is always an error.
                    // Additionally we'd like to restrict the read frame size to 64k.
                    if (readBytes <= 4 || readBytes > MaxReadFrameSize)
                    {
                        throw new IOException(SR.net_frame_read_size);
                    }
 
                    // Always pass InternalBuffer for SSPI "in place" decryption.
                    // A user buffer can be shared by many threads in that case decryption/integrity check may fail cause of data corruption.
                    _readBufferCount = readBytes;
                    _readBufferOffset = 0;
                    if (_readBuffer.Length < readBytes)
                    {
                        _readBuffer = new byte[readBytes];
                    }
 
                    readBytes = await ReadAllAsync(InnerStream, new Memory<byte>(_readBuffer, 0, readBytes), allowZeroRead: false, cancellationToken).ConfigureAwait(false);
 
                    // Decrypt into the same buffer (decrypted data size can be shrunk after decryption).
                    NegotiateAuthenticationStatusCode statusCode;
                    if (isNtlm && !_context.IsEncrypted)
                    {
                        // Non-encrypted NTLM uses an encoding quirk
                        const int NtlmSignatureLength = 16;
 
                        if (readBytes < NtlmSignatureLength ||
                            !_context.VerifyIntegrityCheck(_readBuffer.AsSpan(NtlmSignatureLength, readBytes - NtlmSignatureLength), _readBuffer.AsSpan(0, NtlmSignatureLength)))
                        {
                            statusCode = NegotiateAuthenticationStatusCode.InvalidToken;
                        }
                        else
                        {
                            _readBufferOffset = NtlmSignatureLength;
                            _readBufferCount = readBytes - NtlmSignatureLength;
                            statusCode = NegotiateAuthenticationStatusCode.Completed;
                        }
                    }
                    else
                    {
                        statusCode = _context.UnwrapInPlace(_readBuffer.AsSpan(0, readBytes), out _readBufferOffset, out _readBufferCount, out _);
                    }
 
                    if (statusCode != NegotiateAuthenticationStatusCode.Completed)
                    {
                        // TODO: Better exception
                        throw new IOException(SR.net_io_read);
                    }
 
                    // Decrypted data can be shrunk after decryption.
                    if (_readBufferCount == 0 && buffer.Length != 0)
                    {
                        // Read again.
                        continue;
                    }
 
                    int copyBytes = Math.Min(_readBufferCount, buffer.Length);
                    _readBuffer.AsMemory(_readBufferOffset, copyBytes).CopyTo(buffer);
                    _readBufferOffset += copyBytes;
                    _readBufferCount -= copyBytes;
 
                    return copyBytes;
                }
            }
            catch (Exception e) when (!(e is IOException || e is OperationCanceledException))
            {
                throw new IOException(SR.net_io_read, e);
            }
            finally
            {
                _readInProgress = false;
            }
 
            static async ValueTask<int> ReadAllAsync(Stream stream, Memory<byte> buffer, bool allowZeroRead, CancellationToken cancellationToken)
            {
                int read = await TIOAdapter.ReadAtLeastAsync(
                    stream, buffer, buffer.Length, throwOnEndOfStream: false, cancellationToken).ConfigureAwait(false);
                if (read < buffer.Length)
                {
                    if (read != 0 || !allowZeroRead)
                    {
                        throw new IOException(SR.net_io_eof);
                    }
                }
 
                return read;
            }
        }
 
        public override void Write(byte[] buffer, int offset, int count)
        {
            ValidateBufferArguments(buffer, offset, count);
 
            ThrowIfFailed(authSuccessCheck: true);
            if (!CanGetSecureStream)
            {
                InnerStream.Write(buffer, offset, count);
                return;
            }
 
            WriteAsync<SyncReadWriteAdapter>(new ReadOnlyMemory<byte>(buffer, offset, count), default(CancellationToken)).GetAwaiter().GetResult();
        }
 
        /// <returns>A <see cref="Task"/> that represents the asynchronous read operation.</returns>
        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
        {
            ValidateBufferArguments(buffer, offset, count);
 
            ThrowIfFailed(authSuccessCheck: true);
            if (!CanGetSecureStream)
            {
                return InnerStream.WriteAsync(buffer, offset, count, cancellationToken);
            }
 
            return WriteAsync<AsyncReadWriteAdapter>(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken);
        }
 
        /// <returns>A <see cref="ValueTask"/> that represents the asynchronous read operation.</returns>
        public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
        {
            ThrowIfFailed(authSuccessCheck: true);
            if (!CanGetSecureStream)
            {
                return InnerStream.WriteAsync(buffer, cancellationToken);
            }
 
            return new ValueTask(WriteAsync<AsyncReadWriteAdapter>(buffer, cancellationToken));
        }
 
        private async Task WriteAsync<TIOAdapter>(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken)
            where TIOAdapter : IReadWriteAdapter
        {
            Debug.Assert(_context is not null);
            Debug.Assert(_writeBuffer is not null);
 
            if (Interlocked.Exchange(ref _writeInProgress, true))
            {
                throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, "write"));
            }
 
            try
            {
                ThrowIfFailed(authSuccessCheck: true);
 
                while (!buffer.IsEmpty)
                {
                    int chunkBytes = Math.Min(buffer.Length, MaxWriteDataSize);
 
                    bool isEncrypted = _context.IsEncrypted;
                    NegotiateAuthenticationStatusCode statusCode;
                    ReadOnlyMemory<byte> bufferToWrap = buffer.Slice(0, chunkBytes);
 
                    if (isNtlm && !isEncrypted)
                    {
                        // Non-encrypted NTLM uses an encoding quirk
                        _context.ComputeIntegrityCheck(bufferToWrap.Span, _writeBuffer);
                        _writeBuffer.Write(bufferToWrap.Span);
                        statusCode = NegotiateAuthenticationStatusCode.Completed;
                    }
                    else
                    {
                        statusCode = _context.Wrap(bufferToWrap.Span, _writeBuffer, isEncrypted, out _);
                    }
 
                    if (statusCode != NegotiateAuthenticationStatusCode.Completed)
                    {
                        // TODO: Trace the error
                        throw new IOException(SR.net_io_encrypt);
                    }
 
                    BinaryPrimitives.WriteInt32LittleEndian(_writeHeader, _writeBuffer.WrittenCount);
                    await TIOAdapter.WriteAsync(InnerStream, _writeHeader, cancellationToken).ConfigureAwait(false);
 
                    await TIOAdapter.WriteAsync(InnerStream, _writeBuffer.WrittenMemory, cancellationToken).ConfigureAwait(false);
                    buffer = buffer.Slice(chunkBytes);
                    _writeBuffer.Clear();
                }
            }
            catch (Exception e) when (!(e is IOException || e is OperationCanceledException))
            {
                throw new IOException(SR.net_io_write, e);
            }
            finally
            {
                _writeBuffer.Clear();
                _writeInProgress = false;
            }
        }
 
        public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? asyncCallback, object? asyncState) =>
            TaskToAsyncResult.Begin(ReadAsync(buffer, offset, count), asyncCallback, asyncState);
 
        public override int EndRead(IAsyncResult asyncResult) =>
            TaskToAsyncResult.End<int>(asyncResult);
 
        public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? asyncCallback, object? asyncState) =>
            TaskToAsyncResult.Begin(WriteAsync(buffer, offset, count), asyncCallback, asyncState);
 
        public override void EndWrite(IAsyncResult asyncResult) =>
            TaskToAsyncResult.End(asyncResult);
 
        private void ThrowIfExceptional()
        {
            ExceptionDispatchInfo? e = _exception;
            if (e != null)
            {
                ThrowExceptional(e);
            }
 
            // Local function to make the check method more inline friendly.
            void ThrowExceptional(ExceptionDispatchInfo e)
            {
                // If the stored exception just indicates disposal, throw a new ODE rather than the stored one,
                // so as to not continually build onto the shared exception's stack.
                ObjectDisposedException.ThrowIf(ReferenceEquals(e, s_disposedSentinel), this);
 
                // Throw the stored exception.
                e.Throw();
            }
        }
 
        private void ValidateCreateContext(
            string package,
            NetworkCredential credential,
            string servicePrincipalName,
            ExtendedProtectionPolicy? policy,
            ProtectionLevel protectionLevel,
            TokenImpersonationLevel impersonationLevel)
        {
            if (policy != null)
            {
                // One of these must be set if EP is turned on
                if (policy.CustomChannelBinding == null && policy.CustomServiceNames == null)
                {
                    throw new ArgumentException(SR.net_auth_must_specify_extended_protection_scheme, nameof(policy));
                }
 
                _extendedProtectionPolicy = policy;
            }
            else
            {
                _extendedProtectionPolicy = new ExtendedProtectionPolicy(PolicyEnforcement.Never);
            }
 
            ValidateCreateContext(package, isServer: true, credential, servicePrincipalName, _extendedProtectionPolicy.CustomChannelBinding, protectionLevel, impersonationLevel);
        }
 
        private void ValidateCreateContext(
            string package,
            bool isServer,
            NetworkCredential credential,
            string? servicePrincipalName,
            ChannelBinding? channelBinding,
            ProtectionLevel protectionLevel,
            TokenImpersonationLevel impersonationLevel)
        {
            if (!_canRetryAuthentication)
            {
                ThrowIfExceptional();
            }
 
            if (_context != null)
            {
                throw new InvalidOperationException(SR.net_auth_reauth);
            }
 
            ArgumentNullException.ThrowIfNull(credential);
            ArgumentNullException.ThrowIfNull(servicePrincipalName);
 
            if (impersonationLevel != TokenImpersonationLevel.Identification &&
                impersonationLevel != TokenImpersonationLevel.Impersonation &&
                impersonationLevel != TokenImpersonationLevel.Delegation)
            {
                throw new ArgumentOutOfRangeException(nameof(impersonationLevel), impersonationLevel.ToString(), SR.net_auth_supported_impl_levels);
            }
 
            if (_context is not null && IsServer != isServer)
            {
                throw new InvalidOperationException(SR.net_auth_client_server);
            }
 
            _exception = null;
            _remoteOk = false;
            _framer = new StreamFramer();
            _framer.WriteHeader.MessageId = FrameHeader.HandshakeId;
 
            _canRetryAuthentication = false;
 
            // A workaround for the client when talking to Win9x on the server side.
            if (protectionLevel == ProtectionLevel.None && !isServer)
            {
                package = NegotiationInfoClass.NTLM;
            }
 
            if (isServer)
            {
                _expectedProtectionLevel = protectionLevel;
                _expectedImpersonationLevel = impersonationLevel;
                _context = new NegotiateAuthentication(
                    new NegotiateAuthenticationServerOptions
                    {
                        Package = package,
                        Credential = credential,
                        Binding = channelBinding,
                        RequiredProtectionLevel = protectionLevel,
                        RequiredImpersonationLevel = impersonationLevel,
                        Policy = _extendedProtectionPolicy,
                    });
            }
            else
            {
                _expectedProtectionLevel = protectionLevel;
                _expectedImpersonationLevel = TokenImpersonationLevel.None;
                _context = new NegotiateAuthentication(
                    new NegotiateAuthenticationClientOptions
                    {
                        Package = package,
                        Credential = credential,
                        TargetName = servicePrincipalName,
                        Binding = channelBinding,
                        RequiredProtectionLevel = protectionLevel,
                        AllowedImpersonationLevel = impersonationLevel,
                        RequireMutualAuthentication = protectionLevel != ProtectionLevel.None
                    });
            }
        }
 
        private void SetFailed(Exception e)
        {
            if (_exception == null || !(_exception.SourceException is ObjectDisposedException))
            {
                _exception = ExceptionDispatchInfo.Capture(e);
            }
 
            _context?.Dispose();
        }
 
        private void ThrowIfFailed(bool authSuccessCheck)
        {
            ThrowIfExceptional();
 
            if (authSuccessCheck && !IsAuthenticatedCore)
            {
                throw new InvalidOperationException(SR.net_auth_noauth);
            }
        }
 
        private async Task AuthenticateAsync<TIOAdapter>(CancellationToken cancellationToken)
            where TIOAdapter : IReadWriteAdapter
        {
            Debug.Assert(_context != null);
 
            ThrowIfFailed(authSuccessCheck: false);
            if (Interlocked.Exchange(ref _authInProgress, true))
            {
                throw new InvalidOperationException(SR.Format(SR.net_io_invalidnestedcall, "authenticate"));
            }
 
            try
            {
                await (_context.IsServer ?
                    ReceiveBlobAsync<TIOAdapter>(cancellationToken) : // server should listen for a client blob
                    SendBlobAsync<TIOAdapter>(message: null, cancellationToken)).ConfigureAwait(false); // client should send the first blob
            }
            catch (Exception e)
            {
                SetFailed(e);
                throw;
            }
            finally
            {
                _authInProgress = false;
            }
        }
 
        // Client authentication starts here, but server also loops through this method.
        private async Task SendBlobAsync<TIOAdapter>(byte[]? message, CancellationToken cancellationToken)
            where TIOAdapter : IReadWriteAdapter
        {
            Debug.Assert(_context != null);
 
            NegotiateAuthenticationStatusCode statusCode = NegotiateAuthenticationStatusCode.Completed;
            if (message != s_emptyMessage)
            {
                message = _context.GetOutgoingBlob(message, out statusCode);
            }
 
            if (statusCode is NegotiateAuthenticationStatusCode.BadBinding or
                NegotiateAuthenticationStatusCode.TargetUnknown or
                NegotiateAuthenticationStatusCode.ImpersonationValidationFailed or
                NegotiateAuthenticationStatusCode.SecurityQosFailed)
            {
                Exception exception = statusCode switch
                {
                    NegotiateAuthenticationStatusCode.BadBinding =>
                        new AuthenticationException(SR.net_auth_bad_client_creds_or_target_mismatch),
                    NegotiateAuthenticationStatusCode.TargetUnknown =>
                        new AuthenticationException(SR.net_auth_bad_client_creds_or_target_mismatch),
                    NegotiateAuthenticationStatusCode.ImpersonationValidationFailed =>
                        new AuthenticationException(SR.Format(SR.net_auth_context_expectation, _expectedImpersonationLevel.ToString(), PrivateImpersonationLevel.ToString())),
                    _ => // NegotiateAuthenticationStatusCode.SecurityQosFailed
                        new AuthenticationException(SR.Format(SR.net_auth_context_expectation, _context.ProtectionLevel.ToString(), _expectedProtectionLevel.ToString())),
                };
 
                message = new byte[sizeof(long)];
                BinaryPrimitives.WriteInt64LittleEndian(message, ERROR_TRUST_FAILURE);
 
                await SendAuthResetSignalAndThrowAsync<TIOAdapter>(message, exception, cancellationToken).ConfigureAwait(false);
                Debug.Fail("Unreachable");
            }
            else if (statusCode == NegotiateAuthenticationStatusCode.Completed)
            {
                _writeBuffer = new ArrayBufferWriter<byte>();
 
                isNtlm = string.Equals(_context.Package, NegotiationInfoClass.NTLM);
 
                // Signal remote party that we are done
                _framer!.WriteHeader.MessageId = FrameHeader.HandshakeDoneId;
                if (_context.IsServer)
                {
                    // Server may complete now because client SSPI would not complain at this point.
                    _remoteOk = true;
 
                    // However the client will wait for server to send this ACK
                    // Force signaling server OK to the client
                    message ??= s_emptyMessage;
                }
 
                if (message != null)
                {
                    //even if we are completed, there could be a blob for sending.
                    await _framer!.WriteMessageAsync<TIOAdapter>(InnerStream, message, cancellationToken).ConfigureAwait(false);
                }
 
                if (_remoteOk)
                {
                    // We are done with success.
                    return;
                }
            }
            else if (statusCode != NegotiateAuthenticationStatusCode.ContinueNeeded)
            {
                int errorCode = statusCode switch
                {
                    NegotiateAuthenticationStatusCode.BadBinding => (int)Interop.SECURITY_STATUS.BadBinding,
                    NegotiateAuthenticationStatusCode.Unsupported => (int)Interop.SECURITY_STATUS.Unsupported,
                    NegotiateAuthenticationStatusCode.MessageAltered => (int)Interop.SECURITY_STATUS.MessageAltered,
                    NegotiateAuthenticationStatusCode.ContextExpired => (int)Interop.SECURITY_STATUS.ContextExpired,
                    NegotiateAuthenticationStatusCode.CredentialsExpired => (int)Interop.SECURITY_STATUS.CertExpired,
                    NegotiateAuthenticationStatusCode.InvalidCredentials => (int)Interop.SECURITY_STATUS.LogonDenied,
                    NegotiateAuthenticationStatusCode.InvalidToken => (int)Interop.SECURITY_STATUS.InvalidToken,
                    NegotiateAuthenticationStatusCode.UnknownCredentials => (int)Interop.SECURITY_STATUS.UnknownCredentials,
                    NegotiateAuthenticationStatusCode.QopNotSupported => (int)Interop.SECURITY_STATUS.QopNotSupported,
                    NegotiateAuthenticationStatusCode.OutOfSequence => (int)Interop.SECURITY_STATUS.OutOfSequence,
                    _ => (int)Interop.SECURITY_STATUS.InternalError
                };
                Win32Exception win32Exception = new Win32Exception(errorCode);
                Exception exception = statusCode switch
                {
                    NegotiateAuthenticationStatusCode.InvalidCredentials =>
                        new InvalidCredentialException(IsServer ? SR.net_auth_bad_client_creds : SR.net_auth_bad_client_creds_or_target_mismatch, win32Exception),
                    _ => new AuthenticationException(SR.net_auth_SSPI, win32Exception)
                };
 
                message = new byte[sizeof(long)];
                BinaryPrimitives.WriteInt64LittleEndian(message, errorCode);
 
                // Signal remote side on a failed attempt.
                await SendAuthResetSignalAndThrowAsync<TIOAdapter>(message!, exception, cancellationToken).ConfigureAwait(false);
                Debug.Fail("Unreachable");
            }
            else
            {
                if (message == null || message == s_emptyMessage)
                {
                    throw new InternalException();
                }
 
                await _framer!.WriteMessageAsync<TIOAdapter>(InnerStream, message, cancellationToken).ConfigureAwait(false);
            }
 
            await ReceiveBlobAsync<TIOAdapter>(cancellationToken).ConfigureAwait(false);
        }
 
        // Server authentication starts here, but client also loops through this method.
        private async Task ReceiveBlobAsync<TIOAdapter>(CancellationToken cancellationToken)
            where TIOAdapter : IReadWriteAdapter
        {
            Debug.Assert(_framer != null);
 
            byte[]? message = await _framer.ReadMessageAsync<TIOAdapter>(InnerStream, cancellationToken).ConfigureAwait(false);
            if (message == null)
            {
                // This is an EOF otherwise we would get at least *empty* message but not a null one.
                throw new AuthenticationException(SR.net_auth_eof);
            }
 
            // Process Header information.
            if (_framer.ReadHeader.MessageId == FrameHeader.HandshakeErrId)
            {
                if (message.Length >= sizeof(long))
                {
                    // Try to recover remote win32 Exception.
                    long error = BinaryPrimitives.ReadInt64LittleEndian(message);
                    ThrowCredentialException(error);
                }
 
                throw new AuthenticationException(SR.net_auth_alert);
            }
 
            if (_framer.ReadHeader.MessageId == FrameHeader.HandshakeDoneId)
            {
                if (HandshakeComplete && message.Length > 0)
                {
                    Debug.Assert(_context != null);
                    _context.GetOutgoingBlob(message, out NegotiateAuthenticationStatusCode statusCode);
                    _remoteOk = statusCode is NegotiateAuthenticationStatusCode.Completed;
                }
                else
                {
                    _remoteOk = true;
                }
            }
            else if (_framer.ReadHeader.MessageId != FrameHeader.HandshakeId)
            {
                throw new AuthenticationException(SR.Format(SR.net_io_header_id, nameof(FrameHeader.MessageId), _framer.ReadHeader.MessageId, FrameHeader.HandshakeId));
            }
 
            // If we are done don't go into send.
            if (HandshakeComplete)
            {
                if (!_remoteOk)
                {
                    throw new AuthenticationException(SR.Format(SR.net_io_header_id, nameof(FrameHeader.MessageId), _framer.ReadHeader.MessageId, FrameHeader.HandshakeDoneId));
                }
 
                return;
            }
 
            // Not yet done, get a new blob and send it if any.
            await SendBlobAsync<TIOAdapter>(message, cancellationToken).ConfigureAwait(false);
        }
 
        //  This is to reset auth state on the remote side.
        //  If this write succeeds we will allow auth retrying.
        private async Task SendAuthResetSignalAndThrowAsync<TIOAdapter>(byte[] message, Exception exception, CancellationToken cancellationToken)
            where TIOAdapter : IReadWriteAdapter
        {
            _framer!.WriteHeader.MessageId = FrameHeader.HandshakeErrId;
 
            await _framer.WriteMessageAsync<TIOAdapter>(InnerStream, message, cancellationToken).ConfigureAwait(false);
 
            _canRetryAuthentication = true;
            ExceptionDispatchInfo.Throw(exception);
        }
 
        private static void ThrowCredentialException(long error)
        {
            var e = new Win32Exception((int)error);
            throw e.NativeErrorCode switch
            {
                // Compatibility quirk: .NET Core and .NET 5/6 incorrectly report internal status code instead of Win32 error number
                (int)SecurityStatusPalErrorCode.LogonDenied => new InvalidCredentialException(SR.net_auth_bad_client_creds, e),
                (int)Interop.SECURITY_STATUS.LogonDenied => new InvalidCredentialException(SR.net_auth_bad_client_creds, e),
                ERROR_TRUST_FAILURE => new AuthenticationException(SR.net_auth_context_expectation_remote, e),
                _ => new AuthenticationException(SR.net_auth_alert, e)
            };
        }
    }
}