_TLSstream.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / whidbey / NetFXspW7 / ndp / fx / src / Net / System / Net / _TLSstream.cs / 2 / _TLSstream.cs

                            //------------------------------------------------------------------------------ 
// 
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// 
//----------------------------------------------------------------------------- 

namespace System.Net { 
    using System.IO; 
    using System.Text;
    using System.Net.Sockets; 
    using System.Threading;
    using System.Security.Cryptography.X509Certificates;
    using System.ComponentModel;
    using System.Collections; 
    using System.Net.Security;
    using System.Globalization; 
    using System.Security.Authentication.ExtendedProtection; 

    internal class TlsStream : NetworkStream, IDisposable { 
        private SslState m_Worker;
        private WebExceptionStatus m_ExceptionStatus;
        private string m_DestinationHost;
        private X509CertificateCollection m_ClientCertificates; 
        private static AsyncCallback _CompleteIOCallback = new AsyncCallback(CompleteIOCallback);
 
        private ExecutionContext _ExecutionContext; 
        private ChannelBinding m_CachedChannelBinding;
 
        //
        // This version of an Ssl Stream is for internal HttpWebrequest use.
        // This Ssl client owns the underlined socket
        // The TlsStream will own secured read/write and disposal of the passed "networkStream" stream. 
        //
        public TlsStream(string destinationHost, NetworkStream networkStream, X509CertificateCollection clientCertificates, ServicePoint servicePoint, object initiatingRequest, ExecutionContext executionContext) 
               :base(networkStream, true) { 

        // WebRequest manages the execution context manually so we have to ensure we get one for SSL client certificate demand 
        _ExecutionContext = executionContext;
        if (_ExecutionContext == null)
        {
            _ExecutionContext = ExecutionContext.Capture(); 
        }
 
        // 

 
         GlobalLog.Enter("TlsStream::TlsStream", "host="+destinationHost+", #certs="+((clientCertificates == null) ? "none" : clientCertificates.Count.ToString(NumberFormatInfo.InvariantInfo)));
         if (Logging.On) Logging.PrintInfo(Logging.Web, this, ".ctor", "host="+destinationHost+", #certs="+((clientCertificates == null) ? "null" : clientCertificates.Count.ToString(NumberFormatInfo.InvariantInfo)));

         m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure; 
         m_Worker = new SslState(networkStream, initiatingRequest is HttpWebRequest);
 
         m_DestinationHost = destinationHost; 
         m_ClientCertificates = clientCertificates;
 
         RemoteCertValidationCallback certValidationCallback = servicePoint.SetupHandshakeDoneProcedure(this, initiatingRequest);
         m_Worker.SetCertValidationDelegate(certValidationCallback);

         // The Handshake is NOT done at this point 
         GlobalLog.Leave("TlsStream::TlsStream (Handshake is not done)");
        } 
 
        //
        // HttpWebRequest as a consumer of this class will ---- any write error, by relying on the read side exception. 
        // We want to keep the right failure status for a user application.
        //
        internal WebExceptionStatus ExceptionStatus {
            get { 
                return m_ExceptionStatus;
            } 
        } 

        // This implements the IDisposable contract from the NetworkStream base class 
        // Note that finalizer on the base class WILL call us unless there was explicit disposal.
        int m_ShutDown = 0;
        protected override void Dispose(bool disposing) {
            GlobalLog.Print("TlsStream::Dispose()"); 
            if ( Interlocked.Exchange( ref m_ShutDown,  1) == 1 ) {
                return; 
            } 
            try {
                if (disposing) { 
                    // When KeepAlive is turned off, the TlsStream will be closed before the auth headers for the next request
                    // are computed.  We cannot retrieve the ChannelBinding from the TlsStream after closing it, so we need to
                    // cache it now.
                    m_CachedChannelBinding = GetChannelBinding(ChannelBindingKind.Endpoint); 

                    // Note this will not close the underlined socket, only security context 
                    m_Worker.Close(); 
                }
                else { 
                    m_Worker = null;
                }
            }
            finally { 
                //This will close the underlined socket
                base.Dispose(disposing); 
            } 
        }
 
        public override bool DataAvailable {
            get {
                 return m_Worker.DataAvailable || base.DataAvailable;
            } 
        }
 
        // 
        // [....] Read version
        // 
        public override int Read(byte[] buffer, int offset, int size) {
            GlobalLog.Print("TlsStream#" + ValidationHelper.HashString(this) + "::Read() SecureWorker#" + ValidationHelper.HashString(m_Worker) + " offset:" + offset.ToString() + " size:" + size.ToString());

            if (!m_Worker.IsAuthenticated) 
                ProcessAuthentication(null);
 
            try { 
                return m_Worker.SecureStream.Read(buffer, offset, size);
 
            }
            catch {
                if (m_Worker.IsCertValidationFailed) {
                    m_ExceptionStatus = WebExceptionStatus.TrustFailure; 
                }
                else if (m_Worker.LastSecurityStatus != SecurityStatus.OK) { 
                    m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure; 
                }
                else { 
                    m_ExceptionStatus = WebExceptionStatus.ReceiveFailure;
                }
                throw;
            } 
        }
        // 
        // Async Read version 
        //
        public override IAsyncResult BeginRead(byte[] buffer, int offset, int size, AsyncCallback asyncCallback, object asyncState) { 
            GlobalLog.Print("TlsStream#" + ValidationHelper.HashString(this) + "::BeginRead() SecureWorker#" + ValidationHelper.HashString(m_Worker) + " offset:" + offset.ToString() + " size:" + size.ToString());

            if (!m_Worker.IsAuthenticated)
            { 
                BufferAsyncResult result = new BufferAsyncResult(this, buffer, offset, size, false, asyncState, asyncCallback);
                if (ProcessAuthentication(result)) 
                    return result; 
            }
 
            try {
                return m_Worker.SecureStream.BeginRead(buffer, offset, size, asyncCallback, asyncState);
            }
            catch { 
                if (m_Worker.IsCertValidationFailed) {
                    m_ExceptionStatus = WebExceptionStatus.TrustFailure; 
                } 
                else if (m_Worker.LastSecurityStatus != SecurityStatus.OK) {
                    m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure; 
                }
                else {
                    m_ExceptionStatus = WebExceptionStatus.ReceiveFailure;
                } 
                throw;
            } 
        } 

        // 
        internal override IAsyncResult UnsafeBeginRead(byte[] buffer, int offset, int size, AsyncCallback asyncCallback, object asyncState)
        {
            return BeginRead(buffer, offset, size, asyncCallback, asyncState);
        } 

        // 
        public override int EndRead(IAsyncResult asyncResult) { 
            GlobalLog.Print("TlsStream#" + ValidationHelper.HashString(this) + "::EndRead() IAsyncResult#" + ValidationHelper.HashString(asyncResult));
            try { 

                BufferAsyncResult bufferResult = asyncResult as BufferAsyncResult;

                if (bufferResult == null || (object)bufferResult.AsyncObject != this) 
                    return m_Worker.SecureStream.EndRead(asyncResult);
 
                // we have wrapped user IO in case when handshake was not done as the Begin call 
                bufferResult.InternalWaitForCompletion();
                Exception e = bufferResult.Result as Exception; 
                if (e != null)
                    throw e;

                return (int) bufferResult.Result; 
            }
            catch { 
                if (m_Worker.IsCertValidationFailed) { 
                    m_ExceptionStatus = WebExceptionStatus.TrustFailure;
                } 
                else if (m_Worker.LastSecurityStatus != SecurityStatus.OK) {
                    m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure;
                }
                else { 
                    m_ExceptionStatus = WebExceptionStatus.ReceiveFailure;
                } 
                throw; 
            }
        } 


        //
        // Write, all flavours: synchrnous and asynchrnous 
        //
        public override void Write(byte[] buffer, int offset, int size) { 
            GlobalLog.Print("TlsStream#" + ValidationHelper.HashString(this) + "::Write() SecureWorker#" + ValidationHelper.HashString(m_Worker) + " offset:" + offset.ToString() + " size:" + size.ToString()); 

            if (!m_Worker.IsAuthenticated) 
                ProcessAuthentication(null);

            try {
                m_Worker.SecureStream.Write(buffer, offset, size); 
            }
            catch { 
                // We preserve the original status of a failure because the read 
                // side will now fail with object dispose error.
                if (m_Worker.IsCertValidationFailed) { 
                    m_ExceptionStatus = WebExceptionStatus.TrustFailure;
                }
                else if (m_Worker.LastSecurityStatus != SecurityStatus.OK) {
                    m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure; 
                }
                else { 
                    m_ExceptionStatus = WebExceptionStatus.SendFailure; 
                }
                //HttpWbeRequest depends on the phyical stream to be dropped on a write error. 
                Socket chkSocket = this.Socket;
                if(chkSocket != null) {
                    chkSocket.InternalShutdown(SocketShutdown.Both);
                } 
                throw;
            } 
        } 

        // 
        // BeginWrite -
        //
        // Write the bytes to the write - while encrypting
        // 
        // copy plain text data to a temporary buffer
        // encrypt the data 
        // once the data is encrypted clear the plain text for security 
        //
        public override IAsyncResult BeginWrite( byte[] buffer, int offset, int size, AsyncCallback asyncCallback, object asyncState) { 
            GlobalLog.Print("TlsStream#" + ValidationHelper.HashString(this) + "::BeginWrite() SecureWorker#" + ValidationHelper.HashString(m_Worker) + " offset:" + offset.ToString() + " size:" + size.ToString());
            if (!m_Worker.IsAuthenticated)
            {
                BufferAsyncResult result = new BufferAsyncResult(this, buffer, offset, size, true, asyncState, asyncCallback); 
                if (ProcessAuthentication(result))
                    return result; 
            } 

            try { 
                return m_Worker.SecureStream.BeginWrite(buffer, offset, size, asyncCallback, asyncState);
            }
            catch {
                if (m_Worker.IsCertValidationFailed) { 
                    m_ExceptionStatus = WebExceptionStatus.TrustFailure;
                } 
                else if (m_Worker.LastSecurityStatus != SecurityStatus.OK) { 
                    m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure;
                } 
                else {
                    m_ExceptionStatus = WebExceptionStatus.SendFailure;
                }
                throw; 
            }
        } 
 

        internal override IAsyncResult UnsafeBeginWrite( byte[] buffer, int offset, int size, AsyncCallback asyncCallback, object asyncState) { 
            return BeginWrite(buffer,offset,size,asyncCallback,asyncState);
        }

 

        public override void EndWrite(IAsyncResult asyncResult) { 
            GlobalLog.Print("TlsStream#" + ValidationHelper.HashString(this) + "::EndWrite() IAsyncResult#" + ValidationHelper.HashString(asyncResult)); 

            try { 
                BufferAsyncResult bufferResult = asyncResult as BufferAsyncResult;

                if (bufferResult == null || (object)bufferResult.AsyncObject != this)
                { 
                    m_Worker.SecureStream.EndWrite(asyncResult);
                } 
                else 
                {
                    // we have wrapped user IO in case when handshake was not done as the Begin call 
                    bufferResult.InternalWaitForCompletion();
                    Exception e = bufferResult.Result as Exception;
                    if (e != null)
                        throw e; 
                }
            } 
            catch { 
                //HttpWebRequest depends on the stream to be dropped on a write error.
                Socket chkSocket = this.Socket; 
                if(chkSocket != null) {
                    chkSocket.InternalShutdown(SocketShutdown.Both);
                }
                // We also preserve the original status of a failure because the read 
                // side will now fail with object dispose error.
                if (m_Worker.IsCertValidationFailed) { 
                    m_ExceptionStatus = WebExceptionStatus.TrustFailure; 
                }
                else if (m_Worker.LastSecurityStatus != SecurityStatus.OK) { 
                    m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure;
                }
                else {
                    m_ExceptionStatus = WebExceptionStatus.SendFailure; 
                }
                throw; 
            } 
        }
 
        internal override void MultipleWrite(BufferOffsetSize[] buffers) {
            GlobalLog.Print("TlsStream#" + ValidationHelper.HashString(this) + "::MultipleWrite() SecureWorker#" + ValidationHelper.HashString(m_Worker) + " buffers.Length:" + buffers.Length.ToString());

            if (!m_Worker.IsAuthenticated) 
                ProcessAuthentication(null);
 
            try { 
                m_Worker.SecureStream.Write(buffers);
            } 
            catch {
                //HttpWbeRequest depends on the physical stream to be dropped on a write error.
                Socket chkSocket = this.Socket;
                if(chkSocket != null) { 
                    chkSocket.InternalShutdown(SocketShutdown.Both);
                } 
                // We preserve the original status of a failure because the read 
                // side will now fail with object dispose error.
                if (m_Worker.IsCertValidationFailed) { 
                    m_ExceptionStatus = WebExceptionStatus.TrustFailure;
                }
                else if (m_Worker.LastSecurityStatus != SecurityStatus.OK) {
                    m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure; 
                }
                else { 
                    m_ExceptionStatus = WebExceptionStatus.SendFailure; 
                }
                throw; 
            }
        }

        internal override IAsyncResult BeginMultipleWrite(BufferOffsetSize[] buffers, AsyncCallback callback, object state) { 
            GlobalLog.Print("TlsStream#" + ValidationHelper.HashString(this) + "::BeginMultipleWrite() SecureWorker#" + ValidationHelper.HashString(m_Worker) + " buffers.Length:" + buffers.Length.ToString());
            if (!m_Worker.IsAuthenticated) 
            { 
                BufferAsyncResult result = new BufferAsyncResult(this, buffers, state, callback);
                if (ProcessAuthentication(result)) 
                    return result;
            }

            try { 
                return m_Worker.SecureStream.BeginWrite(buffers, callback, state);
            } 
            catch { 
                if (m_Worker.IsCertValidationFailed) {
                    m_ExceptionStatus = WebExceptionStatus.TrustFailure; 
                }
                else if (m_Worker.LastSecurityStatus != SecurityStatus.OK) {
                    m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure;
                } 
                else {
                    m_ExceptionStatus = WebExceptionStatus.SendFailure; 
                } 
                throw;
            } 
        }

        internal override IAsyncResult UnsafeBeginMultipleWrite(BufferOffsetSize[] buffers, AsyncCallback callback, object state) {
            return BeginMultipleWrite(buffers,callback,state); 
        }
 
        // overrides base.EndMultipeWrite 
        internal override void EndMultipleWrite(IAsyncResult asyncResult)
        { 
            GlobalLog.Print("TlsStream#" + ValidationHelper.HashString(this) + "::EndMultipleWrite() IAsyncResult#" + ValidationHelper.HashString(asyncResult));
            EndWrite(asyncResult);
        }
 

        public X509Certificate ClientCertificate { 
            get { 
                return m_Worker.InternalLocalCertificate;
            } 
        }

        internal ChannelBinding GetChannelBinding(ChannelBindingKind kind)
        { 
            if (kind == ChannelBindingKind.Endpoint && m_CachedChannelBinding != null)
            { 
                return m_CachedChannelBinding; 
            }
 
            return m_Worker.GetChannelBinding(kind);
        }

        // 
        // This methods ensures that IO is only issued when the handshake is completed in ether way
        // The very first coming IO will initiate the handshake and define it's flavor ([....]/async). 
        // 
        // Returns false if the handshake was already done.
        // Returns true  if the user IO is queued and the handshake is started. 
        // Return value is not applicable in [....] case.
        //
        private ArrayList m_PendingIO = new ArrayList();
        internal bool ProcessAuthentication(LazyAsyncResult result) 
        {
            bool doHandshake = false; 
            bool isSyncCall = result == null; 

            lock (m_PendingIO) 
            {
                // do we have handshake as already done before we grabbed a lock?
                if (m_Worker.IsAuthenticated)
                    return false; 

                if (m_PendingIO.Count == 0) 
                { 
                    doHandshake = true;
                } 

                if (isSyncCall)
                {
                    // we will wait on this guy in this method for the handshake to complete 
                    result = new LazyAsyncResult(this, null, null);
                } 
 
                m_PendingIO.Add(result);
            } 

            try {
                if (doHandshake)
                { 
                    bool success = true;
                    LazyAsyncResult handshakeResult = null; 
                    try 
                    {
                        m_Worker.ValidateCreateContext(false, 
                                                       m_DestinationHost,
                                                       (System.Security.Authentication.SslProtocols)ServicePointManager.SecurityProtocol,
                                                       null, m_ClientCertificates,
                                                       true, 
                                                       ServicePointManager.CheckCertificateRevocationList,
                                                       ServicePointManager.CheckCertificateName); 
 

                        if (!isSyncCall) 
                        {
                            // wrap a user async IO/Handshake request into auth request
                            handshakeResult = new LazyAsyncResult(m_Worker, null, new AsyncCallback(WakeupPendingIO));
#if DEBUG 
                            result._DebugAsyncChain = handshakeResult;
#endif 
                        } 

                        // 
                        // TlsStream is used by classes that manually control ExecutionContext, so set it here if we need to.
                        //
                        if (_ExecutionContext != null)
                        { 
                            ExecutionContext.Run(_ExecutionContext.CreateCopy(), new ContextCallback(CallProcessAuthentication), handshakeResult);
                        } 
                        else 
                        {
                            m_Worker.ProcessAuthentication(handshakeResult); 
                        }
                    }
                    catch
                    { 
                        success = false;
                        throw; 
                    } 
                    finally
                    { 
                        if (isSyncCall || !success)
                        {
                            lock (m_PendingIO)
                            { 
                                if(m_PendingIO.Count > 1)
                                { 
                                    // It was a real [....] handshake (now completed) and another IO came in. 
                                    // It's now waiting on us so resume.
                                    ThreadPool.QueueUserWorkItem(new WaitCallback(StartWakeupPendingIO), null); 
                                }
                                else {
                                    m_PendingIO.Clear();
                                } 
                            }
                        } 
                    } 
                }
                else if (isSyncCall) 
                {
                    GlobalLog.Assert(result != null, "TlsStream::ProcessAuthentication() this is a [....] call and it did not started the handshake hence null result must be wrapped into LazyAsyncResult");
                    Exception e = result.InternalWaitForCompletion() as Exception;
                    if (e != null) 
                        throw e;
                } 
            } 
            catch {
                if (m_Worker.IsCertValidationFailed) { 
                    m_ExceptionStatus = WebExceptionStatus.TrustFailure;
                }
                else if (m_Worker.LastSecurityStatus != SecurityStatus.OK) {
                    m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure; 
                }
                else { 
                    m_ExceptionStatus = WebExceptionStatus.ReceiveFailure; 
                }
                throw; 
            }

            // Here in the async case a user IO has been queued (and may be already completed)
            // For [....] case it does not matter since the caller will resume IO upon return 
            return true;
        } 
        // 
        void CallProcessAuthentication(object state)
        { 
            m_Worker.ProcessAuthentication((LazyAsyncResult)state);
        }

        // 
        private void StartWakeupPendingIO(object nullState)
        { 
            // state must be is  null here 
            GlobalLog.Assert(nullState == null, "TlsStream::StartWakeupPendingIO|Expected null state but got {0}.", nullState == null ? "null" : (nullState.GetType().FullName));
            WakeupPendingIO(null); 
        }
        //
        // This is proven to be called without any user stack or it was just ONE IO queued.
        // 
        private void WakeupPendingIO(IAsyncResult ar)
        { 
            Exception exception = null; 
            try {
                if (ar != null) 
                    m_Worker.EndProcessAuthentication(ar);
            }
            catch (Exception e) {
                // This method does not throw because it job is to notify everyon waiting on the result 
                // NOTE: SSL engine remembers the exception and will rethrow it on any access for SecureStream
                // property means on  any IO attempt. 
                exception = e; 

                if (m_Worker.IsCertValidationFailed) { 
                    m_ExceptionStatus = WebExceptionStatus.TrustFailure;
                }
                else if (m_Worker.LastSecurityStatus != SecurityStatus.OK) {
                    m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure; 
                }
                else { 
                    m_ExceptionStatus = WebExceptionStatus.ReceiveFailure; 
                }
            } 

            lock (m_PendingIO)
            {
                while(m_PendingIO.Count != 0) 
                {
                    LazyAsyncResult lazyResult = (LazyAsyncResult )m_PendingIO[m_PendingIO.Count-1]; 
 
                    m_PendingIO.RemoveAt(m_PendingIO.Count-1);
 
                    if (lazyResult is BufferAsyncResult)
                    {
                        if (m_PendingIO.Count == 0)
                        { 
                            // Resume the LAST IO on that thread and offload other IOs on worker threads
                            ResumeIOWorker(lazyResult); 
                        } 
                        else
                        { 
                            ThreadPool.QueueUserWorkItem(new WaitCallback(ResumeIOWorker), lazyResult);
                        }
                    }
                    else 
                    {
                        //resume [....] IO waiting on other thread or signal waiting async handshake result. 
                        try { 
                            lazyResult.InvokeCallback(exception);
                        } 
                        catch {
                            // this method never throws unles the failure is catastrophic
                        }
                    } 
                }
            } 
        } 

        private void ResumeIOWorker(object result) 
        {
            BufferAsyncResult bufferResult = (BufferAsyncResult) result;
            try
            { 
                ResumeIO(bufferResult);
            } 
            catch (Exception exception) 
            {
                if (exception is OutOfMemoryException || exception is StackOverflowException || exception is ThreadAbortException) 
                {
                    throw;
                }
                if (bufferResult.InternalPeekCompleted) 
                    throw;
                bufferResult.InvokeCallback(exception); 
            } 
        }
 
        //
        // Resumes async Read or Write after the handshake is done
        //
        private void ResumeIO(BufferAsyncResult bufferResult) 
        {
            IAsyncResult result; 
            if (bufferResult.IsWrite) 
            {
                if (bufferResult.Buffers != null) 
                    result = m_Worker.SecureStream.BeginWrite(bufferResult.Buffers, _CompleteIOCallback, bufferResult);
                else
                    result = m_Worker.SecureStream.BeginWrite(bufferResult.Buffer, bufferResult.Offset, bufferResult.Count, _CompleteIOCallback, bufferResult);
            } 
            else
            { 
                result = m_Worker.SecureStream.BeginRead(bufferResult.Buffer, bufferResult.Offset, bufferResult.Count, _CompleteIOCallback, bufferResult); 
            }
 
            if (result.CompletedSynchronously)
            {
                CompleteIO(result);
            } 
        }
 
        // 
        //
        // 
        private static void CompleteIOCallback(IAsyncResult result)
        {
            if (result.CompletedSynchronously)
            { 
                return;
            } 
 
            try
            { 
                CompleteIO(result);
            }
            catch (Exception exception)
            { 
                if (exception is OutOfMemoryException || exception is StackOverflowException || exception is ThreadAbortException)
                { 
                    throw; 
                }
 
                if (((LazyAsyncResult) result.AsyncState).InternalPeekCompleted)
                    throw;
                ((LazyAsyncResult) result.AsyncState).InvokeCallback(exception);
            } 
        }
 
        private static void CompleteIO(IAsyncResult result) 
        {
            BufferAsyncResult bufferResult = (BufferAsyncResult) result.AsyncState; 

            object readBytes = null;
            if (bufferResult.IsWrite)
                ((TlsStream)bufferResult.AsyncObject).m_Worker.SecureStream.EndWrite(result); 
            else
                readBytes = ((TlsStream)bufferResult.AsyncObject).m_Worker.SecureStream.EndRead(result); 
 
            bufferResult.InvokeCallback(readBytes);
        } 

        //IT should be virtual but we won't keep internal virtual even in debug version
#if TRAVE
        [System.Diagnostics.Conditional("TRAVE")] 
        internal new void Debug() {
            GlobalLog.Print("m_ExceptionStatus: " + m_ExceptionStatus); 
            GlobalLog.Print("m_DestinationHost: " + m_DestinationHost); 
            GlobalLog.Print("m_Worker:");
            m_Worker.Debug(); 
            base.Debug();
        }
#endif
 

    }; // class TlsStream 
 
} // namespace System.Net

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//------------------------------------------------------------------------------ 
// 
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// 
//----------------------------------------------------------------------------- 

namespace System.Net { 
    using System.IO; 
    using System.Text;
    using System.Net.Sockets; 
    using System.Threading;
    using System.Security.Cryptography.X509Certificates;
    using System.ComponentModel;
    using System.Collections; 
    using System.Net.Security;
    using System.Globalization; 
    using System.Security.Authentication.ExtendedProtection; 

    internal class TlsStream : NetworkStream, IDisposable { 
        private SslState m_Worker;
        private WebExceptionStatus m_ExceptionStatus;
        private string m_DestinationHost;
        private X509CertificateCollection m_ClientCertificates; 
        private static AsyncCallback _CompleteIOCallback = new AsyncCallback(CompleteIOCallback);
 
        private ExecutionContext _ExecutionContext; 
        private ChannelBinding m_CachedChannelBinding;
 
        //
        // This version of an Ssl Stream is for internal HttpWebrequest use.
        // This Ssl client owns the underlined socket
        // The TlsStream will own secured read/write and disposal of the passed "networkStream" stream. 
        //
        public TlsStream(string destinationHost, NetworkStream networkStream, X509CertificateCollection clientCertificates, ServicePoint servicePoint, object initiatingRequest, ExecutionContext executionContext) 
               :base(networkStream, true) { 

        // WebRequest manages the execution context manually so we have to ensure we get one for SSL client certificate demand 
        _ExecutionContext = executionContext;
        if (_ExecutionContext == null)
        {
            _ExecutionContext = ExecutionContext.Capture(); 
        }
 
        // 

 
         GlobalLog.Enter("TlsStream::TlsStream", "host="+destinationHost+", #certs="+((clientCertificates == null) ? "none" : clientCertificates.Count.ToString(NumberFormatInfo.InvariantInfo)));
         if (Logging.On) Logging.PrintInfo(Logging.Web, this, ".ctor", "host="+destinationHost+", #certs="+((clientCertificates == null) ? "null" : clientCertificates.Count.ToString(NumberFormatInfo.InvariantInfo)));

         m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure; 
         m_Worker = new SslState(networkStream, initiatingRequest is HttpWebRequest);
 
         m_DestinationHost = destinationHost; 
         m_ClientCertificates = clientCertificates;
 
         RemoteCertValidationCallback certValidationCallback = servicePoint.SetupHandshakeDoneProcedure(this, initiatingRequest);
         m_Worker.SetCertValidationDelegate(certValidationCallback);

         // The Handshake is NOT done at this point 
         GlobalLog.Leave("TlsStream::TlsStream (Handshake is not done)");
        } 
 
        //
        // HttpWebRequest as a consumer of this class will ---- any write error, by relying on the read side exception. 
        // We want to keep the right failure status for a user application.
        //
        internal WebExceptionStatus ExceptionStatus {
            get { 
                return m_ExceptionStatus;
            } 
        } 

        // This implements the IDisposable contract from the NetworkStream base class 
        // Note that finalizer on the base class WILL call us unless there was explicit disposal.
        int m_ShutDown = 0;
        protected override void Dispose(bool disposing) {
            GlobalLog.Print("TlsStream::Dispose()"); 
            if ( Interlocked.Exchange( ref m_ShutDown,  1) == 1 ) {
                return; 
            } 
            try {
                if (disposing) { 
                    // When KeepAlive is turned off, the TlsStream will be closed before the auth headers for the next request
                    // are computed.  We cannot retrieve the ChannelBinding from the TlsStream after closing it, so we need to
                    // cache it now.
                    m_CachedChannelBinding = GetChannelBinding(ChannelBindingKind.Endpoint); 

                    // Note this will not close the underlined socket, only security context 
                    m_Worker.Close(); 
                }
                else { 
                    m_Worker = null;
                }
            }
            finally { 
                //This will close the underlined socket
                base.Dispose(disposing); 
            } 
        }
 
        public override bool DataAvailable {
            get {
                 return m_Worker.DataAvailable || base.DataAvailable;
            } 
        }
 
        // 
        // [....] Read version
        // 
        public override int Read(byte[] buffer, int offset, int size) {
            GlobalLog.Print("TlsStream#" + ValidationHelper.HashString(this) + "::Read() SecureWorker#" + ValidationHelper.HashString(m_Worker) + " offset:" + offset.ToString() + " size:" + size.ToString());

            if (!m_Worker.IsAuthenticated) 
                ProcessAuthentication(null);
 
            try { 
                return m_Worker.SecureStream.Read(buffer, offset, size);
 
            }
            catch {
                if (m_Worker.IsCertValidationFailed) {
                    m_ExceptionStatus = WebExceptionStatus.TrustFailure; 
                }
                else if (m_Worker.LastSecurityStatus != SecurityStatus.OK) { 
                    m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure; 
                }
                else { 
                    m_ExceptionStatus = WebExceptionStatus.ReceiveFailure;
                }
                throw;
            } 
        }
        // 
        // Async Read version 
        //
        public override IAsyncResult BeginRead(byte[] buffer, int offset, int size, AsyncCallback asyncCallback, object asyncState) { 
            GlobalLog.Print("TlsStream#" + ValidationHelper.HashString(this) + "::BeginRead() SecureWorker#" + ValidationHelper.HashString(m_Worker) + " offset:" + offset.ToString() + " size:" + size.ToString());

            if (!m_Worker.IsAuthenticated)
            { 
                BufferAsyncResult result = new BufferAsyncResult(this, buffer, offset, size, false, asyncState, asyncCallback);
                if (ProcessAuthentication(result)) 
                    return result; 
            }
 
            try {
                return m_Worker.SecureStream.BeginRead(buffer, offset, size, asyncCallback, asyncState);
            }
            catch { 
                if (m_Worker.IsCertValidationFailed) {
                    m_ExceptionStatus = WebExceptionStatus.TrustFailure; 
                } 
                else if (m_Worker.LastSecurityStatus != SecurityStatus.OK) {
                    m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure; 
                }
                else {
                    m_ExceptionStatus = WebExceptionStatus.ReceiveFailure;
                } 
                throw;
            } 
        } 

        // 
        internal override IAsyncResult UnsafeBeginRead(byte[] buffer, int offset, int size, AsyncCallback asyncCallback, object asyncState)
        {
            return BeginRead(buffer, offset, size, asyncCallback, asyncState);
        } 

        // 
        public override int EndRead(IAsyncResult asyncResult) { 
            GlobalLog.Print("TlsStream#" + ValidationHelper.HashString(this) + "::EndRead() IAsyncResult#" + ValidationHelper.HashString(asyncResult));
            try { 

                BufferAsyncResult bufferResult = asyncResult as BufferAsyncResult;

                if (bufferResult == null || (object)bufferResult.AsyncObject != this) 
                    return m_Worker.SecureStream.EndRead(asyncResult);
 
                // we have wrapped user IO in case when handshake was not done as the Begin call 
                bufferResult.InternalWaitForCompletion();
                Exception e = bufferResult.Result as Exception; 
                if (e != null)
                    throw e;

                return (int) bufferResult.Result; 
            }
            catch { 
                if (m_Worker.IsCertValidationFailed) { 
                    m_ExceptionStatus = WebExceptionStatus.TrustFailure;
                } 
                else if (m_Worker.LastSecurityStatus != SecurityStatus.OK) {
                    m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure;
                }
                else { 
                    m_ExceptionStatus = WebExceptionStatus.ReceiveFailure;
                } 
                throw; 
            }
        } 


        //
        // Write, all flavours: synchrnous and asynchrnous 
        //
        public override void Write(byte[] buffer, int offset, int size) { 
            GlobalLog.Print("TlsStream#" + ValidationHelper.HashString(this) + "::Write() SecureWorker#" + ValidationHelper.HashString(m_Worker) + " offset:" + offset.ToString() + " size:" + size.ToString()); 

            if (!m_Worker.IsAuthenticated) 
                ProcessAuthentication(null);

            try {
                m_Worker.SecureStream.Write(buffer, offset, size); 
            }
            catch { 
                // We preserve the original status of a failure because the read 
                // side will now fail with object dispose error.
                if (m_Worker.IsCertValidationFailed) { 
                    m_ExceptionStatus = WebExceptionStatus.TrustFailure;
                }
                else if (m_Worker.LastSecurityStatus != SecurityStatus.OK) {
                    m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure; 
                }
                else { 
                    m_ExceptionStatus = WebExceptionStatus.SendFailure; 
                }
                //HttpWbeRequest depends on the phyical stream to be dropped on a write error. 
                Socket chkSocket = this.Socket;
                if(chkSocket != null) {
                    chkSocket.InternalShutdown(SocketShutdown.Both);
                } 
                throw;
            } 
        } 

        // 
        // BeginWrite -
        //
        // Write the bytes to the write - while encrypting
        // 
        // copy plain text data to a temporary buffer
        // encrypt the data 
        // once the data is encrypted clear the plain text for security 
        //
        public override IAsyncResult BeginWrite( byte[] buffer, int offset, int size, AsyncCallback asyncCallback, object asyncState) { 
            GlobalLog.Print("TlsStream#" + ValidationHelper.HashString(this) + "::BeginWrite() SecureWorker#" + ValidationHelper.HashString(m_Worker) + " offset:" + offset.ToString() + " size:" + size.ToString());
            if (!m_Worker.IsAuthenticated)
            {
                BufferAsyncResult result = new BufferAsyncResult(this, buffer, offset, size, true, asyncState, asyncCallback); 
                if (ProcessAuthentication(result))
                    return result; 
            } 

            try { 
                return m_Worker.SecureStream.BeginWrite(buffer, offset, size, asyncCallback, asyncState);
            }
            catch {
                if (m_Worker.IsCertValidationFailed) { 
                    m_ExceptionStatus = WebExceptionStatus.TrustFailure;
                } 
                else if (m_Worker.LastSecurityStatus != SecurityStatus.OK) { 
                    m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure;
                } 
                else {
                    m_ExceptionStatus = WebExceptionStatus.SendFailure;
                }
                throw; 
            }
        } 
 

        internal override IAsyncResult UnsafeBeginWrite( byte[] buffer, int offset, int size, AsyncCallback asyncCallback, object asyncState) { 
            return BeginWrite(buffer,offset,size,asyncCallback,asyncState);
        }

 

        public override void EndWrite(IAsyncResult asyncResult) { 
            GlobalLog.Print("TlsStream#" + ValidationHelper.HashString(this) + "::EndWrite() IAsyncResult#" + ValidationHelper.HashString(asyncResult)); 

            try { 
                BufferAsyncResult bufferResult = asyncResult as BufferAsyncResult;

                if (bufferResult == null || (object)bufferResult.AsyncObject != this)
                { 
                    m_Worker.SecureStream.EndWrite(asyncResult);
                } 
                else 
                {
                    // we have wrapped user IO in case when handshake was not done as the Begin call 
                    bufferResult.InternalWaitForCompletion();
                    Exception e = bufferResult.Result as Exception;
                    if (e != null)
                        throw e; 
                }
            } 
            catch { 
                //HttpWebRequest depends on the stream to be dropped on a write error.
                Socket chkSocket = this.Socket; 
                if(chkSocket != null) {
                    chkSocket.InternalShutdown(SocketShutdown.Both);
                }
                // We also preserve the original status of a failure because the read 
                // side will now fail with object dispose error.
                if (m_Worker.IsCertValidationFailed) { 
                    m_ExceptionStatus = WebExceptionStatus.TrustFailure; 
                }
                else if (m_Worker.LastSecurityStatus != SecurityStatus.OK) { 
                    m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure;
                }
                else {
                    m_ExceptionStatus = WebExceptionStatus.SendFailure; 
                }
                throw; 
            } 
        }
 
        internal override void MultipleWrite(BufferOffsetSize[] buffers) {
            GlobalLog.Print("TlsStream#" + ValidationHelper.HashString(this) + "::MultipleWrite() SecureWorker#" + ValidationHelper.HashString(m_Worker) + " buffers.Length:" + buffers.Length.ToString());

            if (!m_Worker.IsAuthenticated) 
                ProcessAuthentication(null);
 
            try { 
                m_Worker.SecureStream.Write(buffers);
            } 
            catch {
                //HttpWbeRequest depends on the physical stream to be dropped on a write error.
                Socket chkSocket = this.Socket;
                if(chkSocket != null) { 
                    chkSocket.InternalShutdown(SocketShutdown.Both);
                } 
                // We preserve the original status of a failure because the read 
                // side will now fail with object dispose error.
                if (m_Worker.IsCertValidationFailed) { 
                    m_ExceptionStatus = WebExceptionStatus.TrustFailure;
                }
                else if (m_Worker.LastSecurityStatus != SecurityStatus.OK) {
                    m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure; 
                }
                else { 
                    m_ExceptionStatus = WebExceptionStatus.SendFailure; 
                }
                throw; 
            }
        }

        internal override IAsyncResult BeginMultipleWrite(BufferOffsetSize[] buffers, AsyncCallback callback, object state) { 
            GlobalLog.Print("TlsStream#" + ValidationHelper.HashString(this) + "::BeginMultipleWrite() SecureWorker#" + ValidationHelper.HashString(m_Worker) + " buffers.Length:" + buffers.Length.ToString());
            if (!m_Worker.IsAuthenticated) 
            { 
                BufferAsyncResult result = new BufferAsyncResult(this, buffers, state, callback);
                if (ProcessAuthentication(result)) 
                    return result;
            }

            try { 
                return m_Worker.SecureStream.BeginWrite(buffers, callback, state);
            } 
            catch { 
                if (m_Worker.IsCertValidationFailed) {
                    m_ExceptionStatus = WebExceptionStatus.TrustFailure; 
                }
                else if (m_Worker.LastSecurityStatus != SecurityStatus.OK) {
                    m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure;
                } 
                else {
                    m_ExceptionStatus = WebExceptionStatus.SendFailure; 
                } 
                throw;
            } 
        }

        internal override IAsyncResult UnsafeBeginMultipleWrite(BufferOffsetSize[] buffers, AsyncCallback callback, object state) {
            return BeginMultipleWrite(buffers,callback,state); 
        }
 
        // overrides base.EndMultipeWrite 
        internal override void EndMultipleWrite(IAsyncResult asyncResult)
        { 
            GlobalLog.Print("TlsStream#" + ValidationHelper.HashString(this) + "::EndMultipleWrite() IAsyncResult#" + ValidationHelper.HashString(asyncResult));
            EndWrite(asyncResult);
        }
 

        public X509Certificate ClientCertificate { 
            get { 
                return m_Worker.InternalLocalCertificate;
            } 
        }

        internal ChannelBinding GetChannelBinding(ChannelBindingKind kind)
        { 
            if (kind == ChannelBindingKind.Endpoint && m_CachedChannelBinding != null)
            { 
                return m_CachedChannelBinding; 
            }
 
            return m_Worker.GetChannelBinding(kind);
        }

        // 
        // This methods ensures that IO is only issued when the handshake is completed in ether way
        // The very first coming IO will initiate the handshake and define it's flavor ([....]/async). 
        // 
        // Returns false if the handshake was already done.
        // Returns true  if the user IO is queued and the handshake is started. 
        // Return value is not applicable in [....] case.
        //
        private ArrayList m_PendingIO = new ArrayList();
        internal bool ProcessAuthentication(LazyAsyncResult result) 
        {
            bool doHandshake = false; 
            bool isSyncCall = result == null; 

            lock (m_PendingIO) 
            {
                // do we have handshake as already done before we grabbed a lock?
                if (m_Worker.IsAuthenticated)
                    return false; 

                if (m_PendingIO.Count == 0) 
                { 
                    doHandshake = true;
                } 

                if (isSyncCall)
                {
                    // we will wait on this guy in this method for the handshake to complete 
                    result = new LazyAsyncResult(this, null, null);
                } 
 
                m_PendingIO.Add(result);
            } 

            try {
                if (doHandshake)
                { 
                    bool success = true;
                    LazyAsyncResult handshakeResult = null; 
                    try 
                    {
                        m_Worker.ValidateCreateContext(false, 
                                                       m_DestinationHost,
                                                       (System.Security.Authentication.SslProtocols)ServicePointManager.SecurityProtocol,
                                                       null, m_ClientCertificates,
                                                       true, 
                                                       ServicePointManager.CheckCertificateRevocationList,
                                                       ServicePointManager.CheckCertificateName); 
 

                        if (!isSyncCall) 
                        {
                            // wrap a user async IO/Handshake request into auth request
                            handshakeResult = new LazyAsyncResult(m_Worker, null, new AsyncCallback(WakeupPendingIO));
#if DEBUG 
                            result._DebugAsyncChain = handshakeResult;
#endif 
                        } 

                        // 
                        // TlsStream is used by classes that manually control ExecutionContext, so set it here if we need to.
                        //
                        if (_ExecutionContext != null)
                        { 
                            ExecutionContext.Run(_ExecutionContext.CreateCopy(), new ContextCallback(CallProcessAuthentication), handshakeResult);
                        } 
                        else 
                        {
                            m_Worker.ProcessAuthentication(handshakeResult); 
                        }
                    }
                    catch
                    { 
                        success = false;
                        throw; 
                    } 
                    finally
                    { 
                        if (isSyncCall || !success)
                        {
                            lock (m_PendingIO)
                            { 
                                if(m_PendingIO.Count > 1)
                                { 
                                    // It was a real [....] handshake (now completed) and another IO came in. 
                                    // It's now waiting on us so resume.
                                    ThreadPool.QueueUserWorkItem(new WaitCallback(StartWakeupPendingIO), null); 
                                }
                                else {
                                    m_PendingIO.Clear();
                                } 
                            }
                        } 
                    } 
                }
                else if (isSyncCall) 
                {
                    GlobalLog.Assert(result != null, "TlsStream::ProcessAuthentication() this is a [....] call and it did not started the handshake hence null result must be wrapped into LazyAsyncResult");
                    Exception e = result.InternalWaitForCompletion() as Exception;
                    if (e != null) 
                        throw e;
                } 
            } 
            catch {
                if (m_Worker.IsCertValidationFailed) { 
                    m_ExceptionStatus = WebExceptionStatus.TrustFailure;
                }
                else if (m_Worker.LastSecurityStatus != SecurityStatus.OK) {
                    m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure; 
                }
                else { 
                    m_ExceptionStatus = WebExceptionStatus.ReceiveFailure; 
                }
                throw; 
            }

            // Here in the async case a user IO has been queued (and may be already completed)
            // For [....] case it does not matter since the caller will resume IO upon return 
            return true;
        } 
        // 
        void CallProcessAuthentication(object state)
        { 
            m_Worker.ProcessAuthentication((LazyAsyncResult)state);
        }

        // 
        private void StartWakeupPendingIO(object nullState)
        { 
            // state must be is  null here 
            GlobalLog.Assert(nullState == null, "TlsStream::StartWakeupPendingIO|Expected null state but got {0}.", nullState == null ? "null" : (nullState.GetType().FullName));
            WakeupPendingIO(null); 
        }
        //
        // This is proven to be called without any user stack or it was just ONE IO queued.
        // 
        private void WakeupPendingIO(IAsyncResult ar)
        { 
            Exception exception = null; 
            try {
                if (ar != null) 
                    m_Worker.EndProcessAuthentication(ar);
            }
            catch (Exception e) {
                // This method does not throw because it job is to notify everyon waiting on the result 
                // NOTE: SSL engine remembers the exception and will rethrow it on any access for SecureStream
                // property means on  any IO attempt. 
                exception = e; 

                if (m_Worker.IsCertValidationFailed) { 
                    m_ExceptionStatus = WebExceptionStatus.TrustFailure;
                }
                else if (m_Worker.LastSecurityStatus != SecurityStatus.OK) {
                    m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure; 
                }
                else { 
                    m_ExceptionStatus = WebExceptionStatus.ReceiveFailure; 
                }
            } 

            lock (m_PendingIO)
            {
                while(m_PendingIO.Count != 0) 
                {
                    LazyAsyncResult lazyResult = (LazyAsyncResult )m_PendingIO[m_PendingIO.Count-1]; 
 
                    m_PendingIO.RemoveAt(m_PendingIO.Count-1);
 
                    if (lazyResult is BufferAsyncResult)
                    {
                        if (m_PendingIO.Count == 0)
                        { 
                            // Resume the LAST IO on that thread and offload other IOs on worker threads
                            ResumeIOWorker(lazyResult); 
                        } 
                        else
                        { 
                            ThreadPool.QueueUserWorkItem(new WaitCallback(ResumeIOWorker), lazyResult);
                        }
                    }
                    else 
                    {
                        //resume [....] IO waiting on other thread or signal waiting async handshake result. 
                        try { 
                            lazyResult.InvokeCallback(exception);
                        } 
                        catch {
                            // this method never throws unles the failure is catastrophic
                        }
                    } 
                }
            } 
        } 

        private void ResumeIOWorker(object result) 
        {
            BufferAsyncResult bufferResult = (BufferAsyncResult) result;
            try
            { 
                ResumeIO(bufferResult);
            } 
            catch (Exception exception) 
            {
                if (exception is OutOfMemoryException || exception is StackOverflowException || exception is ThreadAbortException) 
                {
                    throw;
                }
                if (bufferResult.InternalPeekCompleted) 
                    throw;
                bufferResult.InvokeCallback(exception); 
            } 
        }
 
        //
        // Resumes async Read or Write after the handshake is done
        //
        private void ResumeIO(BufferAsyncResult bufferResult) 
        {
            IAsyncResult result; 
            if (bufferResult.IsWrite) 
            {
                if (bufferResult.Buffers != null) 
                    result = m_Worker.SecureStream.BeginWrite(bufferResult.Buffers, _CompleteIOCallback, bufferResult);
                else
                    result = m_Worker.SecureStream.BeginWrite(bufferResult.Buffer, bufferResult.Offset, bufferResult.Count, _CompleteIOCallback, bufferResult);
            } 
            else
            { 
                result = m_Worker.SecureStream.BeginRead(bufferResult.Buffer, bufferResult.Offset, bufferResult.Count, _CompleteIOCallback, bufferResult); 
            }
 
            if (result.CompletedSynchronously)
            {
                CompleteIO(result);
            } 
        }
 
        // 
        //
        // 
        private static void CompleteIOCallback(IAsyncResult result)
        {
            if (result.CompletedSynchronously)
            { 
                return;
            } 
 
            try
            { 
                CompleteIO(result);
            }
            catch (Exception exception)
            { 
                if (exception is OutOfMemoryException || exception is StackOverflowException || exception is ThreadAbortException)
                { 
                    throw; 
                }
 
                if (((LazyAsyncResult) result.AsyncState).InternalPeekCompleted)
                    throw;
                ((LazyAsyncResult) result.AsyncState).InvokeCallback(exception);
            } 
        }
 
        private static void CompleteIO(IAsyncResult result) 
        {
            BufferAsyncResult bufferResult = (BufferAsyncResult) result.AsyncState; 

            object readBytes = null;
            if (bufferResult.IsWrite)
                ((TlsStream)bufferResult.AsyncObject).m_Worker.SecureStream.EndWrite(result); 
            else
                readBytes = ((TlsStream)bufferResult.AsyncObject).m_Worker.SecureStream.EndRead(result); 
 
            bufferResult.InvokeCallback(readBytes);
        } 

        //IT should be virtual but we won't keep internal virtual even in debug version
#if TRAVE
        [System.Diagnostics.Conditional("TRAVE")] 
        internal new void Debug() {
            GlobalLog.Print("m_ExceptionStatus: " + m_ExceptionStatus); 
            GlobalLog.Print("m_DestinationHost: " + m_DestinationHost); 
            GlobalLog.Print("m_Worker:");
            m_Worker.Debug(); 
            base.Debug();
        }
#endif
 

    }; // class TlsStream 
 
} // namespace System.Net

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.

                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK