1 //------------------------------------------------------------------------------
2 // <copyright file="_TLSstream.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
10 using System.Net.Sockets;
11 using System.Threading;
12 using System.Security.Cryptography.X509Certificates;
13 using System.ComponentModel;
14 using System.Collections;
15 using System.Net.Security;
16 using System.Globalization;
17 using System.Security.Authentication.ExtendedProtection;
18 using System.Net.Configuration;
20 internal class TlsStream : NetworkStream, IDisposable {
21 private SslState m_Worker;
22 private WebExceptionStatus m_ExceptionStatus;
23 private string m_DestinationHost;
24 private X509CertificateCollection m_ClientCertificates;
25 private static AsyncCallback _CompleteIOCallback = new AsyncCallback(CompleteIOCallback);
27 private ExecutionContext _ExecutionContext;
28 private ChannelBinding m_CachedChannelBinding;
31 // This version of an Ssl Stream is for internal HttpWebrequest use.
32 // This Ssl client owns the underlined socket
33 // The TlsStream will own secured read/write and disposal of the passed "networkStream" stream.
35 public TlsStream(string destinationHost, NetworkStream networkStream, X509CertificateCollection clientCertificates, ServicePoint servicePoint, object initiatingRequest, ExecutionContext executionContext)
36 :base(networkStream, true) {
38 // WebRequest manages the execution context manually so we have to ensure we get one for SSL client certificate demand
39 _ExecutionContext = executionContext;
40 if (_ExecutionContext == null)
42 _ExecutionContext = ExecutionContext.Capture();
48 GlobalLog.Enter("TlsStream::TlsStream", "host="+destinationHost+", #certs="+((clientCertificates == null) ? "none" : clientCertificates.Count.ToString(NumberFormatInfo.InvariantInfo)));
49 if (Logging.On) Logging.PrintInfo(Logging.Web, this, ".ctor", "host="+destinationHost+", #certs="+((clientCertificates == null) ? "null" : clientCertificates.Count.ToString(NumberFormatInfo.InvariantInfo)));
51 m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure;
52 m_Worker = new SslState(networkStream, initiatingRequest is HttpWebRequest, SettingsSectionInternal.Section.EncryptionPolicy);
54 m_DestinationHost = destinationHost;
55 m_ClientCertificates = clientCertificates;
57 RemoteCertValidationCallback certValidationCallback = servicePoint.SetupHandshakeDoneProcedure(this, initiatingRequest);
58 m_Worker.SetCertValidationDelegate(certValidationCallback);
60 // The Handshake is NOT done at this point
61 GlobalLog.Leave("TlsStream::TlsStream (Handshake is not done)");
65 // HttpWebRequest as a consumer of this class will ignore any write error, by relying on the read side exception.
66 // We want to keep the right failure status for a user application.
68 internal WebExceptionStatus ExceptionStatus {
70 return m_ExceptionStatus;
74 // This implements the IDisposable contract from the NetworkStream base class
75 // Note that finalizer on the base class WILL call us unless there was explicit disposal.
77 protected override void Dispose(bool disposing) {
78 GlobalLog.Print("TlsStream::Dispose()");
79 if ( Interlocked.Exchange( ref m_ShutDown, 1) == 1 ) {
84 // When KeepAlive is turned off, the TlsStream will be closed before the auth headers for the next request
85 // are computed. We cannot retrieve the ChannelBinding from the TlsStream after closing it, so we need to
87 m_CachedChannelBinding = GetChannelBinding(ChannelBindingKind.Endpoint);
89 // Note this will not close the underlined socket, only security context
97 //This will close the underlined socket
98 base.Dispose(disposing);
102 public override bool DataAvailable {
104 return m_Worker.DataAvailable || base.DataAvailable;
111 public override int Read(byte[] buffer, int offset, int size) {
112 GlobalLog.Print("TlsStream#" + ValidationHelper.HashString(this) + "::Read() SecureWorker#" + ValidationHelper.HashString(m_Worker) + " offset:" + offset.ToString() + " size:" + size.ToString());
114 if (!m_Worker.IsAuthenticated)
115 ProcessAuthentication(null);
118 return m_Worker.SecureStream.Read(buffer, offset, size);
122 if (m_Worker.IsCertValidationFailed) {
123 m_ExceptionStatus = WebExceptionStatus.TrustFailure;
125 else if (m_Worker.LastSecurityStatus != SecurityStatus.OK) {
126 m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure;
129 m_ExceptionStatus = WebExceptionStatus.ReceiveFailure;
135 // Async Read version
137 public override IAsyncResult BeginRead(byte[] buffer, int offset, int size, AsyncCallback asyncCallback, object asyncState) {
138 GlobalLog.Print("TlsStream#" + ValidationHelper.HashString(this) + "::BeginRead() SecureWorker#" + ValidationHelper.HashString(m_Worker) + " offset:" + offset.ToString() + " size:" + size.ToString());
140 if (!m_Worker.IsAuthenticated)
142 BufferAsyncResult result = new BufferAsyncResult(this, buffer, offset, size, false, asyncState, asyncCallback);
143 if (ProcessAuthentication(result))
148 return m_Worker.SecureStream.BeginRead(buffer, offset, size, asyncCallback, asyncState);
151 if (m_Worker.IsCertValidationFailed) {
152 m_ExceptionStatus = WebExceptionStatus.TrustFailure;
154 else if (m_Worker.LastSecurityStatus != SecurityStatus.OK) {
155 m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure;
158 m_ExceptionStatus = WebExceptionStatus.ReceiveFailure;
165 internal override IAsyncResult UnsafeBeginRead(byte[] buffer, int offset, int size, AsyncCallback asyncCallback, object asyncState)
167 return BeginRead(buffer, offset, size, asyncCallback, asyncState);
171 public override int EndRead(IAsyncResult asyncResult) {
172 GlobalLog.Print("TlsStream#" + ValidationHelper.HashString(this) + "::EndRead() IAsyncResult#" + ValidationHelper.HashString(asyncResult));
175 BufferAsyncResult bufferResult = asyncResult as BufferAsyncResult;
177 if (bufferResult == null || (object)bufferResult.AsyncObject != this)
178 return m_Worker.SecureStream.EndRead(asyncResult);
180 // we have wrapped user IO in case when handshake was not done as the Begin call
181 bufferResult.InternalWaitForCompletion();
182 Exception e = bufferResult.Result as Exception;
186 return (int) bufferResult.Result;
189 if (m_Worker.IsCertValidationFailed) {
190 m_ExceptionStatus = WebExceptionStatus.TrustFailure;
192 else if (m_Worker.LastSecurityStatus != SecurityStatus.OK) {
193 m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure;
196 m_ExceptionStatus = WebExceptionStatus.ReceiveFailure;
204 // Write, all flavours: synchrnous and asynchrnous
206 public override void Write(byte[] buffer, int offset, int size) {
207 GlobalLog.Print("TlsStream#" + ValidationHelper.HashString(this) + "::Write() SecureWorker#" + ValidationHelper.HashString(m_Worker) + " offset:" + offset.ToString() + " size:" + size.ToString());
209 if (!m_Worker.IsAuthenticated)
210 ProcessAuthentication(null);
213 m_Worker.SecureStream.Write(buffer, offset, size);
216 // We preserve the original status of a failure because the read
217 // side will now fail with object dispose error.
218 if (m_Worker.IsCertValidationFailed) {
219 m_ExceptionStatus = WebExceptionStatus.TrustFailure;
221 else if (m_Worker.LastSecurityStatus != SecurityStatus.OK) {
222 m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure;
225 m_ExceptionStatus = WebExceptionStatus.SendFailure;
227 //HttpWbeRequest depends on the phyical stream to be dropped on a write error.
228 Socket chkSocket = this.Socket;
229 if(chkSocket != null) {
230 chkSocket.InternalShutdown(SocketShutdown.Both);
239 // Write the bytes to the write - while encrypting
241 // copy plain text data to a temporary buffer
243 // once the data is encrypted clear the plain text for security
245 public override IAsyncResult BeginWrite( byte[] buffer, int offset, int size, AsyncCallback asyncCallback, object asyncState) {
246 GlobalLog.Print("TlsStream#" + ValidationHelper.HashString(this) + "::BeginWrite() SecureWorker#" + ValidationHelper.HashString(m_Worker) + " offset:" + offset.ToString() + " size:" + size.ToString());
247 if (!m_Worker.IsAuthenticated)
249 BufferAsyncResult result = new BufferAsyncResult(this, buffer, offset, size, true, asyncState, asyncCallback);
250 if (ProcessAuthentication(result))
255 return m_Worker.SecureStream.BeginWrite(buffer, offset, size, asyncCallback, asyncState);
258 if (m_Worker.IsCertValidationFailed) {
259 m_ExceptionStatus = WebExceptionStatus.TrustFailure;
261 else if (m_Worker.LastSecurityStatus != SecurityStatus.OK) {
262 m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure;
265 m_ExceptionStatus = WebExceptionStatus.SendFailure;
272 internal override IAsyncResult UnsafeBeginWrite( byte[] buffer, int offset, int size, AsyncCallback asyncCallback, object asyncState) {
273 return BeginWrite(buffer,offset,size,asyncCallback,asyncState);
278 public override void EndWrite(IAsyncResult asyncResult) {
279 GlobalLog.Print("TlsStream#" + ValidationHelper.HashString(this) + "::EndWrite() IAsyncResult#" + ValidationHelper.HashString(asyncResult));
282 BufferAsyncResult bufferResult = asyncResult as BufferAsyncResult;
284 if (bufferResult == null || (object)bufferResult.AsyncObject != this)
286 m_Worker.SecureStream.EndWrite(asyncResult);
290 // we have wrapped user IO in case when handshake was not done as the Begin call
291 bufferResult.InternalWaitForCompletion();
292 Exception e = bufferResult.Result as Exception;
298 //HttpWebRequest depends on the stream to be dropped on a write error.
299 Socket chkSocket = this.Socket;
300 if(chkSocket != null) {
301 chkSocket.InternalShutdown(SocketShutdown.Both);
303 // We also preserve the original status of a failure because the read
304 // side will now fail with object dispose error.
305 if (m_Worker.IsCertValidationFailed) {
306 m_ExceptionStatus = WebExceptionStatus.TrustFailure;
308 else if (m_Worker.LastSecurityStatus != SecurityStatus.OK) {
309 m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure;
312 m_ExceptionStatus = WebExceptionStatus.SendFailure;
318 internal override void MultipleWrite(BufferOffsetSize[] buffers) {
319 GlobalLog.Print("TlsStream#" + ValidationHelper.HashString(this) + "::MultipleWrite() SecureWorker#" + ValidationHelper.HashString(m_Worker) + " buffers.Length:" + buffers.Length.ToString());
321 if (!m_Worker.IsAuthenticated)
322 ProcessAuthentication(null);
325 m_Worker.SecureStream.Write(buffers);
328 //HttpWbeRequest depends on the physical stream to be dropped on a write error.
329 Socket chkSocket = this.Socket;
330 if(chkSocket != null) {
331 chkSocket.InternalShutdown(SocketShutdown.Both);
333 // We preserve the original status of a failure because the read
334 // side will now fail with object dispose error.
335 if (m_Worker.IsCertValidationFailed) {
336 m_ExceptionStatus = WebExceptionStatus.TrustFailure;
338 else if (m_Worker.LastSecurityStatus != SecurityStatus.OK) {
339 m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure;
342 m_ExceptionStatus = WebExceptionStatus.SendFailure;
348 internal override IAsyncResult BeginMultipleWrite(BufferOffsetSize[] buffers, AsyncCallback callback, object state) {
349 GlobalLog.Print("TlsStream#" + ValidationHelper.HashString(this) + "::BeginMultipleWrite() SecureWorker#" + ValidationHelper.HashString(m_Worker) + " buffers.Length:" + buffers.Length.ToString());
350 if (!m_Worker.IsAuthenticated)
352 BufferAsyncResult result = new BufferAsyncResult(this, buffers, state, callback);
353 if (ProcessAuthentication(result))
358 return m_Worker.SecureStream.BeginWrite(buffers, callback, state);
361 if (m_Worker.IsCertValidationFailed) {
362 m_ExceptionStatus = WebExceptionStatus.TrustFailure;
364 else if (m_Worker.LastSecurityStatus != SecurityStatus.OK) {
365 m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure;
368 m_ExceptionStatus = WebExceptionStatus.SendFailure;
374 internal override IAsyncResult UnsafeBeginMultipleWrite(BufferOffsetSize[] buffers, AsyncCallback callback, object state) {
375 return BeginMultipleWrite(buffers,callback,state);
378 // overrides base.EndMultipeWrite
379 internal override void EndMultipleWrite(IAsyncResult asyncResult)
381 GlobalLog.Print("TlsStream#" + ValidationHelper.HashString(this) + "::EndMultipleWrite() IAsyncResult#" + ValidationHelper.HashString(asyncResult));
382 EndWrite(asyncResult);
386 public X509Certificate ClientCertificate {
388 return m_Worker.InternalLocalCertificate;
392 internal ChannelBinding GetChannelBinding(ChannelBindingKind kind)
394 if (kind == ChannelBindingKind.Endpoint && m_CachedChannelBinding != null)
396 return m_CachedChannelBinding;
399 return m_Worker.GetChannelBinding(kind);
403 // This methods ensures that IO is only issued when the handshake is completed in ether way
404 // The very first coming IO will initiate the handshake and define it's flavor (sync/async).
406 // Returns false if the handshake was already done.
407 // Returns true if the user IO is queued and the handshake is started.
408 // Return value is not applicable in sync case.
410 private ArrayList m_PendingIO = new ArrayList();
411 internal bool ProcessAuthentication(LazyAsyncResult result)
413 bool doHandshake = false;
414 bool isSyncCall = result == null;
418 // do we have handshake as already done before we grabbed a lock?
419 if (m_Worker.IsAuthenticated)
422 if (m_PendingIO.Count == 0)
429 // we will wait on this guy in this method for the handshake to complete
430 result = new LazyAsyncResult(this, null, null);
433 m_PendingIO.Add(result);
440 LazyAsyncResult handshakeResult = null;
443 m_Worker.ValidateCreateContext(false,
445 (System.Security.Authentication.SslProtocols)ServicePointManager.SecurityProtocol,
446 null, m_ClientCertificates,
448 ServicePointManager.CheckCertificateRevocationList,
449 ServicePointManager.CheckCertificateName);
454 // wrap a user async IO/Handshake request into auth request
455 handshakeResult = new LazyAsyncResult(m_Worker, null, new AsyncCallback(WakeupPendingIO));
457 result._DebugAsyncChain = handshakeResult;
462 // TlsStream is used by classes that manually control ExecutionContext, so set it here if we need to.
464 if (_ExecutionContext != null)
466 ExecutionContext.Run(_ExecutionContext.CreateCopy(), new ContextCallback(CallProcessAuthentication), handshakeResult);
470 m_Worker.ProcessAuthentication(handshakeResult);
480 if (isSyncCall || !success)
484 if(m_PendingIO.Count > 1)
486 // It was a real sync handshake (now completed) and another IO came in.
487 // It's now waiting on us so resume.
488 ThreadPool.QueueUserWorkItem(new WaitCallback(StartWakeupPendingIO), null);
499 GlobalLog.Assert(result != null, "TlsStream::ProcessAuthentication() this is a Sync call and it did not started the handshake hence null result must be wrapped into LazyAsyncResult");
500 Exception e = result.InternalWaitForCompletion() as Exception;
506 if (m_Worker.IsCertValidationFailed) {
507 m_ExceptionStatus = WebExceptionStatus.TrustFailure;
509 else if (m_Worker.LastSecurityStatus != SecurityStatus.OK) {
510 m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure;
513 m_ExceptionStatus = WebExceptionStatus.ReceiveFailure;
518 // Here in the async case a user IO has been queued (and may be already completed)
519 // For sync case it does not matter since the caller will resume IO upon return
523 void CallProcessAuthentication(object state)
525 m_Worker.ProcessAuthentication((LazyAsyncResult)state);
529 private void StartWakeupPendingIO(object nullState)
531 // state must be is null here
532 GlobalLog.Assert(nullState == null, "TlsStream::StartWakeupPendingIO|Expected null state but got {0}.", nullState == null ? "null" : (nullState.GetType().FullName));
533 WakeupPendingIO(null);
536 // This is proven to be called without any user stack or it was just ONE IO queued.
538 private void WakeupPendingIO(IAsyncResult ar)
540 Exception exception = null;
543 m_Worker.EndProcessAuthentication(ar);
545 catch (Exception e) {
546 // This method does not throw because it job is to notify everyon waiting on the result
547 // NOTE: SSL engine remembers the exception and will rethrow it on any access for SecureStream
548 // property means on any IO attempt.
551 if (m_Worker.IsCertValidationFailed) {
552 m_ExceptionStatus = WebExceptionStatus.TrustFailure;
554 else if (m_Worker.LastSecurityStatus != SecurityStatus.OK) {
555 m_ExceptionStatus = WebExceptionStatus.SecureChannelFailure;
558 m_ExceptionStatus = WebExceptionStatus.ReceiveFailure;
564 while(m_PendingIO.Count != 0)
566 LazyAsyncResult lazyResult = (LazyAsyncResult )m_PendingIO[m_PendingIO.Count-1];
568 m_PendingIO.RemoveAt(m_PendingIO.Count-1);
570 if (lazyResult is BufferAsyncResult)
572 if (m_PendingIO.Count == 0)
574 // Resume the LAST IO on that thread and offload other IOs on worker threads
575 ResumeIOWorker(lazyResult);
579 ThreadPool.QueueUserWorkItem(new WaitCallback(ResumeIOWorker), lazyResult);
584 //resume sync IO waiting on other thread or signal waiting async handshake result.
586 lazyResult.InvokeCallback(exception);
589 // this method never throws unles the failure is catastrophic
596 private void ResumeIOWorker(object result)
598 BufferAsyncResult bufferResult = (BufferAsyncResult) result;
601 ResumeIO(bufferResult);
603 catch (Exception exception)
605 if (exception is OutOfMemoryException || exception is StackOverflowException || exception is ThreadAbortException)
609 if (bufferResult.InternalPeekCompleted)
611 bufferResult.InvokeCallback(exception);
616 // Resumes async Read or Write after the handshake is done
618 private void ResumeIO(BufferAsyncResult bufferResult)
621 if (bufferResult.IsWrite)
623 if (bufferResult.Buffers != null)
624 result = m_Worker.SecureStream.BeginWrite(bufferResult.Buffers, _CompleteIOCallback, bufferResult);
626 result = m_Worker.SecureStream.BeginWrite(bufferResult.Buffer, bufferResult.Offset, bufferResult.Count, _CompleteIOCallback, bufferResult);
630 result = m_Worker.SecureStream.BeginRead(bufferResult.Buffer, bufferResult.Offset, bufferResult.Count, _CompleteIOCallback, bufferResult);
633 if (result.CompletedSynchronously)
642 private static void CompleteIOCallback(IAsyncResult result)
644 if (result.CompletedSynchronously)
653 catch (Exception exception)
655 if (exception is OutOfMemoryException || exception is StackOverflowException || exception is ThreadAbortException)
660 if (((LazyAsyncResult) result.AsyncState).InternalPeekCompleted)
662 ((LazyAsyncResult) result.AsyncState).InvokeCallback(exception);
666 private static void CompleteIO(IAsyncResult result)
668 BufferAsyncResult bufferResult = (BufferAsyncResult) result.AsyncState;
670 object readBytes = null;
671 if (bufferResult.IsWrite)
672 ((TlsStream)bufferResult.AsyncObject).m_Worker.SecureStream.EndWrite(result);
674 readBytes = ((TlsStream)bufferResult.AsyncObject).m_Worker.SecureStream.EndRead(result);
676 bufferResult.InvokeCallback(readBytes);
679 //IT should be virtual but we won't keep internal virtual even in debug version
681 [System.Diagnostics.Conditional("TRAVE")]
682 internal new void DebugMembers() {
683 GlobalLog.Print("m_ExceptionStatus: " + m_ExceptionStatus);
684 GlobalLog.Print("m_DestinationHost: " + m_DestinationHost);
685 GlobalLog.Print("m_Worker:");
686 m_Worker.DebugMembers();
692 }; // class TlsStream
694 } // namespace System.Net