File: net\System\Net\SecureProtocols\_NegoState.cs
Project: ndp\fx\src\System.csproj (System)
/*++
Copyright (c) Microsoft Corporation
 
Module Name:
 
    _NegoState.cs
 
Abstract:
        The internal class is used by Negotiate Client&Server and by (internal) NegoStream.
        It encapsulates security context and does the real work in authentication and
        user data encryption with NEGO SSPI package.
 
Author:
    Alexei Vopilov    12-Aug-2003
 
Revision History:
    12-Aug-2003 New design that has obsoleted Authenticator class
 
--*/
 
namespace System.Net.Security {
    using System;
    using System.Net;
    using System.IO;
    using System.Security;
    using System.Security.Principal;
    using System.Threading;
    using System.ComponentModel;
    using System.Security.Authentication;
    using System.Security.Authentication.ExtendedProtection;
 
    //
    // The class maintains the state of the authentication process and the security context.
    // On the success it returns the remote side identites created as the result
    // of authentication.
    //
    internal class NegoState {
 
        private const int ERROR_TRUST_FAILURE = 1790;   //used to serialize protectionLevel or impersonationLevel mismatch error to the remote side
 
        private static readonly byte[]  _EmptyMessage = new byte[0];
        private static readonly AsyncCallback   _ReadCallback   = new AsyncCallback(ReadCallback);
        private static readonly AsyncCallback   _WriteCallback  = new AsyncCallback(WriteCallback);
 
        private Stream              _InnerStream;
        private bool                _LeaveStreamOpen;
 
        private Exception           _Exception;
 
        private StreamFramer        _Framer;
        private NTAuthentication    _Context;
 
        private int                 _NestedAuth;
 
        internal const int          c_MaxReadFrameSize   = 64*1024;
        internal const int          c_MaxWriteDataSize   = 63*1024; //we give 1k for the framing and trailer that is laways less as per SSPI.
 
        private  bool                       _CanRetryAuthentication;
        private  ProtectionLevel            _ExpectedProtectionLevel;
        private  TokenImpersonationLevel    _ExpectedImpersonationLevel;
        private  uint                       _WriteSequenceNumber;
        private  uint                       _ReadSequenceNumber;
 
        private ExtendedProtectionPolicy    _ExtendedProtectionPolicy;
 
        // SSPI does not send a server ack on successfull auth.
        // So, this is a state variable used to gracefully handle auth confirmation
        private bool _RemoteOk = false;
 
        //
        //
        //
        internal NegoState(Stream innerStream, bool leaveStreamOpen) {
            if (innerStream==null) {
                throw new ArgumentNullException("stream");
            }
            _InnerStream = innerStream;
            _LeaveStreamOpen = leaveStreamOpen;
        }
        //
        internal static string DefaultPackage {
            get {
                return NegotiationInfoClass.Negotiate;
            }
        }
        //
        //
        //
        internal void ValidateCreateContext(string package,
                                            NetworkCredential credential,
                                            string servicePrincipalName,
                                            ExtendedProtectionPolicy policy,
                                            ProtectionLevel protectionLevel,
                                            TokenImpersonationLevel impersonationLevel)
        {
            if (policy != null)
            {
                if (!AuthenticationManager.OSSupportsExtendedProtection)
                {
                    if (policy.PolicyEnforcement == PolicyEnforcement.Always)
                    {
                        throw new PlatformNotSupportedException(SR.GetString(SR.security_ExtendedProtection_NoOSSupport));
                    }
                }
                else
                {
                    // One of these must be set if EP is turned on
                    if (policy.CustomChannelBinding == null && policy.CustomServiceNames == null)
                        throw new ArgumentException(SR.GetString(SR.net_auth_must_specify_extended_protection_scheme), "policy");
                }
 
                _ExtendedProtectionPolicy = policy;
            }
            else
            {
                _ExtendedProtectionPolicy = new ExtendedProtectionPolicy(PolicyEnforcement.Never);
            }
 
            ValidateCreateContext(package, true, credential, servicePrincipalName, _ExtendedProtectionPolicy.CustomChannelBinding, protectionLevel, impersonationLevel);
        }
        //
        internal void ValidateCreateContext(
                                            string package,
                                            bool isServer,
                                            NetworkCredential credential,
                                            string servicePrincipalName,
                                            ChannelBinding channelBinding,
                                            ProtectionLevel         protectionLevel,
                                            TokenImpersonationLevel impersonationLevel
                                            )
        {
 
            if (_Exception != null && !_CanRetryAuthentication) {
                throw _Exception;
            }
 
            if (_Context != null && _Context.IsValidContext) {
                throw new InvalidOperationException(SR.GetString(SR.net_auth_reauth));
            }
 
            if (credential == null) {
                throw new ArgumentNullException("credential");
            }
 
            if (servicePrincipalName == null) {
                throw new ArgumentNullException("servicePrincipalName");
            }
 
            if (impersonationLevel != TokenImpersonationLevel.Identification &&
                impersonationLevel != TokenImpersonationLevel.Impersonation &&
                impersonationLevel != TokenImpersonationLevel.Delegation)
            {
                throw new ArgumentOutOfRangeException("impersonationLevel", impersonationLevel.ToString(), SR.GetString(SR.net_auth_supported_impl_levels));
            }
 
            if (_Context != null && IsServer != isServer) {
                throw new InvalidOperationException(SR.GetString(SR.net_auth_client_server));
            }
 
            _Exception = null;
            _RemoteOk = false;
            _Framer = new StreamFramer(_InnerStream);
            _Framer.WriteHeader.MessageId = FrameHeader.HandshakeId;
 
            _ExpectedProtectionLevel    = protectionLevel;
            _ExpectedImpersonationLevel = isServer? impersonationLevel: TokenImpersonationLevel.None;
            _WriteSequenceNumber        = 0;
            _ReadSequenceNumber         = 0;
 
            ContextFlags flags = ContextFlags.Connection;
 
            // A workaround for the client when talking to Win9x on the server side
            if (protectionLevel == ProtectionLevel.None && !isServer)
            {
                package = NegotiationInfoClass.NTLM;
            }
 
            else if (protectionLevel == ProtectionLevel.EncryptAndSign)
            {
                flags |= ContextFlags.Confidentiality;
            }
            else if (protectionLevel == ProtectionLevel.Sign)
            {
                // Assuming user expects NT4 SP4 and above
                flags |= ContextFlags.ReplayDetect | ContextFlags.SequenceDetect | ContextFlags.InitIntegrity;
            }
 
            if (isServer)
            {
                if (_ExtendedProtectionPolicy.PolicyEnforcement == PolicyEnforcement.WhenSupported) { flags |= ContextFlags.AllowMissingBindings; }
                if (_ExtendedProtectionPolicy.PolicyEnforcement != PolicyEnforcement.Never &&
                    _ExtendedProtectionPolicy.ProtectionScenario == ProtectionScenario.TrustedProxy)
                {
                    flags |= ContextFlags.ProxyBindings;
                }
            }
            else
            {
                // According to lzhu server side should not request any of these flags
                if (protectionLevel != ProtectionLevel.None)                        {flags |= ContextFlags.MutualAuth;}
                if (impersonationLevel == TokenImpersonationLevel.Identification)   {flags |= ContextFlags.InitIdentify;}
                if (impersonationLevel == TokenImpersonationLevel.Delegation)       {flags |= ContextFlags.Delegate;}
            }
            
 
            _CanRetryAuthentication = false;
 
            //
            // Security: We used to rely on NetworkCredential class to demand permission
            //           Switched over to explicit ControlPrincipalPermission demand (except for DefaultCredential case)
            //           The mitigated attack is brute-force pasword guessing through SSPI.
            if (!(credential is SystemNetworkCredential))
                ExceptionHelper.ControlPrincipalPermission.Demand();
 
            try {
                //
                _Context = new NTAuthentication(isServer, package, credential, servicePrincipalName, flags, channelBinding);
            }
            catch (Win32Exception e)
            {
                throw new AuthenticationException(SR.GetString(SR.net_auth_SSPI), e);
            }
        }
 
        //
        //
        private Exception SetException(Exception e)
        {
            if (_Exception == null || !(_Exception is ObjectDisposedException))
            {
                _Exception = e;
            }
            if (_Exception != null && _Context != null) {
                _Context.CloseContext();
            }
            return _Exception;
        }
 
        //
        // General informational properties
        //
 
        //
        //
        internal bool IsAuthenticated {
            get {
                return _Context != null && HandshakeComplete && _Exception == null && _RemoteOk;
            }
        }
        //
        //
        //
        internal  bool IsMutuallyAuthenticated {
            get {
                if (!IsAuthenticated)
                    return false;
 
                // Suppressing for NTLM since SSPI does not return correct value int the context flags.
                if (_Context.IsNTLM)
                    return false;
 
                return _Context.IsMutualAuthFlag;
            }
        }
        //
        internal  bool IsEncrypted {
            get {
                return IsAuthenticated && _Context.IsConfidentialityFlag;
            }
        }
        //
        internal  bool IsSigned {
            get {
                return IsAuthenticated && (_Context.IsIntegrityFlag || _Context.IsConfidentialityFlag);
            }
        }
        //
        internal bool IsServer {
            get {
                return _Context != null && _Context.IsServer;
            }
        }
        //
        // NEGO specific informational properties
        //
 
        internal bool CanGetSecureStream {
            get {
                return (_Context.IsConfidentialityFlag || _Context.IsIntegrityFlag);
            }
        }
        //
        //
        //
        internal TokenImpersonationLevel AllowedImpersonation {
            get {
                CheckThrow(true);
                return PrivateImpersonationLevel;
            }
        }
        //
        private TokenImpersonationLevel PrivateImpersonationLevel {
            get {
                // according to lzhu we should suppress dlegate flag in NTLM case
                return  (_Context.IsDelegationFlag && _Context.ProtocolName != NegotiationInfoClass.NTLM) ? TokenImpersonationLevel.Delegation
                        :_Context.IsIdentifyFlag?   TokenImpersonationLevel.Identification
                        :TokenImpersonationLevel.Impersonation;
            }
        }
        //
        //
        //
        private bool HandshakeComplete {
            get {
                return _Context.IsCompleted && _Context.IsValidContext;
            }
        }
        //
        // Note that method will demand PrincipalControlPermission
        // which essentially means demanding full trust
        //
        internal IIdentity GetIdentity() {
 
            CheckThrow(true);
 
            IIdentity result = null;
            string name = _Context.IsServer? _Context.AssociatedName: _Context.Spn;
            string protocol = "NTLM";
 
            protocol = _Context.ProtocolName;
 
            if (_Context.IsServer) {
                SafeCloseHandle token = null;
                try {
                    token = _Context.GetContextToken();
                    string authtype = _Context.ProtocolName;
                    result = new WindowsIdentity(token.DangerousGetHandle(), authtype, WindowsAccountType.Normal, true);
                    return result;
                }
                catch (SecurityException) {
                    //ignore and construct generic Identity if failed due to security problem
                }
                finally {
                    if (token != null) {
                        token.Close();
                    }
                }
            }
            // on the client we don't have access to the remote side identity.
            result = new GenericIdentity(name, protocol);
            return result;
        }
 
        //
        // Methods
        //
 
        //
        //
        internal void CheckThrow(bool authSucessCheck) {
            if (_Exception != null) {
                throw _Exception;
            }
            if (authSucessCheck && !IsAuthenticated) {
                throw new InvalidOperationException(SR.GetString(SR.net_auth_noauth));
            }
        }
        //
        // This is to not depend on GC&SafeHandle class if the context is not needed anymore.
        //
        internal void Close() {
            // Mark this instance as disposed
            _Exception = new ObjectDisposedException("NegotiateStream");
            if (_Context != null) {
                _Context.CloseContext();
            }
        }
        //
        //
        //
        internal void ProcessAuthentication(LazyAsyncResult lazyResult)
        {
            CheckThrow(false);
            if (Interlocked.Exchange(ref _NestedAuth, 1) == 1) {
                throw new InvalidOperationException(SR.GetString(SR.net_io_invalidnestedcall, lazyResult==null?"BeginAuthenticate":"Authenticate", "authenticate"));
            }
 
            try {
                if (_Context.IsServer)
                {
                    // Listen for a client blob
                    StartReceiveBlob(lazyResult);
                }
                else
                {
                    // we start with the first blob
                    StartSendBlob(null, lazyResult);
                }
            }
            catch (Exception e)
            {
                // Roundtrip it through the SetException()
                e = SetException(e);
                throw;
            }
            finally
            {
                if (lazyResult == null || _Exception != null) {
                    _NestedAuth = 0;
                }
            }
        }
        //
        //
        //
        internal void EndProcessAuthentication(IAsyncResult result)
        {
            if (result == null)
            {
                throw new ArgumentNullException("asyncResult");
            }
 
            LazyAsyncResult lazyResult = result as LazyAsyncResult;
            if (lazyResult == null)
            {
                throw new ArgumentException(SR.GetString(SR.net_io_async_result, result.GetType().FullName), "asyncResult");
            }
 
            if (Interlocked.Exchange(ref _NestedAuth, 0) == 0)
            {
                throw new InvalidOperationException(SR.GetString(SR.net_io_invalidendcall, "EndAuthenticate"));
            }
 
            // No "artificial" timeouts implemented so far, InnerStream controls that.
            lazyResult.InternalWaitForCompletion();
 
            Exception e = lazyResult.Result as Exception;
 
            if (e != null)
            {
                // Roundtrip it through the SetException()
                e = SetException(e);
                throw e;
            }
 
        }
 
        private bool CheckSpn()
        {
            if (_Context.IsKerberos)
            {
                return true;
            }
 
            if (_ExtendedProtectionPolicy.PolicyEnforcement == PolicyEnforcement.Never ||
                    _ExtendedProtectionPolicy.CustomServiceNames == null)
            {
                return true;
            }
 
            if (!AuthenticationManager.OSSupportsExtendedProtection)
            {
                GlobalLog.Assert(_ExtendedProtectionPolicy.PolicyEnforcement != PolicyEnforcement.Always, 
                    "User managed to set PolicyEnforcement.Always when the OS does not support extended protection!");
                return true;
            }
 
            string clientSpn = _Context.ClientSpecifiedSpn;
 
            if (String.IsNullOrEmpty(clientSpn))
            {
                if (_ExtendedProtectionPolicy.PolicyEnforcement == PolicyEnforcement.WhenSupported)
                {
                    return true;
                }
            }
            else
            {
                return _ExtendedProtectionPolicy.CustomServiceNames.Contains(clientSpn);
            }
 
            return false;
        }
 
        //
        // Client side starts here, but server also loops through this method
        //
        private void StartSendBlob(byte[] message, LazyAsyncResult lazyResult)
        {
            Win32Exception win32exception = null;
            if (message != _EmptyMessage)
            {
                message = GetOutgoingBlob(message, ref win32exception);
            }
 
            if (win32exception != null)
            {
                // signal remote side on a failed attempt
                StartSendAuthResetSignal(lazyResult, message, win32exception);
                return;
            }
 
            if (HandshakeComplete)
            {
                if (_Context.IsServer && !CheckSpn())
                {
                    Exception exception = new AuthenticationException(SR.GetString(SR.net_auth_bad_client_creds_or_target_mismatch));
                    int statusCode = ERROR_TRUST_FAILURE;
                    message = new byte[8];  //sizeof(long)
 
                    for (int i = message.Length - 1; i >= 0; --i)
                    {
                        message[i] = (byte)(statusCode & 0xFF);
                        statusCode = (int)((uint)statusCode >> 8);
                    }
 
                    StartSendAuthResetSignal(lazyResult, message, exception);
                    return;
                }
 
                if (PrivateImpersonationLevel < _ExpectedImpersonationLevel)
                {
                    Exception exception = new AuthenticationException(SR.GetString(SR.net_auth_context_expectation, _ExpectedImpersonationLevel.ToString(), PrivateImpersonationLevel.ToString()));
                    int statusCode = ERROR_TRUST_FAILURE;
                    message = new byte[8];  //sizeof(long)
 
                    for (int i = message.Length-1; i >= 0; --i)
                    {
                        message[i] = (byte)(statusCode & 0xFF);
                        statusCode = (int) ((uint) statusCode >> 8);
                    }
 
                    StartSendAuthResetSignal(lazyResult, message, exception);
                    return;
                }
 
                ProtectionLevel result = _Context.IsConfidentialityFlag? ProtectionLevel.EncryptAndSign: _Context.IsIntegrityFlag? ProtectionLevel.Sign: ProtectionLevel.None;
 
                if (result < _ExpectedProtectionLevel)
                {
                    Exception exception = new AuthenticationException(SR.GetString(SR.net_auth_context_expectation, result.ToString(), _ExpectedProtectionLevel.ToString()));
                    int statusCode = ERROR_TRUST_FAILURE;
                    message = new byte[8];  //sizeof(long)
 
                    for (int i = message.Length-1; i >= 0; --i)
                    {
                        message[i] = (byte)(statusCode & 0xFF);
                        statusCode = (int) ((uint) statusCode >> 8);
                    }
 
                    StartSendAuthResetSignal(lazyResult, message, exception);
                    return;
                }
 
                // 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 signalling server OK to the client
                    if (message == null)
                    {
                        message = _EmptyMessage;
                    }
                }
            }
            else if (message == null || message == _EmptyMessage) {
                throw new InternalException();
            }
 
            if (message != null)
            {
                //even if we are comleted, there could be a blob for sending.
                if (lazyResult == null)
                {
                    _Framer.WriteMessage(message);
                }
                else
                {
                    IAsyncResult ar = _Framer.BeginWriteMessage(message, _WriteCallback, lazyResult);
                    if (!ar.CompletedSynchronously)
                    {
                        return;
                    }
                    _Framer.EndWriteMessage(ar);
                }
            }
            CheckCompletionBeforeNextReceive(lazyResult);
        }
        //
        // This will check and logically complete the auth handshake
        //
        private void CheckCompletionBeforeNextReceive(LazyAsyncResult lazyResult)
        {
            if (HandshakeComplete && _RemoteOk)
            {
                //we are done with success
                if (lazyResult != null)
                {
                    lazyResult.InvokeCallback();
                }
                return;
            }
            StartReceiveBlob(lazyResult);
        }
        //
        // Server side starts here, but client also loops through this method
        //
        private void StartReceiveBlob(LazyAsyncResult lazyResult)
        {
            byte[] message;
            if (lazyResult == null)
            {
                message = _Framer.ReadMessage();
            }
            else
            {
                IAsyncResult ar = _Framer.BeginReadMessage(_ReadCallback, lazyResult);
                if (!ar.CompletedSynchronously)
                {
                    return;
                }
                message = _Framer.EndReadMessage(ar);
            }
            ProcessReceivedBlob(message, lazyResult);
        }
        //
        //
        //
        private void ProcessReceivedBlob(byte[] message, LazyAsyncResult lazyResult)
        {
            // This is an EOF otherwise we would get at least *empty* message but not a null one.
            if (message == null)
            {
                throw new AuthenticationException(SR.GetString(SR.net_auth_eof), null);
            }
 
            //Process Header information
            if (_Framer.ReadHeader.MessageId == FrameHeader.HandshakeErrId)
            {
                Win32Exception e = null;
                if (message.Length >= 8)    // sizeof(long)
                {
                    // Try to recover remote win32 Exception
                    long error = 0;
                    for (int i = 0; i < 8; ++i)
                        error = (error<<8) + message[i];
                    e = new Win32Exception((int)error);
                }
                if (e != null)
                {
                     if (e.NativeErrorCode == (int)SecurityStatus.LogonDenied)
                        throw new InvalidCredentialException(SR.GetString(SR.net_auth_bad_client_creds), e);
 
                     if (e.NativeErrorCode == ERROR_TRUST_FAILURE)
                         throw new AuthenticationException(SR.GetString(SR.net_auth_context_expectation_remote), e);
                 }
 
                throw new AuthenticationException(SR.GetString(SR.net_auth_alert), e);
            }
 
            if (_Framer.ReadHeader.MessageId == FrameHeader.HandshakeDoneId)
            {
                _RemoteOk = true;
            }
            else if (_Framer.ReadHeader.MessageId != FrameHeader.HandshakeId)
            {
                throw new AuthenticationException(SR.GetString(SR.net_io_header_id, "MessageId", _Framer.ReadHeader.MessageId, FrameHeader.HandshakeId), null);
            }
            CheckCompletionBeforeNextSend(message, lazyResult);
        }
        //
        // This will check and logically complete the auth handshake
        //
        private void CheckCompletionBeforeNextSend(byte[] message, LazyAsyncResult lazyResult)
        {
            //If we are done don't go into send
            if (HandshakeComplete)
            {
                if (!_RemoteOk)
                {
                    throw new AuthenticationException(SR.GetString(SR.net_io_header_id, "MessageId", _Framer.ReadHeader.MessageId, FrameHeader.HandshakeDoneId), null);
                }
                if (lazyResult != null)
                {
                    lazyResult.InvokeCallback();
                }
                return;
            }
 
            // Not yet done, get a new blob and send it if any
            StartSendBlob(message, lazyResult);
        }
        //
        //  This is to reset auth state on remote side.
        //  If this write succeeds we will allow auth retrying.
        //
        private void StartSendAuthResetSignal(LazyAsyncResult lazyResult, byte[] message, Exception exception)
        {
            _Framer.WriteHeader.MessageId = FrameHeader.HandshakeErrId;
 
            Win32Exception win32exception = exception as Win32Exception;
 
            if (win32exception != null && win32exception.NativeErrorCode == (int)SecurityStatus.LogonDenied)
                if (IsServer)
                    exception = new InvalidCredentialException(SR.GetString(SR.net_auth_bad_client_creds), exception);
                else
                    exception = new InvalidCredentialException(SR.GetString(SR.net_auth_bad_client_creds_or_target_mismatch), exception);
 
            if (!(exception is AuthenticationException))
                exception = new AuthenticationException(SR.GetString(SR.net_auth_SSPI), exception);
 
            if (lazyResult == null)
            {
                _Framer.WriteMessage(message);
            }
            else
            {
                lazyResult.Result = exception;
                IAsyncResult ar = _Framer.BeginWriteMessage(message, _WriteCallback, lazyResult);
                if(!ar.CompletedSynchronously)
                {
                    return;
                }
                _Framer.EndWriteMessage(ar);
            }
 
            _CanRetryAuthentication = true;
            throw exception;
        }
        //
        //
        //
        private static void WriteCallback(IAsyncResult transportResult)
        {
            GlobalLog.Assert(transportResult.AsyncState is LazyAsyncResult, "WriteCallback|State type is wrong, expected LazyAsyncResult.");
            if (transportResult.CompletedSynchronously)
            {
                return;
            }
 
            LazyAsyncResult lazyResult = (LazyAsyncResult) transportResult.AsyncState;
 
            // Async completion
            try
            {
                NegoState authState = (NegoState)lazyResult.AsyncObject;
                authState._Framer.EndWriteMessage(transportResult);
 
                //special case for an error notification
                if (lazyResult.Result is Exception)
                {
                    authState._CanRetryAuthentication = true;
                    throw (Exception)lazyResult.Result;
                }
                authState.CheckCompletionBeforeNextReceive(lazyResult);
            }
            catch (Exception e)
            {
                if (lazyResult.InternalPeekCompleted) {
                    // This will throw on a worker thread.
                    throw;
                }
                lazyResult.InvokeCallback(e);
            }
        }
        //
        //
        //
        private static void ReadCallback(IAsyncResult transportResult)
        {
            GlobalLog.Assert(transportResult.AsyncState is LazyAsyncResult, "ReadCallback|State type is wrong, expected LazyAsyncResult.");
            if (transportResult.CompletedSynchronously)
            {
                return;
            }
 
            LazyAsyncResult lazyResult = (LazyAsyncResult) transportResult.AsyncState;
 
            // Async completion
            try
            {
                NegoState authState = (NegoState)lazyResult.AsyncObject;
                byte[] message = authState._Framer.EndReadMessage(transportResult);
                authState.ProcessReceivedBlob(message, lazyResult);
            }
            catch (Exception e)
            {
                if (lazyResult.InternalPeekCompleted) {
                    // This will throw on a worker thread.
                    throw;
                }
 
                lazyResult.InvokeCallback(e);
            }
        }
        //
        //
        //
        private unsafe byte[] GetOutgoingBlob(byte[] incomingBlob, ref Win32Exception e) {
 
            SecurityStatus statusCode;
            byte[] message = _Context.GetOutgoingBlob(incomingBlob, false, out statusCode);
 
            if (((int) statusCode & unchecked((int) 0x80000000)) != 0)
            {
                e = new System.ComponentModel.Win32Exception((int) statusCode);
 
                message = new byte[8];  //sizeof(long)
                for (int i = message.Length-1; i >= 0; --i)
                {
                    message[i] = (byte) ((uint) statusCode & 0xFF);
                    statusCode = (SecurityStatus) ((uint) statusCode >> 8);
                }
            }
 
            if (message != null && message.Length == 0) {
                message = _EmptyMessage;
            }
            return message;
        }
        //
        //
        //
        internal int EncryptData(byte[] buffer, int offset, int count, ref byte[] outBuffer)
        {
            CheckThrow(true);
            //
            // Well, this is to play by the rules but in reality SSPI seems to ignore this sequence number.
            // Means we could simply pass 0
            //
            ++_WriteSequenceNumber;
            return _Context.Encrypt(buffer, offset, count, ref outBuffer, _WriteSequenceNumber);
        }
        //
        //
        //
        internal int DecryptData(byte[] buffer, int offset, int count, out int newOffset)
        {
            CheckThrow(true);
            //
            // Well, this is to play by the rules but in reality SSPI seems to ignore this sequence number.
            // Means we could simply pass 0
            //
            ++_ReadSequenceNumber;
            return _Context.Decrypt(buffer, offset, count, out newOffset, _ReadSequenceNumber);
        }
    }
 
}