this is not an exe
[mono.git] / mcs / class / Mono.Security / Mono.Security.Protocol.Tls / SslStreamBase.cs
1 // Transport Security Layer (TLS)
2 // Copyright (c) 2003-2004 Carlos Guzman Alvarez
3
4 //
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:
12 // 
13 // The above copyright notice and this permission notice shall be
14 // included in all copies or substantial portions of the Software.
15 // 
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.
23 //
24
25 using System;
26 using System.Collections;
27 using System.IO;
28 using System.Net;
29 using System.Net.Sockets;
30 using System.Security.Cryptography;
31 using System.Security.Cryptography.X509Certificates;
32 using System.Threading;
33
34 namespace Mono.Security.Protocol.Tls
35 {
36         public abstract class SslStreamBase: Stream, IDisposable
37         {
38                 private delegate void AsyncHandshakeDelegate(InternalAsyncResult asyncResult, bool fromWrite);
39                 
40                 #region Fields
41
42                 internal Stream innerStream;
43                 internal MemoryStream inputBuffer;
44                 internal Context context;
45                 internal RecordProtocol protocol;
46                 internal bool ownsStream;
47                 private volatile bool disposed;
48                 private bool checkCertRevocationStatus;
49                 private object negotiate;
50                 private object read;
51                 private object write;
52                 private ManualResetEvent negotiationComplete;
53
54                 #endregion
55
56
57                 #region Constructors
58
59                 protected SslStreamBase(
60                         Stream stream,
61                         bool ownsStream)
62                 {
63                         if (stream == null)
64                         {
65                                 throw new ArgumentNullException("stream is null.");
66                         }
67                         if (!stream.CanRead || !stream.CanWrite)
68                         {
69                                 throw new ArgumentNullException("stream is not both readable and writable.");
70                         }
71
72                         this.inputBuffer = new MemoryStream();
73                         this.innerStream = stream;
74                         this.ownsStream = ownsStream;
75                         this.negotiate = new object();
76                         this.read = new object();
77                         this.write = new object();
78                         this.negotiationComplete = new ManualResetEvent(false);
79                 }
80
81                 #endregion
82
83                 #region Handshakes
84                 private void AsyncHandshakeCallback(IAsyncResult asyncResult)
85                 {
86                         InternalAsyncResult internalResult = asyncResult.AsyncState as InternalAsyncResult;
87
88                         try
89                         {
90                                 try
91                                 {
92                                         this.OnNegotiateHandshakeCallback(asyncResult);
93                                 }
94                                 catch (TlsException ex)
95                                 {
96                                         this.protocol.SendAlert(ex.Alert);
97
98                                         throw new IOException("The authentication or decryption has failed.", ex);
99                                 }
100                                 catch (Exception ex)
101                                 {
102                                         this.protocol.SendAlert(AlertDescription.InternalError);
103
104                                         throw new IOException("The authentication or decryption has failed.", ex);
105                                 }
106
107                                 if (internalResult.ProceedAfterHandshake)
108                                 {
109                                         //kick off the read or write process (whichever called us) after the handshake is complete
110                                         if (internalResult.FromWrite)
111                                         {
112                                                 InternalBeginWrite(internalResult);
113                                         }
114                                         else
115                                         {
116                                                 InternalBeginRead(internalResult);
117                                         }
118                                         negotiationComplete.Set();
119                                 }
120                                 else
121                                 {
122                                         negotiationComplete.Set();
123                                         internalResult.SetComplete();
124                                 }
125
126                         }
127                         catch (Exception ex)
128                         {
129                                 negotiationComplete.Set();
130                                 internalResult.SetComplete(ex);
131                         }
132                 }
133
134                 internal bool MightNeedHandshake
135                 {
136                         get
137                         {
138                                 if (this.context.HandshakeState == HandshakeState.Finished)
139                                 {
140                                         return false;
141                                 }
142                                 else
143                                 {
144                                         lock (this.negotiate)
145                                         {
146                                                 return (this.context.HandshakeState != HandshakeState.Finished);
147                                         }
148                                 }
149                         }
150                 }
151
152                 internal void NegotiateHandshake()
153                 {
154                         if (this.MightNeedHandshake)
155                         {
156                                 InternalAsyncResult ar = new InternalAsyncResult(null, null, null, 0, 0, false, false);
157
158                                 //if something already started negotiation, wait for it.
159                                 //otherwise end it ourselves.
160                                 if (!BeginNegotiateHandshake(ar))
161                                 {
162                                         this.negotiationComplete.WaitOne();
163                                 }
164                                 else
165                                 {
166                                         this.EndNegotiateHandshake(ar);
167                                 }
168                         }
169                 }
170
171                 #endregion
172
173                 #region Abstracts/Virtuals
174
175                 internal abstract IAsyncResult OnBeginNegotiateHandshake(AsyncCallback callback, object state);
176                 internal abstract void OnNegotiateHandshakeCallback(IAsyncResult asyncResult);
177
178                 internal abstract X509Certificate OnLocalCertificateSelection(X509CertificateCollection clientCertificates,
179                                                                                                                         X509Certificate serverCertificate,
180                                                                                                                         string targetHost,
181                                                                                                                         X509CertificateCollection serverRequestedCertificates);
182
183                 internal abstract bool OnRemoteCertificateValidation(X509Certificate certificate, int[] errors);
184
185                 internal abstract AsymmetricAlgorithm OnLocalPrivateKeySelection(X509Certificate certificate, string targetHost);
186
187                 #endregion
188
189                 #region Event Methods
190
191                 internal X509Certificate RaiseLocalCertificateSelection(X509CertificateCollection certificates,
192                                                                                                                         X509Certificate remoteCertificate,
193                                                                                                                         string targetHost,
194                                                                                                                         X509CertificateCollection requestedCertificates)
195                 {
196                         return OnLocalCertificateSelection(certificates, remoteCertificate, targetHost, requestedCertificates);
197                 }
198
199                 internal bool RaiseRemoteCertificateValidation(X509Certificate certificate, int[] errors)
200                 {
201                         return OnRemoteCertificateValidation(certificate, errors);
202                 }
203
204                 internal AsymmetricAlgorithm RaiseLocalPrivateKeySelection(
205                         X509Certificate certificate,
206                         string targetHost)
207                 {
208                         return OnLocalPrivateKeySelection(certificate, targetHost);
209                 }
210                 #endregion
211
212                 #region Security Properties
213
214                 public bool CheckCertRevocationStatus
215                 {
216                         get { return this.checkCertRevocationStatus; }
217                         set { this.checkCertRevocationStatus = value; }
218                 }
219
220                 public CipherAlgorithmType CipherAlgorithm
221                 {
222                         get
223                         {
224                                 if (this.context.HandshakeState == HandshakeState.Finished)
225                                 {
226                                         return this.context.Cipher.CipherAlgorithmType;
227                                 }
228
229                                 return CipherAlgorithmType.None;
230                         }
231                 }
232
233                 public int CipherStrength
234                 {
235                         get
236                         {
237                                 if (this.context.HandshakeState == HandshakeState.Finished)
238                                 {
239                                         return this.context.Cipher.EffectiveKeyBits;
240                                 }
241
242                                 return 0;
243                         }
244                 }
245
246                 public HashAlgorithmType HashAlgorithm
247                 {
248                         get
249                         {
250                                 if (this.context.HandshakeState == HandshakeState.Finished)
251                                 {
252                                         return this.context.Cipher.HashAlgorithmType;
253                                 }
254
255                                 return HashAlgorithmType.None;
256                         }
257                 }
258
259                 public int HashStrength
260                 {
261                         get
262                         {
263                                 if (this.context.HandshakeState == HandshakeState.Finished)
264                                 {
265                                         return this.context.Cipher.HashSize * 8;
266                                 }
267
268                                 return 0;
269                         }
270                 }
271
272                 public int KeyExchangeStrength
273                 {
274                         get
275                         {
276                                 if (this.context.HandshakeState == HandshakeState.Finished)
277                                 {
278                                         return this.context.ServerSettings.Certificates[0].RSA.KeySize;
279                                 }
280
281                                 return 0;
282                         }
283                 }
284
285                 public ExchangeAlgorithmType KeyExchangeAlgorithm
286                 {
287                         get
288                         {
289                                 if (this.context.HandshakeState == HandshakeState.Finished)
290                                 {
291                                         return this.context.Cipher.ExchangeAlgorithmType;
292                                 }
293
294                                 return ExchangeAlgorithmType.None;
295                         }
296                 }
297
298                 public SecurityProtocolType SecurityProtocol
299                 {
300                         get
301                         {
302                                 if (this.context.HandshakeState == HandshakeState.Finished)
303                                 {
304                                         return this.context.SecurityProtocol;
305                                 }
306
307                                 return 0;
308                         }
309                 }
310
311                 public X509Certificate ServerCertificate
312                 {
313                         get
314                         {
315                                 if (this.context.HandshakeState == HandshakeState.Finished)
316                                 {
317                                         if (this.context.ServerSettings.Certificates != null &&
318                                                 this.context.ServerSettings.Certificates.Count > 0)
319                                         {
320                                                 return new X509Certificate(this.context.ServerSettings.Certificates[0].RawData);
321                                         }
322                                 }
323
324                                 return null;
325                         }
326                 }
327
328                 // this is used by Mono's certmgr tool to download certificates
329                 internal Mono.Security.X509.X509CertificateCollection ServerCertificates
330                 {
331                         get { return context.ServerSettings.Certificates; }
332                 }
333
334                 #endregion
335
336                 #region Internal Async Result/State Class
337
338                 private class InternalAsyncResult : IAsyncResult
339                 {
340                         private object locker = new object ();
341                         private AsyncCallback _userCallback;
342                         private object _userState;
343                         private Exception _asyncException;
344                         private ManualResetEvent handle;
345                         private bool completed;
346                         private int _bytesRead;
347                         private bool _fromWrite;
348                         private bool _proceedAfterHandshake;
349
350                         private byte[] _buffer;
351                         private int _offset;
352                         private int _count;
353
354                         public InternalAsyncResult(AsyncCallback userCallback, object userState, byte[] buffer, int offset, int count, bool fromWrite, bool proceedAfterHandshake)
355                         {
356                                 _userCallback = userCallback;
357                                 _userState = userState;
358                                 _buffer = buffer;
359                                 _offset = offset;
360                                 _count = count;
361                                 _fromWrite = fromWrite;
362                                 _proceedAfterHandshake = proceedAfterHandshake;
363                         }
364
365                         public bool ProceedAfterHandshake
366                         {
367                                 get { return _proceedAfterHandshake; }
368                         }
369
370                         public bool FromWrite
371                         {
372                                 get { return _fromWrite; }
373                         }
374
375                         public byte[] Buffer
376                         {
377                                 get { return _buffer; }
378                         }
379
380                         public int Offset
381                         {
382                                 get { return _offset; }
383                         }
384
385                         public int Count
386                         {
387                                 get { return _count; }
388                         }
389
390                         public int BytesRead
391                         {
392                                 get { return _bytesRead; }
393                         }
394
395                         public object AsyncState
396                         {
397                                 get { return _userState; }
398                         }
399
400                         public Exception AsyncException
401                         {
402                                 get { return _asyncException; }
403                         }
404
405                         public bool CompletedWithError
406                         {
407                                 get {
408                                         if (IsCompleted == false)
409                                                 return false;
410                                         return null != _asyncException;
411                                 }
412                         }
413
414                         public WaitHandle AsyncWaitHandle
415                         {
416                                 get {
417                                         lock (locker) {
418                                                 if (handle == null)
419                                                         handle = new ManualResetEvent (completed);
420                                         }
421                                         return handle;
422                                 }
423                         }
424
425                         public bool CompletedSynchronously
426                         {
427                                 get { return false; }
428                         }
429
430                         public bool IsCompleted
431                         {
432                                 get {
433                                         lock (locker)
434                                                 return completed;
435                                 }
436                         }
437
438                         private void SetComplete(Exception ex, int bytesRead)
439                         {
440                                 lock (locker) {
441                                         if (completed)
442                                                 return;
443
444                                         completed = true;
445                                         if (handle != null)
446                                                 handle.Set ();
447                                         _asyncException = ex;
448                                         _bytesRead = bytesRead;
449                                 }
450                                 if (_userCallback != null)
451                                         _userCallback.BeginInvoke (this, null, null);
452                         }
453
454                         public void SetComplete(Exception ex)
455                         {
456                                 SetComplete(ex, 0);
457                         }
458
459                         public void SetComplete(int bytesRead)
460                         {
461                                 SetComplete(null, bytesRead);
462                         }
463
464                         public void SetComplete()
465                         {
466                                 SetComplete(null, 0);
467                         }
468                 }
469                 #endregion
470
471                 #region Stream Overrides and Async Stream Operations
472
473                 private bool BeginNegotiateHandshake(InternalAsyncResult asyncResult)
474                 {
475                         try
476                         {
477                                 lock (this.negotiate)
478                                 {
479                                         if (this.context.HandshakeState == HandshakeState.None)
480                                         {
481                                                 this.OnBeginNegotiateHandshake(new AsyncCallback(AsyncHandshakeCallback), asyncResult);
482
483                                                 return true;
484                                         }
485                                         else
486                                         {
487                                                 return false;
488                                         }
489                                 }
490                         }
491                         catch (TlsException ex)
492                         {
493                                 this.negotiationComplete.Set();
494                                 this.protocol.SendAlert(ex.Alert);
495
496                                 throw new IOException("The authentication or decryption has failed.", ex);
497                         }
498                         catch (Exception ex)
499                         {
500                                 this.negotiationComplete.Set();
501                                 this.protocol.SendAlert(AlertDescription.InternalError);
502
503                                 throw new IOException("The authentication or decryption has failed.", ex);
504                         }
505                 }
506
507                 private void EndNegotiateHandshake(InternalAsyncResult asyncResult)
508                 {
509                         if (asyncResult.IsCompleted == false)
510                                 asyncResult.AsyncWaitHandle.WaitOne();
511
512                         if (asyncResult.CompletedWithError)
513                         {
514                                 throw asyncResult.AsyncException;
515                         }
516                 }
517
518                 public override IAsyncResult BeginRead(
519                         byte[] buffer,
520                         int offset,
521                         int count,
522                         AsyncCallback callback,
523                         object state)
524                 {
525                         this.checkDisposed();
526
527                         if (buffer == null)
528                         {
529                                 throw new ArgumentNullException("buffer is a null reference.");
530                         }
531                         if (offset < 0)
532                         {
533                                 throw new ArgumentOutOfRangeException("offset is less than 0.");
534                         }
535                         if (offset > buffer.Length)
536                         {
537                                 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
538                         }
539                         if (count < 0)
540                         {
541                                 throw new ArgumentOutOfRangeException("count is less than 0.");
542                         }
543                         if (count > (buffer.Length - offset))
544                         {
545                                 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
546                         }
547
548                         InternalAsyncResult asyncResult = new InternalAsyncResult(callback, state, buffer, offset, count, false, true);
549
550                         if (this.MightNeedHandshake)
551                         {
552                                 if (! BeginNegotiateHandshake(asyncResult))
553                                 {
554                                         //we made it down here so the handshake was not started.
555                                         //another thread must have started it in the mean time.
556                                         //wait for it to complete and then perform our original operation
557                                         this.negotiationComplete.WaitOne();
558
559                                         InternalBeginRead(asyncResult);
560                                 }
561                         }
562                         else
563                         {
564                                 InternalBeginRead(asyncResult);
565                         }
566
567                         return asyncResult;
568                 }
569
570                 private void InternalBeginRead(InternalAsyncResult asyncResult)
571                 {
572                         try
573                         {
574                                 int preReadSize = 0;
575
576                                 lock (this.read)
577                                 {
578                                         // If actual buffer is fully read, reset it
579                                         bool shouldReset = this.inputBuffer.Position == this.inputBuffer.Length && this.inputBuffer.Length > 0;
580
581                                         // If the buffer isn't fully read, but does have data, we need to immediately
582                                         // read the info from the buffer and let the user know that they have more data.
583                                         bool shouldReadImmediately = (this.inputBuffer.Length > 0) && (asyncResult.Count > 0);
584
585                                         if (shouldReset)
586                                         {
587                                                 this.resetBuffer();
588                                         }
589                                         else if (shouldReadImmediately)
590                                         {
591                                                 preReadSize = this.inputBuffer.Read(asyncResult.Buffer, asyncResult.Offset, asyncResult.Count);
592                                         }
593                                 }
594
595                                 // This is explicitly done outside the synclock to avoid 
596                                 // any potential deadlocks in the delegate call.
597                                 if (0 < preReadSize)
598                                 {
599                                         asyncResult.SetComplete(preReadSize);
600                                 }
601                                 else if (!this.context.ConnectionEnd)
602                                 {
603                                         // bigger than max record length for SSL/TLS
604                                         byte[] recbuf = new byte[16384];
605
606                                         // this will read data from the network until we have (at least) one
607                                         // record to send back to the caller
608                                         this.innerStream.BeginRead(recbuf, 0, recbuf.Length,
609                                                 new AsyncCallback(InternalReadCallback), new object[] { recbuf, asyncResult });
610                                 }
611                                 else
612                                 {
613                                         // We're done with the connection so we need to let the caller know with 0 bytes read
614                                         asyncResult.SetComplete(0);
615                                 }
616                         }
617                         catch (TlsException ex)
618                         {
619                                 this.protocol.SendAlert(ex.Alert);
620
621                                 throw new IOException("The authentication or decryption has failed.", ex);
622                         }
623                         catch (Exception ex)
624                         {
625                                 throw new IOException("IO exception during read.", ex);
626                         }
627                 }
628
629
630                 private MemoryStream recordStream = new MemoryStream();
631
632                 // read encrypted data until we have enough to decrypt (at least) one
633                 // record and return are the records (may be more than one) we have
634                 private void InternalReadCallback(IAsyncResult result)
635                 {
636                         if (this.disposed)
637                                 return;
638
639                         object[] state = (object[])result.AsyncState;
640                         byte[] recbuf = (byte[])state[0];
641                         InternalAsyncResult internalResult = (InternalAsyncResult)state[1];
642
643                         try
644                         {
645                                 int n = innerStream.EndRead(result);
646                                 if (n > 0)
647                                 {
648                                         // Add the just received data to the waiting data
649                                         recordStream.Write(recbuf, 0, n);
650                                 }
651                                 else
652                                 {
653                                         // 0 length data means this read operation is done (lost connection in the case of a network stream).
654                                         internalResult.SetComplete(0);
655                                         return;
656                                 }
657
658                                 bool dataToReturn = false;
659                                 long pos = recordStream.Position;
660
661                                 recordStream.Position = 0;
662                                 byte[] record = null;
663
664                                 // don't try to decode record unless we have at least 5 bytes
665                                 // i.e. type (1), protocol (2) and length (2)
666                                 if (recordStream.Length >= 5)
667                                 {
668                                         record = this.protocol.ReceiveRecord(recordStream);
669                                 }
670
671                                 // a record of 0 length is valid (and there may be more record after it)
672                                 while (record != null)
673                                 {
674                                         // we probably received more stuff after the record, and we must keep it!
675                                         long remainder = recordStream.Length - recordStream.Position;
676                                         byte[] outofrecord = null;
677                                         if (remainder > 0)
678                                         {
679                                                 outofrecord = new byte[remainder];
680                                                 recordStream.Read(outofrecord, 0, outofrecord.Length);
681                                         }
682
683                                         lock (this.read)
684                                         {
685                                                 long position = this.inputBuffer.Position;
686
687                                                 if (record.Length > 0)
688                                                 {
689                                                         // Write new data to the inputBuffer
690                                                         this.inputBuffer.Seek(0, SeekOrigin.End);
691                                                         this.inputBuffer.Write(record, 0, record.Length);
692
693                                                         // Restore buffer position
694                                                         this.inputBuffer.Seek(position, SeekOrigin.Begin);
695                                                         dataToReturn = true;
696                                                 }
697                                         }
698
699                                         recordStream.SetLength(0);
700                                         record = null;
701
702                                         if (remainder > 0)
703                                         {
704                                                 recordStream.Write(outofrecord, 0, outofrecord.Length);
705                                                 // type (1), protocol (2) and length (2)
706                                                 if (recordStream.Length >= 5)
707                                                 {
708                                                         // try to see if another record is available
709                                                         recordStream.Position = 0;
710                                                         record = this.protocol.ReceiveRecord(recordStream);
711                                                         if (record == null)
712                                                                 pos = recordStream.Length;
713                                                 }
714                                                 else
715                                                         pos = remainder;
716                                         }
717                                         else
718                                                 pos = 0;
719                                 }
720
721                                 if (!dataToReturn && (n > 0))
722                                 {
723                                         // there is no record to return to caller and (possibly) more data waiting
724                                         // so continue reading from network (and appending to stream)
725                                         recordStream.Position = recordStream.Length;
726                                         this.innerStream.BeginRead(recbuf, 0, recbuf.Length,
727                                                 new AsyncCallback(InternalReadCallback), state);
728                                 }
729                                 else
730                                 {
731                                         // we have record(s) to return -or- no more available to read from network
732                                         // reset position for further reading
733                                         recordStream.Position = pos;
734
735                                         int bytesRead = 0;
736                                         lock (this.read)
737                                         {
738                                                 bytesRead = this.inputBuffer.Read(internalResult.Buffer, internalResult.Offset, internalResult.Count);
739                                         }
740
741                                         internalResult.SetComplete(bytesRead);
742                                 }
743                         }
744                         catch (Exception ex)
745                         {
746                                 internalResult.SetComplete(ex);
747                         }
748
749                 }
750
751                 private void InternalBeginWrite(InternalAsyncResult asyncResult)
752                 {
753                         try
754                         {
755                                 // Send the buffer as a TLS record
756
757                                 lock (this.write)
758                                 {
759                                         byte[] record = this.protocol.EncodeRecord(
760                                                 ContentType.ApplicationData, asyncResult.Buffer, asyncResult.Offset, asyncResult.Count);
761
762                                         this.innerStream.BeginWrite(
763                                                 record, 0, record.Length, new AsyncCallback(InternalWriteCallback), asyncResult);
764                                 }
765                         }
766                         catch (TlsException ex)
767                         {
768                                 this.protocol.SendAlert(ex.Alert);
769                                 this.Close();
770
771                                 throw new IOException("The authentication or decryption has failed.", ex);
772                         }
773                         catch (Exception ex)
774                         {
775                                 throw new IOException("IO exception during Write.", ex);
776                         }
777                 }
778
779                 private void InternalWriteCallback(IAsyncResult ar)
780                 {
781                         if (this.disposed)
782                                 return;
783                         
784                         InternalAsyncResult internalResult = (InternalAsyncResult)ar.AsyncState;
785
786                         try
787                         {
788                                 this.innerStream.EndWrite(ar);
789                                 internalResult.SetComplete();
790                         }
791                         catch (Exception ex)
792                         {
793                                 internalResult.SetComplete(ex);
794                         }
795                 }
796
797                 public override IAsyncResult BeginWrite(
798                         byte[] buffer,
799                         int offset,
800                         int count,
801                         AsyncCallback callback,
802                         object state)
803                 {
804                         this.checkDisposed();
805
806                         if (buffer == null)
807                         {
808                                 throw new ArgumentNullException("buffer is a null reference.");
809                         }
810                         if (offset < 0)
811                         {
812                                 throw new ArgumentOutOfRangeException("offset is less than 0.");
813                         }
814                         if (offset > buffer.Length)
815                         {
816                                 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
817                         }
818                         if (count < 0)
819                         {
820                                 throw new ArgumentOutOfRangeException("count is less than 0.");
821                         }
822                         if (count > (buffer.Length - offset))
823                         {
824                                 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
825                         }
826
827
828                         InternalAsyncResult asyncResult = new InternalAsyncResult(callback, state, buffer, offset, count, true, true);
829
830                         if (this.MightNeedHandshake)
831                         {
832                                 if (! BeginNegotiateHandshake(asyncResult))
833                                 {
834                                         //we made it down here so the handshake was not started.
835                                         //another thread must have started it in the mean time.
836                                         //wait for it to complete and then perform our original operation
837                                         this.negotiationComplete.WaitOne();
838
839                                         InternalBeginWrite(asyncResult);
840                                 }
841                         }
842                         else
843                         {
844                                 InternalBeginWrite(asyncResult);
845                         }
846
847                         return asyncResult;
848                 }
849
850                 public override int EndRead(IAsyncResult asyncResult)
851                 {
852                         this.checkDisposed();
853
854                         InternalAsyncResult internalResult = asyncResult as InternalAsyncResult;
855                         if (internalResult == null)
856                         {
857                                 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
858                         }
859
860                         // Always wait until the read is complete
861                         if (asyncResult.IsCompleted == false)
862                                 asyncResult.AsyncWaitHandle.WaitOne();
863
864                         if (internalResult.CompletedWithError)
865                         {
866                                 throw internalResult.AsyncException;
867                         }
868
869                         return internalResult.BytesRead;
870                 }
871
872                 public override void EndWrite(IAsyncResult asyncResult)
873                 {
874                         this.checkDisposed();
875
876                         InternalAsyncResult internalResult = asyncResult as InternalAsyncResult;
877                         if (internalResult == null)
878                         {
879                                 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginWrite.");
880                         }
881
882
883                         if (asyncResult.IsCompleted == false)
884                                 internalResult.AsyncWaitHandle.WaitOne();
885
886                         if (internalResult.CompletedWithError)
887                         {
888                                 throw internalResult.AsyncException;
889                         }
890                 }
891
892                 public override void Close()
893                 {
894                         ((IDisposable)this).Dispose();
895                 }
896
897                 public override void Flush()
898                 {
899                         this.checkDisposed();
900
901                         this.innerStream.Flush();
902                 }
903
904                 public int Read(byte[] buffer)
905                 {
906                         return this.Read(buffer, 0, buffer.Length);
907                 }
908
909                 public override int Read(byte[] buffer, int offset, int count)
910                 {
911                         IAsyncResult res = this.BeginRead(buffer, offset, count, null, null);
912
913                         return this.EndRead(res);
914                 }
915
916                 public override long Seek(long offset, SeekOrigin origin)
917                 {
918                         throw new NotSupportedException();
919                 }
920
921                 public override void SetLength(long value)
922                 {
923                         throw new NotSupportedException();
924                 }
925
926                 public void Write(byte[] buffer)
927                 {
928                         this.Write(buffer, 0, buffer.Length);
929                 }
930
931                 public override void Write(byte[] buffer, int offset, int count)
932                 {
933                         IAsyncResult res = this.BeginWrite(buffer, offset, count, null, null);
934
935                         this.EndWrite(res);
936                 }
937
938                 public override bool CanRead
939                 {
940                         get { return this.innerStream.CanRead; }
941                 }
942
943                 public override bool CanSeek
944                 {
945                         get { return false; }
946                 }
947
948                 public override bool CanWrite
949                 {
950                         get { return this.innerStream.CanWrite; }
951                 }
952
953                 public override long Length
954                 {
955                         get { throw new NotSupportedException(); }
956                 }
957
958                 public override long Position
959                 {
960                         get
961                         {
962                                 throw new NotSupportedException();
963                         }
964                         set
965                         {
966                                 throw new NotSupportedException();
967                         }
968                 }
969                 #endregion
970
971                 #region IDisposable Members and Finalizer
972
973                 ~SslStreamBase()
974                 {
975                         this.Dispose(false);
976                 }
977
978                 public void Dispose()
979                 {
980                         this.Dispose(true);
981                         GC.SuppressFinalize(this);
982                 }
983
984                 protected virtual void Dispose(bool disposing)
985                 {
986                         if (!this.disposed)
987                         {
988                                 if (disposing)
989                                 {
990                                         if (this.innerStream != null)
991                                         {
992                                                 if (this.context.HandshakeState == HandshakeState.Finished &&
993                                                         !this.context.ConnectionEnd)
994                                                 {
995                                                         // Write close notify                                                   
996                                                         this.protocol.SendAlert(AlertDescription.CloseNotify);
997                                                 }
998
999                                                 if (this.ownsStream)
1000                                                 {
1001                                                         // Close inner stream
1002                                                         this.innerStream.Close();
1003                                                 }
1004                                         }
1005                                         this.ownsStream = false;
1006                                         this.innerStream = null;
1007                                 }
1008
1009                                 this.disposed = true;
1010                         }
1011                 }
1012
1013                 #endregion
1014
1015                 #region Misc Methods
1016
1017                 private void resetBuffer()
1018                 {
1019                         this.inputBuffer.SetLength(0);
1020                         this.inputBuffer.Position = 0;
1021                 }
1022
1023                 internal void checkDisposed()
1024                 {
1025                         if (this.disposed)
1026                         {
1027                                 throw new ObjectDisposedException("The Stream is closed.");
1028                         }
1029                 }
1030
1031                 #endregion
1032
1033         }
1034 }