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