1 // Transport Security Layer (TLS)
2 // Copyright (c) 2003-2004 Carlos Guzman Alvarez
3 // Copyright (C) 2006-2007 Novell, Inc (http://www.novell.com)
5 // Permission is hereby granted, free of charge, to any person obtaining
6 // a copy of this software and associated documentation files (the
7 // "Software"), to deal in the Software without restriction, including
8 // without limitation the rights to use, copy, modify, merge, publish,
9 // distribute, sublicense, and/or sell copies of the Software, and to
10 // permit persons to whom the Software is furnished to do so, subject to
11 // the following conditions:
13 // The above copyright notice and this permission notice shall be
14 // included in all copies or substantial portions of the Software.
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 using System.Collections;
29 using System.Net.Sockets;
30 using System.Security.Cryptography;
31 using System.Security.Cryptography.X509Certificates;
32 using System.Threading;
34 namespace Mono.Security.Protocol.Tls
41 abstract class SslStreamBase: Stream, IDisposable
43 private delegate void AsyncHandshakeDelegate(InternalAsyncResult asyncResult, bool fromWrite);
47 static ManualResetEvent record_processing = new ManualResetEvent (true);
49 internal Stream innerStream;
50 internal MemoryStream inputBuffer;
51 internal Context context;
52 internal RecordProtocol protocol;
53 internal bool ownsStream;
54 private volatile bool disposed;
55 private bool checkCertRevocationStatus;
56 private object negotiate;
59 private ManualResetEvent negotiationComplete;
66 protected SslStreamBase(
72 throw new ArgumentNullException("stream is null.");
74 if (!stream.CanRead || !stream.CanWrite)
76 throw new ArgumentNullException("stream is not both readable and writable.");
79 this.inputBuffer = new MemoryStream();
80 this.innerStream = stream;
81 this.ownsStream = ownsStream;
82 this.negotiate = new object();
83 this.read = new object();
84 this.write = new object();
85 this.negotiationComplete = new ManualResetEvent(false);
91 private void AsyncHandshakeCallback(IAsyncResult asyncResult)
93 InternalAsyncResult internalResult = asyncResult.AsyncState as InternalAsyncResult;
99 this.OnNegotiateHandshakeCallback(asyncResult);
101 catch (TlsException ex)
103 this.protocol.SendAlert(ex.Alert);
105 throw new IOException("The authentication or decryption has failed.", ex);
109 this.protocol.SendAlert(AlertDescription.InternalError);
111 throw new IOException("The authentication or decryption has failed.", ex);
114 if (internalResult.ProceedAfterHandshake)
116 //kick off the read or write process (whichever called us) after the handshake is complete
117 if (internalResult.FromWrite)
119 InternalBeginWrite(internalResult);
123 InternalBeginRead(internalResult);
125 negotiationComplete.Set();
129 negotiationComplete.Set();
130 internalResult.SetComplete();
136 negotiationComplete.Set();
137 internalResult.SetComplete(ex);
141 internal bool MightNeedHandshake
145 if (this.context.HandshakeState == HandshakeState.Finished)
151 lock (this.negotiate)
153 return (this.context.HandshakeState != HandshakeState.Finished);
159 internal void NegotiateHandshake()
161 if (this.MightNeedHandshake)
163 InternalAsyncResult ar = new InternalAsyncResult(null, null, null, 0, 0, false, false);
165 //if something already started negotiation, wait for it.
166 //otherwise end it ourselves.
167 if (!BeginNegotiateHandshake(ar))
169 this.negotiationComplete.WaitOne();
173 this.EndNegotiateHandshake(ar);
180 #region Abstracts/Virtuals
182 internal abstract IAsyncResult OnBeginNegotiateHandshake(AsyncCallback callback, object state);
183 internal abstract void OnNegotiateHandshakeCallback(IAsyncResult asyncResult);
185 internal abstract X509Certificate OnLocalCertificateSelection(X509CertificateCollection clientCertificates,
186 X509Certificate serverCertificate,
188 X509CertificateCollection serverRequestedCertificates);
190 internal abstract bool OnRemoteCertificateValidation(X509Certificate certificate, int[] errors);
191 internal abstract ValidationResult OnRemoteCertificateValidation2 (Mono.Security.X509.X509CertificateCollection collection);
192 internal abstract bool HaveRemoteValidation2Callback { get; }
194 internal abstract AsymmetricAlgorithm OnLocalPrivateKeySelection(X509Certificate certificate, string targetHost);
198 #region Event Methods
200 internal X509Certificate RaiseLocalCertificateSelection(X509CertificateCollection certificates,
201 X509Certificate remoteCertificate,
203 X509CertificateCollection requestedCertificates)
205 return OnLocalCertificateSelection(certificates, remoteCertificate, targetHost, requestedCertificates);
208 internal bool RaiseRemoteCertificateValidation(X509Certificate certificate, int[] errors)
210 return OnRemoteCertificateValidation(certificate, errors);
213 internal ValidationResult RaiseRemoteCertificateValidation2 (Mono.Security.X509.X509CertificateCollection collection)
215 return OnRemoteCertificateValidation2 (collection);
218 internal AsymmetricAlgorithm RaiseLocalPrivateKeySelection(
219 X509Certificate certificate,
222 return OnLocalPrivateKeySelection(certificate, targetHost);
226 #region Security Properties
228 public bool CheckCertRevocationStatus
230 get { return this.checkCertRevocationStatus; }
231 set { this.checkCertRevocationStatus = value; }
234 public CipherAlgorithmType CipherAlgorithm
238 if (this.context.HandshakeState == HandshakeState.Finished)
240 return this.context.Current.Cipher.CipherAlgorithmType;
243 return CipherAlgorithmType.None;
247 public int CipherStrength
251 if (this.context.HandshakeState == HandshakeState.Finished)
253 return this.context.Current.Cipher.EffectiveKeyBits;
260 public HashAlgorithmType HashAlgorithm
264 if (this.context.HandshakeState == HandshakeState.Finished)
266 return this.context.Current.Cipher.HashAlgorithmType;
269 return HashAlgorithmType.None;
273 public int HashStrength
277 if (this.context.HandshakeState == HandshakeState.Finished)
279 return this.context.Current.Cipher.HashSize * 8;
286 public int KeyExchangeStrength
290 if (this.context.HandshakeState == HandshakeState.Finished)
292 return this.context.ServerSettings.Certificates[0].RSA.KeySize;
299 public ExchangeAlgorithmType KeyExchangeAlgorithm
303 if (this.context.HandshakeState == HandshakeState.Finished)
305 return this.context.Current.Cipher.ExchangeAlgorithmType;
308 return ExchangeAlgorithmType.None;
312 public SecurityProtocolType SecurityProtocol
316 if (this.context.HandshakeState == HandshakeState.Finished)
318 return this.context.SecurityProtocol;
325 public X509Certificate ServerCertificate
329 if (this.context.HandshakeState == HandshakeState.Finished)
331 if (this.context.ServerSettings.Certificates != null &&
332 this.context.ServerSettings.Certificates.Count > 0)
334 return new X509Certificate(this.context.ServerSettings.Certificates[0].RawData);
342 // this is used by Mono's certmgr tool to download certificates
343 internal Mono.Security.X509.X509CertificateCollection ServerCertificates
345 get { return context.ServerSettings.Certificates; }
350 #region Internal Async Result/State Class
352 private class InternalAsyncResult : IAsyncResult
354 private object locker = new object ();
355 private AsyncCallback _userCallback;
356 private object _userState;
357 private Exception _asyncException;
358 private ManualResetEvent handle;
359 private bool completed;
360 private int _bytesRead;
361 private bool _fromWrite;
362 private bool _proceedAfterHandshake;
364 private byte[] _buffer;
368 public InternalAsyncResult(AsyncCallback userCallback, object userState, byte[] buffer, int offset, int count, bool fromWrite, bool proceedAfterHandshake)
370 _userCallback = userCallback;
371 _userState = userState;
375 _fromWrite = fromWrite;
376 _proceedAfterHandshake = proceedAfterHandshake;
379 public bool ProceedAfterHandshake
381 get { return _proceedAfterHandshake; }
384 public bool FromWrite
386 get { return _fromWrite; }
391 get { return _buffer; }
396 get { return _offset; }
401 get { return _count; }
406 get { return _bytesRead; }
409 public object AsyncState
411 get { return _userState; }
414 public Exception AsyncException
416 get { return _asyncException; }
419 public bool CompletedWithError
422 if (IsCompleted == false)
424 return null != _asyncException;
428 public WaitHandle AsyncWaitHandle
433 handle = new ManualResetEvent (completed);
439 public bool CompletedSynchronously
441 get { return false; }
444 public bool IsCompleted
452 private void SetComplete(Exception ex, int bytesRead)
459 _asyncException = ex;
460 _bytesRead = bytesRead;
464 if (_userCallback != null)
465 _userCallback.BeginInvoke (this, null, null);
468 public void SetComplete(Exception ex)
473 public void SetComplete(int bytesRead)
475 SetComplete(null, bytesRead);
478 public void SetComplete()
480 SetComplete(null, 0);
485 #region Stream Overrides and Async Stream Operations
487 private bool BeginNegotiateHandshake(InternalAsyncResult asyncResult)
491 lock (this.negotiate)
493 if (this.context.HandshakeState == HandshakeState.None)
495 this.OnBeginNegotiateHandshake(new AsyncCallback(AsyncHandshakeCallback), asyncResult);
505 catch (TlsException ex)
507 this.negotiationComplete.Set();
508 this.protocol.SendAlert(ex.Alert);
510 throw new IOException("The authentication or decryption has failed.", ex);
514 this.negotiationComplete.Set();
515 this.protocol.SendAlert(AlertDescription.InternalError);
517 throw new IOException("The authentication or decryption has failed.", ex);
521 private void EndNegotiateHandshake(InternalAsyncResult asyncResult)
523 if (asyncResult.IsCompleted == false)
524 asyncResult.AsyncWaitHandle.WaitOne();
526 if (asyncResult.CompletedWithError)
528 throw asyncResult.AsyncException;
532 public override IAsyncResult BeginRead(
536 AsyncCallback callback,
539 this.checkDisposed();
543 throw new ArgumentNullException("buffer is a null reference.");
547 throw new ArgumentOutOfRangeException("offset is less than 0.");
549 if (offset > buffer.Length)
551 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
555 throw new ArgumentOutOfRangeException("count is less than 0.");
557 if (count > (buffer.Length - offset))
559 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
562 InternalAsyncResult asyncResult = new InternalAsyncResult(callback, state, buffer, offset, count, false, true);
564 if (this.MightNeedHandshake)
566 if (! BeginNegotiateHandshake(asyncResult))
568 //we made it down here so the handshake was not started.
569 //another thread must have started it in the mean time.
570 //wait for it to complete and then perform our original operation
571 this.negotiationComplete.WaitOne();
573 InternalBeginRead(asyncResult);
578 InternalBeginRead(asyncResult);
584 // bigger than max record length for SSL/TLS
585 private byte[] recbuf = new byte[16384];
587 private void InternalBeginRead(InternalAsyncResult asyncResult)
595 // If actual buffer is fully read, reset it
596 bool shouldReset = this.inputBuffer.Position == this.inputBuffer.Length && this.inputBuffer.Length > 0;
598 // If the buffer isn't fully read, but does have data, we need to immediately
599 // read the info from the buffer and let the user know that they have more data.
600 bool shouldReadImmediately = (this.inputBuffer.Length > 0) && (asyncResult.Count > 0);
606 else if (shouldReadImmediately)
608 preReadSize = this.inputBuffer.Read(asyncResult.Buffer, asyncResult.Offset, asyncResult.Count);
612 // This is explicitly done outside the synclock to avoid
613 // any potential deadlocks in the delegate call.
616 asyncResult.SetComplete(preReadSize);
618 else if (!this.context.ReceivedConnectionEnd)
620 // this will read data from the network until we have (at least) one
621 // record to send back to the caller
622 this.innerStream.BeginRead(recbuf, 0, recbuf.Length,
623 new AsyncCallback(InternalReadCallback), new object[] { recbuf, asyncResult });
627 // We're done with the connection so we need to let the caller know with 0 bytes read
628 asyncResult.SetComplete(0);
631 catch (TlsException ex)
633 this.protocol.SendAlert(ex.Alert);
635 throw new IOException("The authentication or decryption has failed.", ex);
639 throw new IOException("IO exception during read.", ex);
644 private MemoryStream recordStream = new MemoryStream();
646 // read encrypted data until we have enough to decrypt (at least) one
647 // record and return are the records (may be more than one) we have
648 private void InternalReadCallback(IAsyncResult result)
653 object[] state = (object[])result.AsyncState;
654 byte[] recbuf = (byte[])state[0];
655 InternalAsyncResult internalResult = (InternalAsyncResult)state[1];
659 int n = innerStream.EndRead(result);
662 // Add the just received data to the waiting data
663 recordStream.Write(recbuf, 0, n);
667 // 0 length data means this read operation is done (lost connection in the case of a network stream).
668 internalResult.SetComplete(0);
672 bool dataToReturn = false;
673 long pos = recordStream.Position;
675 recordStream.Position = 0;
676 byte[] record = null;
678 // don't try to decode record unless we have at least 5 bytes
679 // i.e. type (1), protocol (2) and length (2)
680 if (recordStream.Length >= 5)
682 record = this.protocol.ReceiveRecord(recordStream);
685 // a record of 0 length is valid (and there may be more record after it)
686 while (record != null)
688 // we probably received more stuff after the record, and we must keep it!
689 long remainder = recordStream.Length - recordStream.Position;
690 byte[] outofrecord = null;
693 outofrecord = new byte[remainder];
694 recordStream.Read(outofrecord, 0, outofrecord.Length);
699 long position = this.inputBuffer.Position;
701 if (record.Length > 0)
703 // Write new data to the inputBuffer
704 this.inputBuffer.Seek(0, SeekOrigin.End);
705 this.inputBuffer.Write(record, 0, record.Length);
707 // Restore buffer position
708 this.inputBuffer.Seek(position, SeekOrigin.Begin);
713 recordStream.SetLength(0);
718 recordStream.Write(outofrecord, 0, outofrecord.Length);
719 // type (1), protocol (2) and length (2)
720 if (recordStream.Length >= 5)
722 // try to see if another record is available
723 recordStream.Position = 0;
724 record = this.protocol.ReceiveRecord(recordStream);
726 pos = recordStream.Length;
735 if (!dataToReturn && (n > 0))
737 if (context.ReceivedConnectionEnd) {
738 internalResult.SetComplete (0);
740 // there is no record to return to caller and (possibly) more data waiting
741 // so continue reading from network (and appending to stream)
742 recordStream.Position = recordStream.Length;
743 this.innerStream.BeginRead(recbuf, 0, recbuf.Length,
744 new AsyncCallback(InternalReadCallback), state);
749 // we have record(s) to return -or- no more available to read from network
750 // reset position for further reading
751 recordStream.Position = pos;
756 bytesRead = this.inputBuffer.Read(internalResult.Buffer, internalResult.Offset, internalResult.Count);
759 internalResult.SetComplete(bytesRead);
764 internalResult.SetComplete(ex);
769 private void InternalBeginWrite(InternalAsyncResult asyncResult)
773 // Send the buffer as a TLS record
777 byte[] record = this.protocol.EncodeRecord(
778 ContentType.ApplicationData, asyncResult.Buffer, asyncResult.Offset, asyncResult.Count);
780 this.innerStream.BeginWrite(
781 record, 0, record.Length, new AsyncCallback(InternalWriteCallback), asyncResult);
784 catch (TlsException ex)
786 this.protocol.SendAlert(ex.Alert);
789 throw new IOException("The authentication or decryption has failed.", ex);
793 throw new IOException("IO exception during Write.", ex);
797 private void InternalWriteCallback(IAsyncResult ar)
802 InternalAsyncResult internalResult = (InternalAsyncResult)ar.AsyncState;
806 this.innerStream.EndWrite(ar);
807 internalResult.SetComplete();
811 internalResult.SetComplete(ex);
815 public override IAsyncResult BeginWrite(
819 AsyncCallback callback,
822 this.checkDisposed();
826 throw new ArgumentNullException("buffer is a null reference.");
830 throw new ArgumentOutOfRangeException("offset is less than 0.");
832 if (offset > buffer.Length)
834 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
838 throw new ArgumentOutOfRangeException("count is less than 0.");
840 if (count > (buffer.Length - offset))
842 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
846 InternalAsyncResult asyncResult = new InternalAsyncResult(callback, state, buffer, offset, count, true, true);
848 if (this.MightNeedHandshake)
850 if (! BeginNegotiateHandshake(asyncResult))
852 //we made it down here so the handshake was not started.
853 //another thread must have started it in the mean time.
854 //wait for it to complete and then perform our original operation
855 this.negotiationComplete.WaitOne();
857 InternalBeginWrite(asyncResult);
862 InternalBeginWrite(asyncResult);
868 public override int EndRead(IAsyncResult asyncResult)
870 this.checkDisposed();
872 InternalAsyncResult internalResult = asyncResult as InternalAsyncResult;
873 if (internalResult == null)
875 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
878 // Always wait until the read is complete
879 if (!asyncResult.IsCompleted)
881 if (!asyncResult.AsyncWaitHandle.WaitOne ())
882 throw new TlsException (AlertDescription.InternalError, "Couldn't complete EndRead");
885 if (internalResult.CompletedWithError)
887 throw internalResult.AsyncException;
890 return internalResult.BytesRead;
893 public override void EndWrite(IAsyncResult asyncResult)
895 this.checkDisposed();
897 InternalAsyncResult internalResult = asyncResult as InternalAsyncResult;
898 if (internalResult == null)
900 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginWrite.");
904 if (!asyncResult.IsCompleted)
906 if (!internalResult.AsyncWaitHandle.WaitOne ())
907 throw new TlsException (AlertDescription.InternalError, "Couldn't complete EndWrite");
910 if (internalResult.CompletedWithError)
912 throw internalResult.AsyncException;
916 public override void Close()
921 public override void Flush()
923 this.checkDisposed();
925 this.innerStream.Flush();
928 public int Read(byte[] buffer)
930 return this.Read(buffer, 0, buffer.Length);
933 public override int Read(byte[] buffer, int offset, int count)
935 this.checkDisposed ();
939 throw new ArgumentNullException ("buffer");
943 throw new ArgumentOutOfRangeException("offset is less than 0.");
945 if (offset > buffer.Length)
947 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
951 throw new ArgumentOutOfRangeException("count is less than 0.");
953 if (count > (buffer.Length - offset))
955 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
958 if (this.context.HandshakeState != HandshakeState.Finished)
960 this.NegotiateHandshake (); // Handshake negotiation
965 record_processing.Reset ();
966 // do we already have some decrypted data ?
967 if (this.inputBuffer.Position > 0) {
968 // or maybe we used all the buffer before ?
969 if (this.inputBuffer.Position == this.inputBuffer.Length) {
970 this.inputBuffer.SetLength (0);
972 int n = this.inputBuffer.Read (buffer, offset, count);
974 record_processing.Set ();
980 bool needMoreData = false;
982 // we first try to process the read with the data we already have
983 if ((recordStream.Position == 0) || needMoreData) {
984 needMoreData = false;
985 // if we loop, then it either means we need more data
986 byte[] recbuf = new byte[16384];
989 int value = innerStream.ReadByte ();
991 recbuf[0] = (byte) value;
995 n = innerStream.Read (recbuf, 0, recbuf.Length);
998 // Add the new received data to the waiting data
999 if ((recordStream.Length > 0) && (recordStream.Position != recordStream.Length))
1000 recordStream.Seek (0, SeekOrigin.End);
1001 recordStream.Write (recbuf, 0, n);
1003 // or that the read operation is done (lost connection in the case of a network stream).
1004 record_processing.Set ();
1009 bool dataToReturn = false;
1011 recordStream.Position = 0;
1012 byte[] record = null;
1014 // don't try to decode record unless we have at least 5 bytes
1015 // i.e. type (1), protocol (2) and length (2)
1016 if (recordStream.Length >= 5) {
1017 record = this.protocol.ReceiveRecord (recordStream);
1018 needMoreData = (record == null);
1021 // a record of 0 length is valid (and there may be more record after it)
1022 while (record != null) {
1023 // we probably received more stuff after the record, and we must keep it!
1024 long remainder = recordStream.Length - recordStream.Position;
1025 byte[] outofrecord = null;
1026 if (remainder > 0) {
1027 outofrecord = new byte[remainder];
1028 recordStream.Read (outofrecord, 0, outofrecord.Length);
1031 long position = this.inputBuffer.Position;
1033 if (record.Length > 0) {
1034 // Write new data to the inputBuffer
1035 this.inputBuffer.Seek (0, SeekOrigin.End);
1036 this.inputBuffer.Write (record, 0, record.Length);
1038 // Restore buffer position
1039 this.inputBuffer.Seek (position, SeekOrigin.Begin);
1040 dataToReturn = true;
1043 recordStream.SetLength (0);
1046 if (remainder > 0) {
1047 recordStream.Write (outofrecord, 0, outofrecord.Length);
1051 // we have record(s) to return -or- no more available to read from network
1052 // reset position for further reading
1053 int i = inputBuffer.Read (buffer, offset, count);
1054 record_processing.Set ();
1060 catch (TlsException ex)
1062 throw new IOException("The authentication or decryption has failed.", ex);
1064 catch (Exception ex)
1066 throw new IOException("IO exception during read.", ex);
1071 public override long Seek(long offset, SeekOrigin origin)
1073 throw new NotSupportedException();
1076 public override void SetLength(long value)
1078 throw new NotSupportedException();
1081 public void Write(byte[] buffer)
1083 this.Write(buffer, 0, buffer.Length);
1086 public override void Write(byte[] buffer, int offset, int count)
1088 this.checkDisposed ();
1092 throw new ArgumentNullException ("buffer");
1096 throw new ArgumentOutOfRangeException("offset is less than 0.");
1098 if (offset > buffer.Length)
1100 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
1104 throw new ArgumentOutOfRangeException("count is less than 0.");
1106 if (count > (buffer.Length - offset))
1108 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
1111 if (this.context.HandshakeState != HandshakeState.Finished)
1113 this.NegotiateHandshake ();
1120 // Send the buffer as a TLS record
1121 byte[] record = this.protocol.EncodeRecord (ContentType.ApplicationData, buffer, offset, count);
1122 this.innerStream.Write (record, 0, record.Length);
1124 catch (TlsException ex)
1126 this.protocol.SendAlert(ex.Alert);
1128 throw new IOException("The authentication or decryption has failed.", ex);
1130 catch (Exception ex)
1132 throw new IOException("IO exception during Write.", ex);
1137 public override bool CanRead
1139 get { return this.innerStream.CanRead; }
1142 public override bool CanSeek
1144 get { return false; }
1147 public override bool CanWrite
1149 get { return this.innerStream.CanWrite; }
1152 public override long Length
1154 get { throw new NotSupportedException(); }
1157 public override long Position
1161 throw new NotSupportedException();
1165 throw new NotSupportedException();
1170 #region IDisposable Members and Finalizer
1174 this.Dispose(false);
1177 protected override void Dispose (bool disposing)
1183 if (this.innerStream != null)
1185 if (this.context.HandshakeState == HandshakeState.Finished &&
1186 !this.context.SentConnectionEnd)
1188 // Write close notify
1190 this.protocol.SendAlert(AlertDescription.CloseNotify);
1195 if (this.ownsStream)
1197 // Close inner stream
1198 this.innerStream.Close();
1201 this.ownsStream = false;
1202 this.innerStream = null;
1205 this.disposed = true;
1206 base.Dispose (disposing);
1212 #region Misc Methods
1214 private void resetBuffer()
1216 this.inputBuffer.SetLength(0);
1217 this.inputBuffer.Position = 0;
1220 internal void checkDisposed()
1224 throw new ObjectDisposedException("The Stream is closed.");